blob: 1fae4ad2a2b6940a0436185d5e512a22e5099bec [file] [log] [blame]
// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "net/http/http_cache.h"
#include <algorithm>
#include "base/compiler_specific.h"
#if defined(OS_POSIX)
#include <unistd.h>
#endif
#include "base/message_loop.h"
#include "base/pickle.h"
#include "base/ref_counted.h"
#include "base/string_util.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/disk_cache/disk_cache.h"
#include "net/http/http_cache_transaction.h"
#include "net/http/http_network_layer.h"
#include "net/http/http_network_session.h"
#include "net/http/http_request_info.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_response_info.h"
namespace net {
// disk cache entry data indices.
enum {
kResponseInfoIndex,
kResponseContentIndex
};
//-----------------------------------------------------------------------------
HttpCache::ActiveEntry::ActiveEntry(disk_cache::Entry* e)
: disk_entry(e),
writer(NULL),
will_process_pending_queue(false),
doomed(false) {
}
HttpCache::ActiveEntry::~ActiveEntry() {
if (disk_entry)
disk_entry->Close();
}
//-----------------------------------------------------------------------------
HttpCache::HttpCache(HostResolver* host_resolver,
ProxyService* proxy_service,
SSLConfigService* ssl_config_service,
const FilePath& cache_dir,
int cache_size)
: disk_cache_dir_(cache_dir),
mode_(NORMAL),
type_(DISK_CACHE),
network_layer_(HttpNetworkLayer::CreateFactory(
host_resolver, proxy_service, ssl_config_service)),
ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)),
enable_range_support_(true),
cache_size_(cache_size) {
}
HttpCache::HttpCache(HttpNetworkSession* session,
const FilePath& cache_dir,
int cache_size)
: disk_cache_dir_(cache_dir),
mode_(NORMAL),
type_(DISK_CACHE),
network_layer_(HttpNetworkLayer::CreateFactory(session)),
ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)),
enable_range_support_(true),
cache_size_(cache_size) {
}
HttpCache::HttpCache(HostResolver* host_resolver,
ProxyService* proxy_service,
SSLConfigService* ssl_config_service,
int cache_size)
: mode_(NORMAL),
type_(MEMORY_CACHE),
network_layer_(HttpNetworkLayer::CreateFactory(
host_resolver, proxy_service, ssl_config_service)),
ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)),
enable_range_support_(true),
cache_size_(cache_size) {
}
HttpCache::HttpCache(HttpTransactionFactory* network_layer,
disk_cache::Backend* disk_cache)
: mode_(NORMAL),
type_(DISK_CACHE),
network_layer_(network_layer),
disk_cache_(disk_cache),
ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)),
enable_range_support_(true),
cache_size_(0) {
}
HttpCache::~HttpCache() {
// If we have any active entries remaining, then we need to deactivate them.
// We may have some pending calls to OnProcessPendingQueue, but since those
// won't run (due to our destruction), we can simply ignore the corresponding
// will_process_pending_queue flag.
while (!active_entries_.empty()) {
ActiveEntry* entry = active_entries_.begin()->second;
entry->will_process_pending_queue = false;
entry->pending_queue.clear();
entry->readers.clear();
entry->writer = NULL;
DeactivateEntry(entry);
}
ActiveEntriesSet::iterator it = doomed_entries_.begin();
for (; it != doomed_entries_.end(); ++it)
delete *it;
}
disk_cache::Backend* HttpCache::GetBackend() {
if (disk_cache_.get())
return disk_cache_.get();
DCHECK_GE(cache_size_, 0);
if (type_ == MEMORY_CACHE) {
// We may end up with no folder name and no cache if the initialization
// of the disk cache fails. We want to be sure that what we wanted to have
// was an in-memory cache.
disk_cache_.reset(disk_cache::CreateInMemoryCacheBackend(cache_size_));
} else if (!disk_cache_dir_.empty()) {
disk_cache_.reset(disk_cache::CreateCacheBackend(disk_cache_dir_, true,
cache_size_, type_));
disk_cache_dir_ = FilePath(); // Reclaim memory.
}
return disk_cache_.get();
}
int HttpCache::CreateTransaction(scoped_ptr<HttpTransaction>* trans) {
// Do lazy initialization of disk cache if needed.
GetBackend();
trans->reset(new HttpCache::Transaction(this, enable_range_support_));
return OK;
}
HttpCache* HttpCache::GetCache() {
return this;
}
HttpNetworkSession* HttpCache::GetSession() {
net::HttpNetworkLayer* network =
static_cast<net::HttpNetworkLayer*>(network_layer_.get());
return network->GetSession();
}
void HttpCache::Suspend(bool suspend) {
network_layer_->Suspend(suspend);
}
// static
bool HttpCache::ParseResponseInfo(const char* data, int len,
HttpResponseInfo* response_info,
bool* response_truncated) {
Pickle pickle(data, len);
return response_info->InitFromPickle(pickle, response_truncated);
}
// static
bool HttpCache::ReadResponseInfo(disk_cache::Entry* disk_entry,
HttpResponseInfo* response_info,
bool* response_truncated) {
int size = disk_entry->GetDataSize(kResponseInfoIndex);
scoped_refptr<IOBuffer> buffer = new IOBuffer(size);
int rv = disk_entry->ReadData(kResponseInfoIndex, 0, buffer, size, NULL);
if (rv != size) {
DLOG(ERROR) << "ReadData failed: " << rv;
return false;
}
return ParseResponseInfo(buffer->data(), size, response_info,
response_truncated);
}
// static
bool HttpCache::WriteResponseInfo(disk_cache::Entry* disk_entry,
const HttpResponseInfo* response_info,
bool skip_transient_headers,
bool response_truncated) {
Pickle pickle;
response_info->Persist(
&pickle, skip_transient_headers, response_truncated);
scoped_refptr<WrappedIOBuffer> data = new WrappedIOBuffer(
reinterpret_cast<const char*>(pickle.data()));
int len = static_cast<int>(pickle.size());
return disk_entry->WriteData(kResponseInfoIndex, 0, data, len, NULL,
true) == len;
}
// Generate a key that can be used inside the cache.
std::string HttpCache::GenerateCacheKey(const HttpRequestInfo* request) {
// Strip out the reference, username, and password sections of the URL.
std::string url = HttpUtil::SpecForRequest(request->url);
DCHECK(mode_ != DISABLE);
if (mode_ == NORMAL) {
// No valid URL can begin with numerals, so we should not have to worry
// about collisions with normal URLs.
if (request->upload_data && request->upload_data->identifier())
url.insert(0, StringPrintf("%lld/", request->upload_data->identifier()));
return url;
}
// In playback and record mode, we cache everything.
// Lazily initialize.
if (playback_cache_map_ == NULL)
playback_cache_map_.reset(new PlaybackCacheMap());
// Each time we request an item from the cache, we tag it with a
// generation number. During playback, multiple fetches for the same
// item will use the same generation number and pull the proper
// instance of an URL from the cache.
int generation = 0;
DCHECK(playback_cache_map_ != NULL);
if (playback_cache_map_->find(url) != playback_cache_map_->end())
generation = (*playback_cache_map_)[url];
(*playback_cache_map_)[url] = generation + 1;
// The key into the cache is GENERATION # + METHOD + URL.
std::string result = IntToString(generation);
result.append(request->method);
result.append(url);
return result;
}
void HttpCache::DoomEntry(const std::string& key) {
// Need to abandon the ActiveEntry, but any transaction attached to the entry
// should not be impacted. Dooming an entry only means that it will no
// longer be returned by FindActiveEntry (and it will also be destroyed once
// all consumers are finished with the entry).
ActiveEntriesMap::iterator it = active_entries_.find(key);
if (it == active_entries_.end()) {
disk_cache_->DoomEntry(key);
} else {
ActiveEntry* entry = it->second;
active_entries_.erase(it);
// We keep track of doomed entries so that we can ensure that they are
// cleaned up properly when the cache is destroyed.
doomed_entries_.insert(entry);
entry->disk_entry->Doom();
entry->doomed = true;
DCHECK(entry->writer || !entry->readers.empty());
}
}
void HttpCache::FinalizeDoomedEntry(ActiveEntry* entry) {
DCHECK(entry->doomed);
DCHECK(!entry->writer);
DCHECK(entry->readers.empty());
DCHECK(entry->pending_queue.empty());
ActiveEntriesSet::iterator it = doomed_entries_.find(entry);
DCHECK(it != doomed_entries_.end());
doomed_entries_.erase(it);
delete entry;
}
HttpCache::ActiveEntry* HttpCache::FindActiveEntry(const std::string& key) {
ActiveEntriesMap::const_iterator it = active_entries_.find(key);
return it != active_entries_.end() ? it->second : NULL;
}
HttpCache::ActiveEntry* HttpCache::OpenEntry(const std::string& key) {
DCHECK(!FindActiveEntry(key));
disk_cache::Entry* disk_entry;
if (!disk_cache_->OpenEntry(key, &disk_entry))
return NULL;
return ActivateEntry(key, disk_entry);
}
HttpCache::ActiveEntry* HttpCache::CreateEntry(const std::string& key) {
DCHECK(!FindActiveEntry(key));
disk_cache::Entry* disk_entry;
if (!disk_cache_->CreateEntry(key, &disk_entry))
return NULL;
return ActivateEntry(key, disk_entry);
}
void HttpCache::DestroyEntry(ActiveEntry* entry) {
if (entry->doomed) {
FinalizeDoomedEntry(entry);
} else {
DeactivateEntry(entry);
}
}
HttpCache::ActiveEntry* HttpCache::ActivateEntry(
const std::string& key,
disk_cache::Entry* disk_entry) {
ActiveEntry* entry = new ActiveEntry(disk_entry);
active_entries_[key] = entry;
return entry;
}
void HttpCache::DeactivateEntry(ActiveEntry* entry) {
DCHECK(!entry->will_process_pending_queue);
DCHECK(!entry->doomed);
DCHECK(!entry->writer);
DCHECK(entry->readers.empty());
DCHECK(entry->pending_queue.empty());
std::string key = entry->disk_entry->GetKey();
if (key.empty())
return SlowDeactivateEntry(entry);
ActiveEntriesMap::iterator it = active_entries_.find(key);
DCHECK(it != active_entries_.end());
DCHECK(it->second == entry);
active_entries_.erase(it);
delete entry;
}
// We don't know this entry's key so we have to find it without it.
void HttpCache::SlowDeactivateEntry(ActiveEntry* entry) {
for (ActiveEntriesMap::iterator it = active_entries_.begin();
it != active_entries_.end(); ++it) {
if (it->second == entry) {
active_entries_.erase(it);
delete entry;
break;
}
}
}
int HttpCache::AddTransactionToEntry(ActiveEntry* entry, Transaction* trans) {
DCHECK(entry);
// We implement a basic reader/writer lock for the disk cache entry. If
// there is already a writer, then everyone has to wait for the writer to
// finish before they can access the cache entry. There can be multiple
// readers.
//
// NOTE: If the transaction can only write, then the entry should not be in
// use (since any existing entry should have already been doomed).
if (entry->writer || entry->will_process_pending_queue) {
entry->pending_queue.push_back(trans);
return ERR_IO_PENDING;
}
if (trans->mode() & Transaction::WRITE) {
// transaction needs exclusive access to the entry
if (entry->readers.empty()) {
entry->writer = trans;
} else {
entry->pending_queue.push_back(trans);
return ERR_IO_PENDING;
}
} else {
// transaction needs read access to the entry
entry->readers.push_back(trans);
}
// We do this before calling EntryAvailable to force any further calls to
// AddTransactionToEntry to add their transaction to the pending queue, which
// ensures FIFO ordering.
if (!entry->writer && !entry->pending_queue.empty())
ProcessPendingQueue(entry);
return trans->EntryAvailable(entry);
}
void HttpCache::DoneWithEntry(ActiveEntry* entry, Transaction* trans,
bool cancel) {
// If we already posted a task to move on to the next transaction and this was
// the writer, there is nothing to cancel.
if (entry->will_process_pending_queue && entry->readers.empty())
return;
if (entry->writer) {
DCHECK(trans == entry->writer);
// Assume there was a failure.
bool success = false;
if (cancel) {
DCHECK(entry->disk_entry);
// This is a successful operation in the sense that we want to keep the
// entry.
success = trans->AddTruncatedFlag();
}
DoneWritingToEntry(entry, success);
} else {
DoneReadingFromEntry(entry, trans);
}
}
void HttpCache::DoneWritingToEntry(ActiveEntry* entry, bool success) {
DCHECK(entry->readers.empty());
entry->writer = NULL;
if (success) {
ProcessPendingQueue(entry);
} else {
DCHECK(!entry->will_process_pending_queue);
// We failed to create this entry.
TransactionList pending_queue;
pending_queue.swap(entry->pending_queue);
entry->disk_entry->Doom();
DestroyEntry(entry);
// We need to do something about these pending entries, which now need to
// be added to a new entry.
while (!pending_queue.empty()) {
pending_queue.front()->AddToEntry();
pending_queue.pop_front();
}
}
}
void HttpCache::DoneReadingFromEntry(ActiveEntry* entry, Transaction* trans) {
DCHECK(!entry->writer);
TransactionList::iterator it =
std::find(entry->readers.begin(), entry->readers.end(), trans);
DCHECK(it != entry->readers.end());
entry->readers.erase(it);
ProcessPendingQueue(entry);
}
void HttpCache::ConvertWriterToReader(ActiveEntry* entry) {
DCHECK(entry->writer);
DCHECK(entry->writer->mode() == Transaction::READ_WRITE);
DCHECK(entry->readers.empty());
Transaction* trans = entry->writer;
entry->writer = NULL;
entry->readers.push_back(trans);
ProcessPendingQueue(entry);
}
void HttpCache::RemovePendingTransaction(Transaction* trans) {
ActiveEntriesMap::const_iterator i = active_entries_.find(trans->key());
bool found = false;
if (i != active_entries_.end())
found = RemovePendingTransactionFromEntry(i->second, trans);
if (found)
return;
ActiveEntriesSet::iterator it = doomed_entries_.begin();
for (; it != doomed_entries_.end() && !found; ++it)
found = RemovePendingTransactionFromEntry(*it, trans);
}
bool HttpCache::RemovePendingTransactionFromEntry(ActiveEntry* entry,
Transaction* trans) {
TransactionList& pending_queue = entry->pending_queue;
TransactionList::iterator j =
find(pending_queue.begin(), pending_queue.end(), trans);
if (j == pending_queue.end())
return false;
pending_queue.erase(j);
return true;
}
void HttpCache::ProcessPendingQueue(ActiveEntry* entry) {
// Multiple readers may finish with an entry at once, so we want to batch up
// calls to OnProcessPendingQueue. This flag also tells us that we should
// not delete the entry before OnProcessPendingQueue runs.
if (entry->will_process_pending_queue)
return;
entry->will_process_pending_queue = true;
MessageLoop::current()->PostTask(FROM_HERE,
task_factory_.NewRunnableMethod(&HttpCache::OnProcessPendingQueue,
entry));
}
void HttpCache::OnProcessPendingQueue(ActiveEntry* entry) {
entry->will_process_pending_queue = false;
DCHECK(!entry->writer);
// If no one is interested in this entry, then we can de-activate it.
if (entry->pending_queue.empty()) {
if (entry->readers.empty())
DestroyEntry(entry);
return;
}
// Promote next transaction from the pending queue.
Transaction* next = entry->pending_queue.front();
if ((next->mode() & Transaction::WRITE) && !entry->readers.empty())
return; // Have to wait.
entry->pending_queue.erase(entry->pending_queue.begin());
AddTransactionToEntry(entry, next);
}
void HttpCache::CloseIdleConnections() {
net::HttpNetworkLayer* network =
static_cast<net::HttpNetworkLayer*>(network_layer_.get());
HttpNetworkSession* session = network->GetSession();
if (session) {
session->tcp_socket_pool()->CloseIdleSockets();
}
}
//-----------------------------------------------------------------------------
} // namespace net