[go: nahoru, domu]

blob: b7707d1b440454f396e3644628b7a0d48408443c [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 "base/time.h"
#include "net/base/io_buffer.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/base/ssl_cert_request_info.h"
#include "net/disk_cache/disk_cache.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"
#include "net/http/http_transaction.h"
#include "net/http/http_util.h"
#include "net/http/partial_data.h"
using base::Time;
// Uncomment this to enable experimental byte-range support.
// #define ENABLE_RANGE_SUPPORT
namespace net {
// disk cache entry data indices.
enum {
kResponseInfoIndex,
kResponseContentIndex
};
// These values can be bit-wise combined to form the flags field of the
// serialized HttpResponseInfo.
enum {
// The version of the response info used when persisting response info.
RESPONSE_INFO_VERSION = 1,
// We reserve up to 8 bits for the version number.
RESPONSE_INFO_VERSION_MASK = 0xFF,
// This bit is set if the response info has a cert at the end.
RESPONSE_INFO_HAS_CERT = 1 << 8,
// This bit is set if the response info has a security-bits field (security
// strength, in bits, of the SSL connection) at the end.
RESPONSE_INFO_HAS_SECURITY_BITS = 1 << 9,
// This bit is set if the response info has a cert status at the end.
RESPONSE_INFO_HAS_CERT_STATUS = 1 << 10,
// This bit is set if the response info has vary header data.
RESPONSE_INFO_HAS_VARY_DATA = 1 << 11,
// TODO(darin): Add other bits to indicate alternate request methods and
// whether or not we are storing a partial document. For now, we don't
// support storing those.
};
//-----------------------------------------------------------------------------
struct HeaderNameAndValue {
const char* name;
const char* value;
};
// If the request includes one of these request headers, then avoid caching
// to avoid getting confused.
static const HeaderNameAndValue kPassThroughHeaders[] = {
{ "if-unmodified-since", NULL }, // causes unexpected 412s
{ "if-match", NULL }, // causes unexpected 412s
{ NULL, NULL }
};
struct ValidationHeaderInfo {
const char* request_header_name;
const char* related_response_header_name;
};
static const ValidationHeaderInfo kValidationHeaders[] = {
{ "if-modified-since", "last-modified" },
{ "if-none-match", "etag" },
};
// Helper struct to pair a header name with its value, for
// headers used to validate cache entries.
struct ValidationHeader {
enum {kInvalidIndex = -1};
ValidationHeader() : type_index(kInvalidIndex) {}
bool initialized() const {
return type_index != kInvalidIndex;
}
const ValidationHeaderInfo& type_info() {
DCHECK(initialized());
return kValidationHeaders[type_index];
}
// Index into |kValidationHeaders|.
int type_index;
std::string value;
};
// If the request includes one of these request headers, then avoid reusing
// our cached copy if any.
static const HeaderNameAndValue kForceFetchHeaders[] = {
{ "cache-control", "no-cache" },
{ "pragma", "no-cache" },
{ NULL, NULL }
};
// If the request includes one of these request headers, then force our
// cached copy (if any) to be revalidated before reusing it.
static const HeaderNameAndValue kForceValidateHeaders[] = {
{ "cache-control", "max-age=0" },
{ NULL, NULL }
};
static bool HeaderMatches(const HttpUtil::HeadersIterator& h,
const HeaderNameAndValue* search) {
for (; search->name; ++search) {
if (!LowerCaseEqualsASCII(h.name_begin(), h.name_end(), search->name))
continue;
if (!search->value)
return true;
HttpUtil::ValuesIterator v(h.values_begin(), h.values_end(), ',');
while (v.GetNext()) {
if (LowerCaseEqualsASCII(v.value_begin(), v.value_end(), search->value))
return true;
}
}
return false;
}
//-----------------------------------------------------------------------------
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();
}
//-----------------------------------------------------------------------------
class HttpCache::Transaction
: public HttpTransaction, public RevocableStore::Revocable {
public:
explicit Transaction(HttpCache* cache)
: RevocableStore::Revocable(&cache->transactions_),
request_(NULL),
cache_(cache),
entry_(NULL),
network_trans_(NULL),
callback_(NULL),
mode_(NONE),
reading_(false),
read_offset_(0),
effective_load_flags_(0),
final_upload_progress_(0),
ALLOW_THIS_IN_INITIALIZER_LIST(
network_info_callback_(this, &Transaction::OnNetworkInfoAvailable)),
ALLOW_THIS_IN_INITIALIZER_LIST(
network_read_callback_(this, &Transaction::OnNetworkReadCompleted)),
ALLOW_THIS_IN_INITIALIZER_LIST(
cache_read_callback_(new CancelableCompletionCallback<Transaction>(
this, &Transaction::OnCacheReadCompleted))) {
}
// Clean up the transaction.
virtual ~Transaction();
// HttpTransaction methods:
virtual int Start(const HttpRequestInfo*, CompletionCallback*);
virtual int RestartIgnoringLastError(CompletionCallback*);
virtual int RestartWithCertificate(X509Certificate* client_cert,
CompletionCallback* callback);
virtual int RestartWithAuth(const std::wstring& username,
const std::wstring& password,
CompletionCallback* callback);
virtual bool IsReadyToRestartForAuth();
virtual int Read(IOBuffer* buf, int buf_len, CompletionCallback*);
virtual const HttpResponseInfo* GetResponseInfo() const;
virtual LoadState GetLoadState() const;
virtual uint64 GetUploadProgress(void) const;
// The transaction has the following modes, which apply to how it may access
// its cache entry.
//
// o If the mode of the transaction is NONE, then it is in "pass through"
// mode and all methods just forward to the inner network transaction.
//
// o If the mode of the transaction is only READ, then it may only read from
// the cache entry.
//
// o If the mode of the transaction is only WRITE, then it may only write to
// the cache entry.
//
// o If the mode of the transaction is READ_WRITE, then the transaction may
// optionally modify the cache entry (e.g., possibly corresponding to
// cache validation).
//
// o If the mode of the transaction is UPDATE, then the transaction may
// update existing cache entries, but will never create a new entry or
// respond using the entry read from the cache.
enum Mode {
NONE = 0,
READ_META = 1 << 0,
READ_DATA = 1 << 1,
READ = READ_META | READ_DATA,
WRITE = 1 << 2,
READ_WRITE = READ | WRITE,
UPDATE = READ_META | WRITE, // READ_WRITE & ~READ_DATA
};
Mode mode() const { return mode_; }
const std::string& key() const { return cache_key_; }
// Associates this transaction with a cache entry.
int AddToEntry();
// Called by the HttpCache when the given disk cache entry becomes accessible
// to the transaction. Returns network error code.
int EntryAvailable(ActiveEntry* entry);
private:
// This is a helper function used to trigger a completion callback. It may
// only be called if callback_ is non-null.
void DoCallback(int rv);
// This will trigger the completion callback if appropriate.
int HandleResult(int rv);
// Sets request_ and fields derived from it.
void SetRequest(const HttpRequestInfo* request);
// Returns true if the request should be handled exclusively by the network
// layer (skipping the cache entirely).
bool ShouldPassThrough();
// Returns true if we should force an end-to-end fetch.
bool ShouldBypassCache();
// Called to begin reading from the cache. Returns network error code.
int BeginCacheRead();
// Called to begin validating the cache entry. Returns network error code.
int BeginCacheValidation();
// Called to begin validating an entry that stores partial content. Returns
// a network error code.
int BeginPartialCacheValidation();
// Performs the cache validation for the next chunk of data stored by the
// cache. If this chunk is not currently stored, starts the network request
// to fetch it. Returns a network error code.
int ContinuePartialCacheValidation();
// Called to start requests which were given an "if-modified-since" or
// "if-none-match" validation header by the caller (NOT when the request was
// conditionalized internally in response to LOAD_VALIDATE_CACHE).
// Returns a network error code.
int BeginExternallyConditionalizedRequest();
// Called to begin a network transaction. Returns network error code.
int BeginNetworkRequest();
// Called to restart a network transaction after an error. Returns network
// error code.
int RestartNetworkRequest();
// Called to restart a network transaction with a client certificate.
// Returns network error code.
int RestartNetworkRequestWithCertificate(X509Certificate* client_cert);
// Called to restart a network transaction with authentication credentials.
// Returns network error code.
int RestartNetworkRequestWithAuth(const std::wstring& username,
const std::wstring& password);
// Called to determine if we need to validate the cache entry before using it.
bool RequiresValidation();
// Called to make the request conditional (to ask the server if the cached
// copy is valid). Returns true if able to make the request conditional.
bool ConditionalizeRequest();
// Makes sure that a 206 response is expected. Returns a network error code.
bool ValidatePartialResponse(const HttpResponseHeaders* headers);
// Reads data from the network.
int ReadFromNetwork(IOBuffer* data, int data_len);
// Reads data from the cache entry.
int ReadFromEntry(IOBuffer* data, int data_len);
// Called to populate response_ from the cache entry.
int ReadResponseInfoFromEntry();
// Called to write data to the cache entry. If the write fails, then the
// cache entry is destroyed. Future calls to this function will just do
// nothing without side-effect.
void WriteToEntry(int index, int offset, IOBuffer* data, int data_len);
// Called to write response_ to the cache entry.
void WriteResponseInfoToEntry();
// Called to truncate response content in the entry.
void TruncateResponseData();
// Called to append response data to the cache entry.
void AppendResponseDataToEntry(IOBuffer* data, int data_len);
// Called when we are done writing to the cache entry.
void DoneWritingToEntry(bool success);
// Deletes the current partial cache entry (sparse), and optionally removes
// the control object (partial_).
void DoomPartialEntry(bool delete_object);
// Performs the needed work after receiving data from the network.
int DoNetworkReadCompleted(int result);
// Performs the needed work after receiving data from the network, when
// working with range requests.
int DoPartialNetworkReadCompleted(int result);
// Performs the needed work after receiving data from the cache.
int DoCacheReadCompleted(int result);
// Performs the needed work after receiving data from the cache, when
// working with range requests.
int DoPartialCacheReadCompleted(int result);
// Called to signal completion of the network transaction's Start method:
void OnNetworkInfoAvailable(int result);
// Called to signal completion of the network transaction's Read method:
void OnNetworkReadCompleted(int result);
// Called to signal completion of the cache's ReadData method:
void OnCacheReadCompleted(int result);
const HttpRequestInfo* request_;
scoped_ptr<HttpRequestInfo> custom_request_;
// If extra_headers specified a "if-modified-since" or "if-none-match",
// |external_validation_| contains the value of that header.
ValidationHeader external_validation_;
HttpCache* cache_;
HttpCache::ActiveEntry* entry_;
scoped_ptr<HttpTransaction> network_trans_;
CompletionCallback* callback_; // Consumer's callback.
HttpResponseInfo response_;
HttpResponseInfo auth_response_;
std::string cache_key_;
Mode mode_;
bool reading_; // We are already reading.
scoped_refptr<IOBuffer> read_buf_;
int read_buf_len_;
int read_offset_;
int effective_load_flags_;
scoped_ptr<PartialData> partial_; // We are dealing with range requests.
uint64 final_upload_progress_;
CompletionCallbackImpl<Transaction> network_info_callback_;
CompletionCallbackImpl<Transaction> network_read_callback_;
scoped_refptr<CancelableCompletionCallback<Transaction> >
cache_read_callback_;
};
HttpCache::Transaction::~Transaction() {
if (!revoked()) {
if (entry_) {
cache_->DoneWithEntry(entry_, this);
} else {
cache_->RemovePendingTransaction(this);
}
}
// If there is an outstanding callback, mark it as cancelled so running it
// does nothing.
cache_read_callback_->Cancel();
// We could still have a cache read in progress, so we just null the cache_
// pointer to signal that we are dead. See OnCacheReadCompleted.
cache_ = NULL;
}
int HttpCache::Transaction::Start(const HttpRequestInfo* request,
CompletionCallback* callback) {
DCHECK(request);
DCHECK(callback);
// ensure that we only have one asynchronous call at a time.
DCHECK(!callback_);
if (revoked())
return ERR_UNEXPECTED;
SetRequest(request);
int rv;
if (!ShouldPassThrough()) {
cache_key_ = cache_->GenerateCacheKey(request);
// requested cache access mode
if (effective_load_flags_ & LOAD_ONLY_FROM_CACHE) {
mode_ = READ;
} else if (effective_load_flags_ & LOAD_BYPASS_CACHE) {
mode_ = WRITE;
} else {
mode_ = READ_WRITE;
}
// Downgrade to UPDATE if the request has been externally conditionalized.
if (external_validation_.initialized()) {
if (mode_ & WRITE) {
// Strip off the READ_DATA bit (and maybe add back a READ_META bit
// in case READ was off).
mode_ = UPDATE;
} else {
mode_ = NONE;
}
}
}
// if must use cache, then we must fail. this can happen for back/forward
// navigations to a page generated via a form post.
if (!(mode_ & READ) && effective_load_flags_ & LOAD_ONLY_FROM_CACHE)
return ERR_CACHE_MISS;
if (mode_ == NONE)
rv = BeginNetworkRequest();
else
rv = AddToEntry();
// setting this here allows us to check for the existance of a callback_ to
// determine if we are still inside Start.
if (rv == ERR_IO_PENDING)
callback_ = callback;
return rv;
}
int HttpCache::Transaction::RestartIgnoringLastError(
CompletionCallback* callback) {
DCHECK(callback);
// ensure that we only have one asynchronous call at a time.
DCHECK(!callback_);
if (revoked())
return ERR_UNEXPECTED;
int rv = RestartNetworkRequest();
if (rv == ERR_IO_PENDING)
callback_ = callback;
return rv;
}
int HttpCache::Transaction::RestartWithCertificate(
X509Certificate* client_cert,
CompletionCallback* callback) {
DCHECK(callback);
// ensure that we only have one asynchronous call at a time.
DCHECK(!callback_);
if (revoked())
return ERR_UNEXPECTED;
int rv = RestartNetworkRequestWithCertificate(client_cert);
if (rv == ERR_IO_PENDING)
callback_ = callback;
return rv;
}
int HttpCache::Transaction::RestartWithAuth(
const std::wstring& username,
const std::wstring& password,
CompletionCallback* callback) {
DCHECK(auth_response_.headers);
DCHECK(callback);
// Ensure that we only have one asynchronous call at a time.
DCHECK(!callback_);
if (revoked())
return ERR_UNEXPECTED;
// Clear the intermediate response since we are going to start over.
auth_response_ = HttpResponseInfo();
int rv = RestartNetworkRequestWithAuth(username, password);
if (rv == ERR_IO_PENDING)
callback_ = callback;
return rv;
}
bool HttpCache::Transaction::IsReadyToRestartForAuth() {
if (!network_trans_.get())
return false;
return network_trans_->IsReadyToRestartForAuth();
}
int HttpCache::Transaction::Read(IOBuffer* buf, int buf_len,
CompletionCallback* callback) {
DCHECK(buf);
DCHECK(buf_len > 0);
DCHECK(callback);
DCHECK(!callback_);
if (revoked())
return ERR_UNEXPECTED;
// If we have an intermediate auth response at this point, then it means the
// user wishes to read the network response (the error page). If there is a
// previous response in the cache then we should leave it intact.
if (auth_response_.headers && mode_ != NONE) {
DCHECK(mode_ & WRITE);
DoneWritingToEntry(mode_ == READ_WRITE);
mode_ = NONE;
}
int rv;
switch (mode_) {
case READ_WRITE:
DCHECK(partial_.get());
reading_ = true;
if (!network_trans_.get()) {
// We are just reading from the cache, but we may be writing later.
rv = ReadFromEntry(buf, buf_len);
break;
}
case NONE:
case WRITE:
DCHECK(network_trans_.get());
rv = ReadFromNetwork(buf, buf_len);
break;
case READ:
rv = ReadFromEntry(buf, buf_len);
break;
default:
NOTREACHED();
rv = ERR_FAILED;
}
if (rv == ERR_IO_PENDING) {
DCHECK(!callback_);
callback_ = callback;
}
return rv;
}
const HttpResponseInfo* HttpCache::Transaction::GetResponseInfo() const {
// Null headers means we encountered an error or haven't a response yet
if (auth_response_.headers)
return &auth_response_;
return (response_.headers || response_.ssl_info.cert ||
response_.cert_request_info) ? &response_ : NULL;
}
LoadState HttpCache::Transaction::GetLoadState() const {
if (network_trans_.get())
return network_trans_->GetLoadState();
if (entry_ || !request_)
return LOAD_STATE_IDLE;
return LOAD_STATE_WAITING_FOR_CACHE;
}
uint64 HttpCache::Transaction::GetUploadProgress() const {
if (network_trans_.get())
return network_trans_->GetUploadProgress();
return final_upload_progress_;
}
int HttpCache::Transaction::AddToEntry() {
ActiveEntry* entry = NULL;
if (revoked())
return ERR_UNEXPECTED;
if (mode_ == WRITE) {
cache_->DoomEntry(cache_key_);
} else {
entry = cache_->FindActiveEntry(cache_key_);
if (!entry) {
entry = cache_->OpenEntry(cache_key_);
if (!entry) {
if (mode_ == READ_WRITE) {
mode_ = WRITE;
} else if (mode_ == UPDATE) {
// There is no cache entry to update; proceed without caching.
mode_ = NONE;
return BeginNetworkRequest();
} else {
if (cache_->mode() == PLAYBACK)
DLOG(INFO) << "Playback Cache Miss: " << request_->url;
// entry does not exist, and not permitted to create a new entry, so
// we must fail.
return HandleResult(ERR_CACHE_MISS);
}
}
}
}
if (mode_ == WRITE) {
DCHECK(!entry);
entry = cache_->CreateEntry(cache_key_);
if (!entry) {
DLOG(WARNING) << "unable to create cache entry";
mode_ = NONE;
return BeginNetworkRequest();
}
}
return cache_->AddTransactionToEntry(entry, this);
}
int HttpCache::Transaction::EntryAvailable(ActiveEntry* entry) {
// We now have access to the cache entry.
//
// o if we are the writer for the transaction, then we can start the network
// transaction.
//
// o if we are a reader for the transaction, then we can start reading the
// cache entry.
//
// o if we can read or write, then we should check if the cache entry needs
// to be validated and then issue a network request if needed or just read
// from the cache if the cache entry is already valid.
//
// o if we are set to UPDATE, then we are handling an externally
// conditionalized request (if-modified-since / if-none-match). We read
// the cache entry, and check if the request headers define a validation
// request.
//
int rv;
entry_ = entry;
switch (mode_) {
case READ:
rv = BeginCacheRead();
break;
case WRITE:
if (partial_.get())
partial_->RestoreHeaders(&custom_request_->extra_headers);
rv = BeginNetworkRequest();
break;
case READ_WRITE:
rv = BeginPartialCacheValidation();
break;
case UPDATE:
rv = BeginExternallyConditionalizedRequest();
break;
default:
NOTREACHED();
rv = ERR_FAILED;
}
return rv;
}
void HttpCache::Transaction::DoCallback(int rv) {
DCHECK(rv != ERR_IO_PENDING);
DCHECK(callback_);
// since Run may result in Read being called, clear callback_ up front.
CompletionCallback* c = callback_;
callback_ = NULL;
c->Run(rv);
}
int HttpCache::Transaction::HandleResult(int rv) {
DCHECK(rv != ERR_IO_PENDING);
if (callback_)
DoCallback(rv);
return rv;
}
void HttpCache::Transaction::SetRequest(const HttpRequestInfo* request) {
request_ = request;
effective_load_flags_ = request_->load_flags;
switch (cache_->mode()) {
case NORMAL:
break;
case RECORD:
// When in record mode, we want to NEVER load from the cache.
// The reason for this is beacuse we save the Set-Cookie headers
// (intentionally). If we read from the cache, we replay them
// prematurely.
effective_load_flags_ |= LOAD_BYPASS_CACHE;
case PLAYBACK:
// When in playback mode, we want to load exclusively from the cache.
effective_load_flags_ |= LOAD_ONLY_FROM_CACHE;
break;
case DISABLE:
effective_load_flags_ |= LOAD_DISABLE_CACHE;
break;
}
// Some headers imply load flags. The order here is significant.
//
// LOAD_DISABLE_CACHE : no cache read or write
// LOAD_BYPASS_CACHE : no cache read
// LOAD_VALIDATE_CACHE : no cache read unless validation
//
// The former modes trump latter modes, so if we find a matching header we
// can stop iterating kSpecialHeaders.
//
static const struct {
const HeaderNameAndValue* search;
int load_flag;
} kSpecialHeaders[] = {
{ kPassThroughHeaders, LOAD_DISABLE_CACHE },
{ kForceFetchHeaders, LOAD_BYPASS_CACHE },
{ kForceValidateHeaders, LOAD_VALIDATE_CACHE },
};
std::string new_extra_headers;
bool range_found = false;
// We will scan through the headers to see if any "if-modified-since" or
// "if-none-match" request headers were specified as part of extra_headers.
int num_validation_headers = 0;
ValidationHeader validation_header;
// scan request headers to see if we have any that would impact our load flags
HttpUtil::HeadersIterator it(request_->extra_headers.begin(),
request_->extra_headers.end(),
"\r\n");
while (it.GetNext()) {
if (!LowerCaseEqualsASCII(it.name(), "range")) {
new_extra_headers.append(it.name_begin(), it.values_end());
new_extra_headers.append("\r\n");
} else {
#ifdef ENABLE_RANGE_SUPPORT
range_found = true;
#else
effective_load_flags_ |= LOAD_DISABLE_CACHE;
continue;
#endif
}
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kSpecialHeaders); ++i) {
if (HeaderMatches(it, kSpecialHeaders[i].search)) {
effective_load_flags_ |= kSpecialHeaders[i].load_flag;
break;
}
}
// Check for conditionalization headers which may correspond with a
// cache validation request.
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kValidationHeaders); ++i) {
const ValidationHeaderInfo& info = kValidationHeaders[i];
if (LowerCaseEqualsASCII(it.name_begin(), it.name_end(),
info.request_header_name)) {
num_validation_headers++;
validation_header.type_index = i;
validation_header.value = it.values();
break;
}
}
}
if (range_found && !(effective_load_flags_ & LOAD_DISABLE_CACHE)) {
partial_.reset(new PartialData);
if (partial_->Init(request_->extra_headers, new_extra_headers)) {
// We will be modifying the actual range requested to the server, so
// let's remove the header here.
custom_request_.reset(new HttpRequestInfo(*request_));
request_ = custom_request_.get();
custom_request_->extra_headers = new_extra_headers;
} else {
// The range is invalid or we cannot handle it properly.
effective_load_flags_ |= LOAD_DISABLE_CACHE;
partial_.reset(NULL);
}
}
// If there is more than one validation header, we can't treat this request as
// a cache validation, since we don't know for sure which header the server
// will give us a response for (and they could be contradictory).
if (num_validation_headers > 1) {
LOG(WARNING) << "Multiple validation headers found.";
effective_load_flags_ |= LOAD_DISABLE_CACHE;
}
if (num_validation_headers == 1) {
DCHECK(validation_header.initialized());
external_validation_ = validation_header;
}
}
bool HttpCache::Transaction::ShouldPassThrough() {
// We may have a null disk_cache if there is an error we cannot recover from,
// like not enough disk space, or sharing violations.
if (!cache_->disk_cache())
return true;
// When using the record/playback modes, we always use the cache
// and we never pass through.
if (cache_->mode() == RECORD || cache_->mode() == PLAYBACK)
return false;
if (effective_load_flags_ & LOAD_DISABLE_CACHE)
return true;
if (request_->method == "GET")
return false;
if (request_->method == "POST" &&
request_->upload_data && request_->upload_data->identifier())
return false;
// TODO(darin): add support for caching HEAD responses
return true;
}
int HttpCache::Transaction::BeginCacheRead() {
DCHECK(mode_ == READ);
// Read response headers.
// TODO(rvargas): Handle partial content (206).
return HandleResult(ReadResponseInfoFromEntry());
}
int HttpCache::Transaction::BeginCacheValidation() {
DCHECK(mode_ == READ_WRITE);
if ((effective_load_flags_ & LOAD_PREFERRING_CACHE ||
!RequiresValidation()) && !partial_.get()) {
cache_->ConvertWriterToReader(entry_);
mode_ = READ;
} else {
// Make the network request conditional, to see if we may reuse our cached
// response. If we cannot do so, then we just resort to a normal fetch.
// Our mode remains READ_WRITE for a conditional request. We'll switch to
// either READ or WRITE mode once we hear back from the server.
if (!ConditionalizeRequest())
mode_ = WRITE;
return BeginNetworkRequest();
}
return HandleResult(OK);
}
int HttpCache::Transaction::BeginPartialCacheValidation() {
DCHECK(mode_ == READ_WRITE);
int rv = ReadResponseInfoFromEntry();
if (rv != OK) {
DCHECK(rv != ERR_IO_PENDING);
return HandleResult(rv);
}
if (response_.headers->response_code() != 206)
return BeginCacheValidation();
#if !defined(ENABLE_RANGE_SUPPORT)
return BeginCacheValidation();
#endif
bool byte_range_requested = partial_.get() != NULL;
if (!byte_range_requested) {
// The request is not for a range, but we have stored just ranges.
partial_.reset(new PartialData());
if (!custom_request_.get()) {
custom_request_.reset(new HttpRequestInfo(*request_));
request_ = custom_request_.get();
DCHECK(custom_request_->extra_headers.empty());
}
}
if (!partial_->UpdateFromStoredHeaders(response_.headers,
entry_->disk_entry)) {
// The stored data cannot be used. Get rid of it and restart this request.
DoomPartialEntry(!byte_range_requested);
mode_ = WRITE;
return AddToEntry();
}
return ContinuePartialCacheValidation();
}
int HttpCache::Transaction::ContinuePartialCacheValidation() {
DCHECK(mode_ == READ_WRITE);
int rv = partial_->PrepareCacheValidation(entry_->disk_entry,
&custom_request_->extra_headers);
if (!rv) {
// Don't invoke the callback before telling the cache we're done.
return rv;
}
if (rv < 0) {
DCHECK(rv != ERR_IO_PENDING);
return HandleResult(rv);
}
if (reading_ && partial_->IsCurrentRangeCached()) {
rv = ReadFromEntry(read_buf_, read_buf_len_);
// We are supposed to hanlde errors here.
if (rv < 0 && rv != ERR_IO_PENDING)
HandleResult(rv);
return rv;
}
return BeginCacheValidation();
}
int HttpCache::Transaction::BeginExternallyConditionalizedRequest() {
DCHECK_EQ(UPDATE, mode_);
DCHECK(external_validation_.initialized());
// Read the cached response.
int rv = ReadResponseInfoFromEntry();
if (rv != OK) {
DCHECK(rv != ERR_IO_PENDING);
return HandleResult(rv);
}
// Retrieve either the cached response's "etag" or "last-modified" header,
// depending on which is applicable for the caller's request header.
std::string validator;
response_.headers->EnumerateHeader(
NULL,
external_validation_.type_info().related_response_header_name,
&validator);
if (response_.headers->response_code() != 200 ||
validator.empty() ||
validator != external_validation_.value) {
// The externally conditionalized request is not a validation request
// for our existing cache entry. Proceed with caching disabled.
DoneWritingToEntry(true);
}
return BeginNetworkRequest();
}
int HttpCache::Transaction::BeginNetworkRequest() {
DCHECK(mode_ & WRITE || mode_ == NONE);
DCHECK(!network_trans_.get());
network_trans_.reset(cache_->network_layer_->CreateTransaction());
if (!network_trans_.get())
return net::ERR_CACHE_CANNOT_CREATE_NETWORK_TRANSACTION;
int rv = network_trans_->Start(request_, &network_info_callback_);
if (rv != ERR_IO_PENDING)
OnNetworkInfoAvailable(rv);
return rv;
}
int HttpCache::Transaction::RestartNetworkRequest() {
DCHECK(mode_ & WRITE || mode_ == NONE);
DCHECK(network_trans_.get());
int rv = network_trans_->RestartIgnoringLastError(&network_info_callback_);
if (rv != ERR_IO_PENDING)
OnNetworkInfoAvailable(rv);
return rv;
}
int HttpCache::Transaction::RestartNetworkRequestWithCertificate(
X509Certificate* client_cert) {
DCHECK(mode_ & WRITE || mode_ == NONE);
DCHECK(network_trans_.get());
int rv = network_trans_->RestartWithCertificate(client_cert,
&network_info_callback_);
if (rv != ERR_IO_PENDING)
OnNetworkInfoAvailable(rv);
return rv;
}
int HttpCache::Transaction::RestartNetworkRequestWithAuth(
const std::wstring& username,
const std::wstring& password) {
DCHECK(mode_ & WRITE || mode_ == NONE);
DCHECK(network_trans_.get());
int rv = network_trans_->RestartWithAuth(username, password,
&network_info_callback_);
if (rv != ERR_IO_PENDING)
OnNetworkInfoAvailable(rv);
return rv;
}
bool HttpCache::Transaction::RequiresValidation() {
// TODO(darin): need to do more work here:
// - make sure we have a matching request method
// - watch out for cached responses that depend on authentication
// In playback mode, nothing requires validation.
if (cache_->mode() == net::HttpCache::PLAYBACK)
return false;
if (effective_load_flags_ & LOAD_VALIDATE_CACHE)
return true;
if (response_.headers->RequiresValidation(
response_.request_time, response_.response_time, Time::Now()))
return true;
// Since Vary header computation is fairly expensive, we save it for last.
if (response_.vary_data.is_valid() &&
!response_.vary_data.MatchesRequest(*request_, *response_.headers))
return true;
return false;
}
bool HttpCache::Transaction::ConditionalizeRequest() {
DCHECK(response_.headers);
#if !defined(ENABLE_RANGE_SUPPORT)
// This only makes sense for cached 200 responses.
if (response_.headers->response_code() != 200)
return false;
#endif
// This only makes sense for cached 200 or 206 responses.
if (response_.headers->response_code() != 200 &&
response_.headers->response_code() != 206)
return false;
// Just use the first available ETag and/or Last-Modified header value.
// TODO(darin): Or should we use the last?
std::string etag_value;
response_.headers->EnumerateHeader(NULL, "etag", &etag_value);
std::string last_modified_value;
response_.headers->EnumerateHeader(NULL, "last-modified",
&last_modified_value);
if (etag_value.empty() && last_modified_value.empty())
return false;
if (!partial_.get()) {
// Need to customize the request, so this forces us to allocate :(
custom_request_.reset(new HttpRequestInfo(*request_));
request_ = custom_request_.get();
}
DCHECK(custom_request_.get());
if (!etag_value.empty()) {
if (partial_.get() && !partial_->IsCurrentRangeCached()) {
// We don't want to switch to WRITE mode if we don't have this block of a
// byte-range request because we may have other parts cached.
custom_request_->extra_headers.append("If-Range: ");
} else {
custom_request_->extra_headers.append("If-None-Match: ");
}
custom_request_->extra_headers.append(etag_value);
custom_request_->extra_headers.append("\r\n");
if (partial_.get() && !partial_->IsCurrentRangeCached())
return true;
}
if (!last_modified_value.empty()) {
if (partial_.get() && !partial_->IsCurrentRangeCached()) {
custom_request_->extra_headers.append("If-Range: ");
} else {
custom_request_->extra_headers.append("If-Modified-Since: ");
}
custom_request_->extra_headers.append(last_modified_value);
custom_request_->extra_headers.append("\r\n");
}
return true;
}
bool HttpCache::Transaction::ValidatePartialResponse(
const HttpResponseHeaders* headers) {
#ifdef ENABLE_RANGE_SUPPORT
bool partial_content = headers->response_code() == 206;
#else
bool partial_content = false;
#endif
bool failure = false;
if (!partial_content) {
if (!partial_.get())
return false;
// TODO(rvargas): Do we need to consider other results here?.
if (headers->response_code() == 200 || headers->response_code() == 416)
failure = true;
if (!reading_ && failure) {
// We are expecting 206 or 304 because we asked for a range. Given that
// the server is refusing the request we'll remove the sparse entry.
DoomPartialEntry(true);
mode_ = NONE;
return false;
}
}
if (!failure && partial_.get() && partial_->ResponseHeadersOK(headers))
return true;
// We have a problem. We may or may not be reading already (in which case we
// returned the headers), but we'll just pretend that this request is not
// using the cache and see what happens. Most likely this is the first
// response from the server (it's not changing its mind midway, right?).
if (mode_ & WRITE) {
DoneWritingToEntry(mode_ != WRITE);
} else if (mode_ & READ && entry_) {
cache_->DoneReadingFromEntry(entry_, this);
}
entry_ = NULL;
mode_ = NONE;
return false;
}
int HttpCache::Transaction::ReadFromNetwork(IOBuffer* data, int data_len) {
int rv = network_trans_->Read(data, data_len, &network_read_callback_);
read_buf_ = data;
read_buf_len_ = data_len;
if (rv >= 0)
rv = DoNetworkReadCompleted(rv);
return rv;
}
int HttpCache::Transaction::ReadFromEntry(IOBuffer* data, int data_len) {
DCHECK(entry_);
int rv;
cache_read_callback_->AddRef(); // Balanced in DoCacheReadCompleted.
if (partial_.get()) {
rv = partial_->CacheRead(entry_->disk_entry, data, data_len,
cache_read_callback_);
} else {
rv = entry_->disk_entry->ReadData(kResponseContentIndex, read_offset_,
data, data_len, cache_read_callback_);
}
read_buf_ = data;
read_buf_len_ = data_len;
if (rv >= 0) {
rv = DoCacheReadCompleted(rv);
} else if (rv != ERR_IO_PENDING) {
cache_read_callback_->Release();
}
return rv;
}
int HttpCache::Transaction::ReadResponseInfoFromEntry() {
DCHECK(entry_);
if (!HttpCache::ReadResponseInfo(entry_->disk_entry, &response_))
return ERR_CACHE_READ_FAILURE;
return OK;
}
void HttpCache::Transaction::WriteToEntry(int index, int offset,
IOBuffer* data, int data_len) {
if (!entry_)
return;
int rv = 0;
if (!partial_.get() || !data_len) {
rv = entry_->disk_entry->WriteData(index, offset, data, data_len, NULL,
true);
} else {
rv = partial_->CacheWrite(entry_->disk_entry, data, data_len, NULL);
}
if (rv != data_len) {
DLOG(ERROR) << "failed to write response data to cache";
DoneWritingToEntry(false);
}
}
void HttpCache::Transaction::WriteResponseInfoToEntry() {
if (!entry_)
return;
// Do not cache no-store content (unless we are record mode). Do not cache
// content with cert errors either. This is to prevent not reporting net
// errors when loading a resource from the cache. When we load a page over
// HTTPS with a cert error we show an SSL blocking page. If the user clicks
// proceed we reload the resource ignoring the errors. The loaded resource
// is then cached. If that resource is subsequently loaded from the cache,
// no net error is reported (even though the cert status contains the actual
// errors) and no SSL blocking page is shown. An alternative would be to
// reverse-map the cert status to a net error and replay the net error.
if ((cache_->mode() != RECORD &&
response_.headers->HasHeaderValue("cache-control", "no-store")) ||
net::IsCertStatusError(response_.ssl_info.cert_status)) {
DoneWritingToEntry(false);
return;
}
// When writing headers, we normally only write the non-transient
// headers; when in record mode, record everything.
bool skip_transient_headers = (cache_->mode() != RECORD);
if (!HttpCache::WriteResponseInfo(entry_->disk_entry, &response_,
skip_transient_headers)) {
DLOG(ERROR) << "failed to write response info to cache";
DoneWritingToEntry(false);
}
}
void HttpCache::Transaction::AppendResponseDataToEntry(IOBuffer* data,
int data_len) {
if (!entry_ || !data_len)
return;
int current_size = entry_->disk_entry->GetDataSize(kResponseContentIndex);
WriteToEntry(kResponseContentIndex, current_size, data, data_len);
}
void HttpCache::Transaction::TruncateResponseData() {
if (!entry_)
return;
// Truncate the stream.
WriteToEntry(kResponseContentIndex, 0, NULL, 0);
}
void HttpCache::Transaction::DoneWritingToEntry(bool success) {
if (!entry_)
return;
if (cache_->mode() == RECORD)
DLOG(INFO) << "Recorded: " << request_->method << request_->url
<< " status: " << response_.headers->response_code();
cache_->DoneWritingToEntry(entry_, success);
entry_ = NULL;
mode_ = NONE; // switch to 'pass through' mode
}
void HttpCache::Transaction::DoomPartialEntry(bool delete_object) {
cache_->DoneWithEntry(entry_, this);
cache_->DoomEntry(cache_key_);
entry_ = NULL;
if (delete_object)
partial_.reset(NULL);
}
void HttpCache::Transaction::OnNetworkInfoAvailable(int result) {
DCHECK(result != ERR_IO_PENDING);
if (revoked()) {
HandleResult(ERR_UNEXPECTED);
return;
}
if (result == OK) {
const HttpResponseInfo* new_response = network_trans_->GetResponseInfo();
if (new_response->headers->response_code() == 401 ||
new_response->headers->response_code() == 407) {
auth_response_ = *new_response;
} else {
bool partial_content = ValidatePartialResponse(new_response->headers);
// Are we expecting a response to a conditional query?
if (mode_ == READ_WRITE || mode_ == UPDATE) {
if (new_response->headers->response_code() == 304 || partial_content) {
// Update cached response based on headers in new_response.
// TODO(wtc): should we update cached certificate
// (response_.ssl_info), too?
response_.headers->Update(*new_response->headers);
if (response_.headers->HasHeaderValue("cache-control", "no-store")) {
cache_->DoomEntry(cache_key_);
} else {
WriteResponseInfoToEntry();
}
if (mode_ == UPDATE) {
DCHECK(!partial_content);
// We got a "not modified" response and already updated the
// corresponding cache entry above.
//
// By closing the cached entry now, we make sure that the
// 304 rather than the cached 200 response, is what will be
// returned to the user.
DoneWritingToEntry(true);
} else if (entry_ && !partial_content) {
DCHECK_EQ(READ_WRITE, mode_);
if (!partial_.get() || partial_->IsLastRange())
cache_->ConvertWriterToReader(entry_);
// We no longer need the network transaction, so destroy it.
final_upload_progress_ = network_trans_->GetUploadProgress();
network_trans_.reset();
if (!partial_.get() || partial_->IsLastRange()) {
DCHECK_NE(UPDATE, mode_);
mode_ = READ;
}
}
} else {
mode_ = WRITE;
}
}
if (!(mode_ & READ)) {
// We change the value of Content-Length for partial content.
if (partial_content && partial_.get())
partial_->FixContentLength(new_response->headers);
response_ = *new_response;
WriteResponseInfoToEntry();
// Truncate response data.
TruncateResponseData();
// If this response is a redirect, then we can stop writing now. (We
// don't need to cache the response body of a redirect.)
if (response_.headers->IsRedirect(NULL))
DoneWritingToEntry(true);
}
if (reading_) {
DCHECK(partial_.get());
if (network_trans_.get()) {
result = ReadFromNetwork(read_buf_, read_buf_len_);
} else {
result = ReadFromEntry(read_buf_, read_buf_len_);
}
if (result >= 0 || result == net::ERR_IO_PENDING)
return;
} else if (partial_.get()) {
// We are about to return the headers for a byte-range request to the
// user, so let's fix them.
partial_->FixResponseHeaders(response_.headers);
}
}
} else if (IsCertificateError(result)) {
const HttpResponseInfo* response = network_trans_->GetResponseInfo();
// If we get a certificate error, then there is a certificate in ssl_info,
// so GetResponseInfo() should never returns NULL here.
DCHECK(response);
response_.ssl_info = response->ssl_info;
} else if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
const HttpResponseInfo* response = network_trans_->GetResponseInfo();
DCHECK(response);
response_.cert_request_info = response->cert_request_info;
}
HandleResult(result);
}
void HttpCache::Transaction::OnNetworkReadCompleted(int result) {
DoNetworkReadCompleted(result);
}
int HttpCache::Transaction::DoNetworkReadCompleted(int result) {
DCHECK(mode_ & WRITE || mode_ == NONE);
if (revoked())
return HandleResult(ERR_UNEXPECTED);
AppendResponseDataToEntry(read_buf_, result);
if (partial_.get())
return DoPartialNetworkReadCompleted(result);
if (result == 0) // End of file.
DoneWritingToEntry(true);
return HandleResult(result);
}
int HttpCache::Transaction::DoPartialNetworkReadCompleted(int result) {
partial_->OnNetworkReadCompleted(result);
if (result == 0) { // End of file.
if (mode_ == READ_WRITE) {
// We need to move on to the next range.
network_trans_.reset();
result = ContinuePartialCacheValidation();
if (result != OK)
// Any error was already handled.
return result;
}
DoneWritingToEntry(true);
}
return HandleResult(result);
}
void HttpCache::Transaction::OnCacheReadCompleted(int result) {
DoCacheReadCompleted(result);
}
int HttpCache::Transaction::DoCacheReadCompleted(int result) {
DCHECK(cache_);
cache_read_callback_->Release(); // Balance the AddRef() from Start().
if (revoked())
return HandleResult(ERR_UNEXPECTED);
if (partial_.get())
return DoPartialCacheReadCompleted(result);
if (result > 0) {
read_offset_ += result;
} else if (result == 0) { // End of file.
cache_->DoneReadingFromEntry(entry_, this);
entry_ = NULL;
}
return HandleResult(result);
}
int HttpCache::Transaction::DoPartialCacheReadCompleted(int result) {
partial_->OnCacheReadCompleted(result);
if (result == 0) { // End of file.
if (partial_.get() && mode_ == READ_WRITE) {
// We need to move on to the next range.
result = ContinuePartialCacheValidation();
if (result != OK)
// Any error was already handled.
return result;
cache_->ConvertWriterToReader(entry_);
}
cache_->DoneReadingFromEntry(entry_, this);
entry_ = NULL;
}
return HandleResult(result);
}
//-----------------------------------------------------------------------------
HttpCache::HttpCache(HostResolver* host_resolver,
ProxyService* proxy_service,
const std::wstring& cache_dir,
int cache_size)
: disk_cache_dir_(cache_dir),
mode_(NORMAL),
type_(DISK_CACHE),
network_layer_(HttpNetworkLayer::CreateFactory(
host_resolver, proxy_service)),
ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)),
in_memory_cache_(false),
deleted_(false),
cache_size_(cache_size) {
}
HttpCache::HttpCache(HttpNetworkSession* session,
const std::wstring& 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)),
in_memory_cache_(false),
deleted_(false),
cache_size_(cache_size) {
}
HttpCache::HttpCache(HostResolver* host_resolver,
ProxyService* proxy_service,
int cache_size)
: mode_(NORMAL),
type_(MEMORY_CACHE),
network_layer_(HttpNetworkLayer::CreateFactory(
host_resolver, proxy_service)),
ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)),
in_memory_cache_(true),
deleted_(false),
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)),
in_memory_cache_(false),
deleted_(false),
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;
// TODO(rvargas): remove this. I'm just tracking a few crashes.
deleted_ = true;
}
HttpTransaction* HttpCache::CreateTransaction() {
// Do lazy initialization of disk cache if needed.
if (!disk_cache_.get()) {
DCHECK(cache_size_ >= 0);
if (in_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_.clear(); // Reclaim memory.
}
}
return new HttpCache::Transaction(this);
}
HttpCache* HttpCache::GetCache() {
return this;
}
void HttpCache::Suspend(bool suspend) {
network_layer_->Suspend(suspend);
}
// static
bool HttpCache::ReadResponseInfo(disk_cache::Entry* disk_entry,
HttpResponseInfo* response_info) {
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;
}
Pickle pickle(buffer->data(), size);
void* iter = NULL;
// read flags and verify version
int flags;
if (!pickle.ReadInt(&iter, &flags))
return false;
int version = flags & RESPONSE_INFO_VERSION_MASK;
if (version != RESPONSE_INFO_VERSION) {
DLOG(ERROR) << "unexpected response info version: " << version;
return false;
}
// read request-time
int64 time_val;
if (!pickle.ReadInt64(&iter, &time_val))
return false;
response_info->request_time = Time::FromInternalValue(time_val);
response_info->was_cached = true; // Set status to show cache resurrection.
// read response-time
if (!pickle.ReadInt64(&iter, &time_val))
return false;
response_info->response_time = Time::FromInternalValue(time_val);
// read response-headers
response_info->headers = new HttpResponseHeaders(pickle, &iter);
DCHECK(response_info->headers->response_code() != -1);
// read ssl-info
if (flags & RESPONSE_INFO_HAS_CERT) {
response_info->ssl_info.cert =
X509Certificate::CreateFromPickle(pickle, &iter);
}
if (flags & RESPONSE_INFO_HAS_CERT_STATUS) {
int cert_status;
if (!pickle.ReadInt(&iter, &cert_status))
return false;
response_info->ssl_info.cert_status = cert_status;
}
if (flags & RESPONSE_INFO_HAS_SECURITY_BITS) {
int security_bits;
if (!pickle.ReadInt(&iter, &security_bits))
return false;
response_info->ssl_info.security_bits = security_bits;
}
// read vary-data
if (flags & RESPONSE_INFO_HAS_VARY_DATA) {
if (!response_info->vary_data.InitFromPickle(pickle, &iter))
return false;
}
return true;
}
// static
bool HttpCache::WriteResponseInfo(disk_cache::Entry* disk_entry,
const HttpResponseInfo* response_info,
bool skip_transient_headers) {
int flags = RESPONSE_INFO_VERSION;
if (response_info->ssl_info.cert) {
flags |= RESPONSE_INFO_HAS_CERT;
flags |= RESPONSE_INFO_HAS_CERT_STATUS;
}
if (response_info->ssl_info.security_bits != -1)
flags |= RESPONSE_INFO_HAS_SECURITY_BITS;
if (response_info->vary_data.is_valid())
flags |= RESPONSE_INFO_HAS_VARY_DATA;
Pickle pickle;
pickle.WriteInt(flags);
pickle.WriteInt64(response_info->request_time.ToInternalValue());
pickle.WriteInt64(response_info->response_time.ToInternalValue());
net::HttpResponseHeaders::PersistOptions persist_options =
net::HttpResponseHeaders::PERSIST_RAW;
if (skip_transient_headers) {
persist_options =
net::HttpResponseHeaders::PERSIST_SANS_COOKIES |
net::HttpResponseHeaders::PERSIST_SANS_CHALLENGES |
net::HttpResponseHeaders::PERSIST_SANS_HOP_BY_HOP |
net::HttpResponseHeaders::PERSIST_SANS_NON_CACHEABLE |
net::HttpResponseHeaders::PERSIST_SANS_RANGES;
}
response_info->headers->Persist(&pickle, persist_options);
if (response_info->ssl_info.cert) {
response_info->ssl_info.cert->Persist(&pickle);
pickle.WriteInt(response_info->ssl_info.cert_status);
}
if (response_info->ssl_info.security_bits != -1)
pickle.WriteInt(response_info->ssl_info.security_bits);
if (response_info->vary_data.is_valid())
response_info->vary_data.Persist(&pickle);
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) {
// TODO(rvargas): remove this code.
ActiveEntriesMap::iterator it = active_entries_.find(key);
CHECK(it == active_entries_.end());
ActiveEntry* entry = new ActiveEntry(disk_entry);
active_entries_[key] = entry;
return entry;
}
#if defined(OS_WIN)
#pragma optimize("", off)
#pragma warning(disable:4748)
#endif
// Avoid optimizing local_entry out of the code.
void HttpCache::DeactivateEntry(ActiveEntry* entry) {
std::string key = entry->disk_entry->GetKey();
if (key.empty())
return SlowDeactivateEntry(entry);
// TODO(rvargas): remove this code and go back to DCHECKS once we find out
// why are we crashing. I'm just trying to gather more info for bug 3931.
ActiveEntry local_entry = *entry;
size_t readers_size = local_entry.readers.size();
size_t pending_size = local_entry.pending_queue.size();
ActiveEntriesMap::iterator it = active_entries_.find(key);
if (it == active_entries_.end() || it->second != entry ||
local_entry.will_process_pending_queue || local_entry.doomed ||
local_entry.writer || readers_size || pending_size || deleted_) {
bool local_mem_flag = in_memory_cache_;
ActiveEntriesSet::iterator it2 = doomed_entries_.find(entry);
char local_key[64];
int key_length = key.size();
base::strlcpy(local_key, key.c_str(), sizeof(local_key));
CHECK(it2 == doomed_entries_.end());
CHECK(!deleted_);
CHECK(local_mem_flag);
CHECK(key_length);
CHECK(false);
}
active_entries_.erase(it);
delete entry;
// Avoid closing the disk_entry again on the destructor.
local_entry.disk_entry = NULL;
}
#if defined(OS_WIN)
#pragma warning(default:4748)
#pragma optimize("", on)
#endif
// 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);
}
#if defined(OS_WIN)
#pragma optimize("", off)
#pragma warning(disable:4748)
#endif
// Avoid optimizing local_transaction out of the code.
void HttpCache::DoneWithEntry(ActiveEntry* entry, Transaction* trans) {
// 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) {
// TODO(rvargas): convert this to a DCHECK.
CHECK(trans == entry->writer);
// Get a local copy of the transaction, in preparation for a crash inside
// DeactivateEntry.
char local_transaction[sizeof(*trans)];
memcpy(local_transaction, trans, sizeof(*trans));
char local_key[64];
base::strlcpy(local_key, trans->key().c_str(), sizeof(local_key));
// Assume that this is not a successful write.
DoneWritingToEntry(entry, false);
} else {
DoneReadingFromEntry(entry, trans);
}
}
#if defined(OS_WIN)
#pragma warning(default:4748)
#pragma optimize("", on)
#endif
void HttpCache::DoneWritingToEntry(ActiveEntry* entry, bool success) {
DCHECK(entry->readers.empty());
entry->writer = NULL;
if (success) {
ProcessPendingQueue(entry);
} else {
// TODO(rvargas): convert this to a DCHECK.
CHECK(!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());
if (i == active_entries_.end())
return;
TransactionList& pending_queue = i->second->pending_queue;
TransactionList::iterator j =
find(pending_queue.begin(), pending_queue.end(), trans);
if (j == pending_queue.end())
return;
pending_queue.erase(j);
}
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;
// TODO(rvargas): Convert this to a DCHECK.
CHECK(!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->connection_pool()->CloseIdleSockets();
}
}
//-----------------------------------------------------------------------------
} // namespace net