summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorrvargas@google.com <rvargas@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-12 17:35:50 +0000
committerrvargas@google.com <rvargas@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-12 17:35:50 +0000
commit8bf26f49ab4b5fd24fe788e4d64c8294280a118d (patch)
tree801038a21ca17d04400cbafef4b9b50314d400a9 /net
parent906a74512ed2bddab37ccab2730a17244b799d5b (diff)
downloadchromium_src-8bf26f49ab4b5fd24fe788e4d64c8294280a118d.zip
chromium_src-8bf26f49ab4b5fd24fe788e4d64c8294280a118d.tar.gz
chromium_src-8bf26f49ab4b5fd24fe788e4d64c8294280a118d.tar.bz2
Http Cache: First pass of byte-range requests support.
This is the first pass to implement support for range requests and the asociated sparse cache entry. It is disabled by default, and requires ENABLE_RANGE_SUPPORT to be defined in order to activate the code: all the code is compiled in, but Start() bypasses the cache for range requests, and OnNetworkInfoAvailable ignores 206. Big parts are still not implemented. Most notably we are not modifying the response headers that we send to the user, so the content-range and content-length info are not correct. BUG=12258 TEST=Unit tests. Review URL: http://codereview.chromium.org/118345 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@18289 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r--net/http/http_cache.cc338
-rw-r--r--net/http/http_cache_unittest.cc130
-rw-r--r--net/http/http_response_headers.cc7
-rw-r--r--net/http/http_response_headers.h4
-rw-r--r--net/http/partial_data.cc131
-rw-r--r--net/http/partial_data.h99
-rw-r--r--net/net.gyp2
7 files changed, 647 insertions, 64 deletions
diff --git a/net/http/http_cache.cc b/net/http/http_cache.cc
index 7baa3fc..f87b306 100644
--- a/net/http/http_cache.cc
+++ b/net/http/http_cache.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -28,9 +28,13 @@
#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.
@@ -76,7 +80,6 @@ struct HeaderNameAndValue {
// If the request includes one of these request headers, then avoid caching
// to avoid getting confused.
static const HeaderNameAndValue kPassThroughHeaders[] = {
- { "range", NULL }, // causes unexpected 206s
{ "if-modified-since", NULL }, // causes unexpected 304s
{ "if-none-match", NULL }, // causes unexpected 304s
{ "if-unmodified-since", NULL }, // causes unexpected 412s
@@ -119,8 +122,6 @@ static bool HeaderMatches(const HttpUtil::HeadersIterator& h,
//-----------------------------------------------------------------------------
-//-----------------------------------------------------------------------------
-
HttpCache::ActiveEntry::ActiveEntry(disk_cache::Entry* e)
: disk_entry(e),
writer(NULL),
@@ -146,6 +147,7 @@ class HttpCache::Transaction
network_trans_(NULL),
callback_(NULL),
mode_(NONE),
+ reading_(false),
read_offset_(0),
effective_load_flags_(0),
final_upload_progress_(0),
@@ -231,6 +233,15 @@ class HttpCache::Transaction
// 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 begin a network transaction. Returns network error code.
int BeginNetworkRequest();
@@ -250,6 +261,12 @@ class HttpCache::Transaction
// copy is valid). Returns true if able to make the request conditional.
bool ConditionalizeRequest();
+ // 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();
@@ -270,6 +287,20 @@ class HttpCache::Transaction
// Called when we are done writing to the cache entry.
void DoneWritingToEntry(bool success);
+ // 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);
@@ -284,14 +315,17 @@ class HttpCache::Transaction
HttpCache* cache_;
HttpCache::ActiveEntry* entry_;
scoped_ptr<HttpTransaction> network_trans_;
- CompletionCallback* callback_; // consumer's callback
+ 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_;
@@ -433,33 +467,31 @@ int HttpCache::Transaction::Read(IOBuffer* buf, int buf_len,
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 = network_trans_->Read(buf, buf_len, &network_read_callback_);
- read_buf_ = buf;
- if (rv >= 0)
- OnNetworkReadCompleted(rv);
+ rv = ReadFromNetwork(buf, buf_len);
break;
case READ:
- DCHECK(entry_);
- cache_read_callback_->AddRef(); // Balanced in OnCacheReadCompleted.
- rv = entry_->disk_entry->ReadData(kResponseContentIndex, read_offset_,
- buf, buf_len, cache_read_callback_);
- read_buf_ = buf;
- if (rv >= 0) {
- OnCacheReadCompleted(rv);
- } else if (rv != ERR_IO_PENDING) {
- cache_read_callback_->Release();
- }
+ rv = ReadFromEntry(buf, buf_len);
break;
default:
NOTREACHED();
rv = ERR_FAILED;
}
- if (rv == ERR_IO_PENDING)
+ if (rv == ERR_IO_PENDING) {
+ DCHECK(!callback_);
callback_ = callback;
+ }
return rv;
}
@@ -544,10 +576,12 @@ int HttpCache::Transaction::EntryAvailable(ActiveEntry* entry) {
rv = BeginCacheRead();
break;
case WRITE:
+ if (partial_.get())
+ partial_->RestoreHeaders(&custom_request_->extra_headers);
rv = BeginNetworkRequest();
break;
case READ_WRITE:
- rv = BeginCacheValidation();
+ rv = BeginPartialCacheValidation();
break;
default:
NOTREACHED();
@@ -577,7 +611,7 @@ void HttpCache::Transaction::SetRequest(const HttpRequestInfo* request) {
request_ = request;
effective_load_flags_ = request_->load_flags;
- switch(cache_->mode()) {
+ switch (cache_->mode()) {
case NORMAL:
break;
case RECORD:
@@ -613,11 +647,25 @@ void HttpCache::Transaction::SetRequest(const HttpRequestInfo* request) {
{ kForceValidateHeaders, LOAD_VALIDATE_CACHE },
};
+ std::string new_extra_headers;
+ bool range_found = false;
+
// 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;
@@ -625,6 +673,20 @@ void HttpCache::Transaction::SetRequest(const HttpRequestInfo* request) {
}
}
}
+
+ 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;
+ }
+ }
}
bool HttpCache::Transaction::ShouldPassThrough() {
@@ -655,18 +717,16 @@ bool HttpCache::Transaction::ShouldPassThrough() {
int HttpCache::Transaction::BeginCacheRead() {
DCHECK(mode_ == READ);
- // read response headers
+ // Read response headers.
+ // TODO(rvargas): Handle partial content (206).
return HandleResult(ReadResponseInfoFromEntry());
}
int HttpCache::Transaction::BeginCacheValidation() {
DCHECK(mode_ == READ_WRITE);
- int rv = ReadResponseInfoFromEntry();
- if (rv != OK) {
- DCHECK(rv != ERR_IO_PENDING);
- } else if (effective_load_flags_ & LOAD_PREFERRING_CACHE ||
- !RequiresValidation()) {
+ if ((effective_load_flags_ & LOAD_PREFERRING_CACHE ||
+ !RequiresValidation()) && !partial_.get()) {
cache_->ConvertWriterToReader(entry_);
mode_ = READ;
} else {
@@ -678,7 +738,47 @@ int HttpCache::Transaction::BeginCacheValidation() {
mode_ = WRITE;
return BeginNetworkRequest();
}
- return HandleResult(rv);
+ 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 (!partial_.get()) {
+ // The request is not for a range, but we have stored just ranges.
+ // TODO(rvargas): Add support for this case.
+ NOTREACHED();
+ }
+
+ 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);
+ }
+
+ return BeginCacheValidation();
}
int HttpCache::Transaction::BeginNetworkRequest() {
@@ -744,8 +844,9 @@ bool HttpCache::Transaction::RequiresValidation() {
bool HttpCache::Transaction::ConditionalizeRequest() {
DCHECK(response_.headers);
- // This only makes sense for cached 200 responses.
- if (response_.headers->response_code() != 200)
+ // 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.
@@ -761,18 +862,33 @@ bool HttpCache::Transaction::ConditionalizeRequest() {
if (etag_value.empty() && last_modified_value.empty())
return false;
- // Need to customize the request, so this forces us to allocate :(
- custom_request_.reset(new HttpRequestInfo(*request_));
- request_ = custom_request_.get();
+ 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()) {
- custom_request_->extra_headers.append("If-None-Match: ");
+ 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()) {
- custom_request_->extra_headers.append("If-Modified-Since: ");
+ 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");
}
@@ -780,6 +896,36 @@ bool HttpCache::Transaction::ConditionalizeRequest() {
return true;
}
+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_);
@@ -793,8 +939,13 @@ void HttpCache::Transaction::WriteToEntry(int index, int offset,
if (!entry_)
return;
- int rv = entry_->disk_entry->WriteData(index, offset, data, data_len, NULL,
- true);
+ 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);
@@ -834,7 +985,7 @@ void HttpCache::Transaction::WriteResponseInfoToEntry() {
void HttpCache::Transaction::AppendResponseDataToEntry(IOBuffer* data,
int data_len) {
- if (!entry_)
+ if (!entry_ || !data_len)
return;
int current_size = entry_->disk_entry->GetDataSize(kResponseContentIndex);
@@ -876,10 +1027,19 @@ void HttpCache::Transaction::OnNetworkInfoAvailable(int result) {
new_response->headers->response_code() == 407) {
auth_response_ = *new_response;
} else {
+#ifdef ENABLE_RANGE_SUPPORT
+ bool partial_content = new_response->headers->response_code() == 206;
+#else
+ bool partial_content = false;
+#endif
+ // TODO(rvargas): Validate partial_content vs partial_ and mode_
+ if (partial_content) {
+ DCHECK(partial_.get());
+ }
// Are we expecting a response to a conditional query?
if (mode_ == READ_WRITE) {
- if (new_response->headers->response_code() == 304) {
- // Update cached response based on headers in new_response
+ 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);
@@ -889,12 +1049,14 @@ void HttpCache::Transaction::OnNetworkInfoAvailable(int result) {
WriteResponseInfoToEntry();
}
- if (entry_) {
- cache_->ConvertWriterToReader(entry_);
+ if (entry_ && !partial_content) {
+ 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();
- mode_ = READ;
+ if (!partial_.get() || partial_->IsLastRange())
+ mode_ = READ;
}
} else {
mode_ = WRITE;
@@ -905,7 +1067,7 @@ void HttpCache::Transaction::OnNetworkInfoAvailable(int result) {
response_ = *new_response;
WriteResponseInfoToEntry();
- // Truncate response data
+ // Truncate response data.
TruncateResponseData();
// If this response is a redirect, then we can stop writing now. (We
@@ -913,6 +1075,16 @@ void HttpCache::Transaction::OnNetworkInfoAvailable(int result) {
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 (IsCertificateError(result)) {
const HttpResponseInfo* response = network_trans_->GetResponseInfo();
@@ -925,37 +1097,82 @@ void HttpCache::Transaction::OnNetworkInfoAvailable(int result) {
}
void HttpCache::Transaction::OnNetworkReadCompleted(int result) {
+ DoNetworkReadCompleted(result);
+}
+
+int HttpCache::Transaction::DoNetworkReadCompleted(int result) {
DCHECK(mode_ & WRITE || mode_ == NONE);
- if (revoked()) {
- HandleResult(ERR_UNEXPECTED);
- return;
- }
+ if (revoked())
+ return HandleResult(ERR_UNEXPECTED);
- if (result > 0) {
- AppendResponseDataToEntry(read_buf_, result);
- } else if (result == 0) { // end of file
+ 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);
}
- HandleResult(result);
+ 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()) {
- HandleResult(ERR_UNEXPECTED);
- return;
- }
+ 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
+ } else if (result == 0) { // End of file.
cache_->DoneReadingFromEntry(entry_, this);
entry_ = NULL;
}
- HandleResult(result);
+ 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);
}
//-----------------------------------------------------------------------------
@@ -1149,7 +1366,8 @@ bool HttpCache::WriteResponseInfo(disk_cache::Entry* disk_entry,
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_NON_CACHEABLE |
+ net::HttpResponseHeaders::PERSIST_SANS_RANGES;
}
response_info->headers->Persist(&pickle, persist_options);
diff --git a/net/http/http_cache_unittest.cc b/net/http/http_cache_unittest.cc
index ae1ecaa..2e0f94d 100644
--- a/net/http/http_cache_unittest.cc
+++ b/net/http/http_cache_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// 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.
@@ -10,10 +10,12 @@
#include "net/base/net_errors.h"
#include "net/base/load_flags.h"
#include "net/disk_cache/disk_cache.h"
+#include "net/http/http_byte_range.h"
#include "net/http/http_request_info.h"
#include "net/http/http_response_info.h"
#include "net/http/http_transaction.h"
#include "net/http/http_transaction_unittest.h"
+#include "net/http/http_util.h"
#include "testing/gtest/include/gtest/gtest.h"
using base::Time;
@@ -412,6 +414,77 @@ const MockTransaction kFastNoStoreGET_Transaction = {
0
};
+// This class provides a handler for kRangeGET_TransactionOK so that the range
+// request can be served on demand.
+class RangeTransactionServer {
+ public:
+ RangeTransactionServer() {
+ no_store = false;
+ }
+ ~RangeTransactionServer() {}
+
+ void set_no_store(bool value) { no_store = value; }
+
+ static void RangeHandler(const net::HttpRequestInfo* request,
+ std::string* response_status,
+ std::string* response_headers,
+ std::string* response_data);
+
+ private:
+ static bool no_store;
+ DISALLOW_COPY_AND_ASSIGN(RangeTransactionServer);
+};
+
+// Static.
+void RangeTransactionServer::RangeHandler(const net::HttpRequestInfo* request,
+ std::string* response_status,
+ std::string* response_headers,
+ std::string* response_data) {
+ if (request->extra_headers.empty())
+ return;
+
+ std::vector<net::HttpByteRange> ranges;
+ if (!net::HttpUtil::ParseRanges(request->extra_headers, &ranges) ||
+ ranges.size() != 1)
+ return;
+ // We can handle this range request.
+ net::HttpByteRange byte_range = ranges[0];
+ EXPECT_TRUE(byte_range.ComputeBounds(80));
+ int start = static_cast<int>(byte_range.first_byte_position());
+ int end = static_cast<int>(byte_range.last_byte_position());
+
+ EXPECT_LT(end, 80);
+
+ std::string content_range = StringPrintf("Content-Range: bytes %d-%d/80\n",
+ start, end);
+ response_headers->append(content_range);
+
+ if (request->extra_headers.find("If-None-Match") == std::string::npos) {
+ EXPECT_EQ(9, end - start);
+ std::string data = StringPrintf("rg: %d-%d ", start, end);
+ *response_data = data;
+ } else {
+ response_status->assign("HTTP/1.1 304 Not Modified");
+ response_data->clear();
+ }
+}
+
+const MockTransaction kRangeGET_TransactionOK = {
+ "http://www.google.com/range",
+ "GET",
+ "Range: bytes = 40-49\r\n",
+ net::LOAD_NORMAL,
+ "HTTP/1.1 206 Partial Content",
+ "Last-Modified: Sat, 18 Apr 2009 01:10:43 GMT\n"
+ "ETag: \"foo\"\n"
+ "Accept-Ranges: bytes\n"
+ "Content-Length: 10\n",
+ "rg: 40-49 ",
+ TEST_MODE_NORMAL,
+ &RangeTransactionServer::RangeHandler,
+ 0
+};
+
} // namespace
@@ -1048,9 +1121,9 @@ TEST(HttpCache, SimplePOST_LoadOnlyFromCache_Hit) {
TEST(HttpCache, RangeGET_SkipsCache) {
MockHttpCache cache;
- // Test that we skip the cache for POST requests. Eventually, we will want
- // to cache these, but we'll still have cases where skipping the cache makes
- // sense, so we want to make sure that it works properly.
+ // Test that we skip the cache for range GET requests. Eventually, we will
+ // want to cache these, but we'll still have cases where skipping the cache
+ // makes sense, so we want to make sure that it works properly.
RunTransactionTest(cache.http_cache(), kRangeGET_Transaction);
@@ -1075,6 +1148,55 @@ TEST(HttpCache, RangeGET_SkipsCache) {
EXPECT_EQ(0, cache.disk_cache()->create_count());
}
+TEST(HttpCache, DISABLED_RangeGET_OK) {
+ MockHttpCache cache;
+ AddMockTransaction(&kRangeGET_TransactionOK);
+
+ // Test that we can cache range requests and fetch random blocks from the
+ // cache and the network.
+
+ // Write to the cache (40-49).
+ RunTransactionTest(cache.http_cache(), kRangeGET_TransactionOK);
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Read from the cache (40-49).
+ RunTransactionTest(cache.http_cache(), kRangeGET_TransactionOK);
+
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Make sure we are done with the previous transaction.
+ MessageLoop::current()->RunAllPending();
+
+ // Write to the cache (30-39).
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.request_headers = "Range: bytes = 30-39\r\n";
+ transaction.data = "rg: 30-39 ";
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(3, cache.network_layer()->transaction_count());
+ EXPECT_EQ(2, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ // Make sure we are done with the previous transaction.
+ MessageLoop::current()->RunAllPending();
+
+ // Write and read from the cache (20-59).
+ transaction.request_headers = "Range: bytes = 20-59\r\n";
+ transaction.data = "rg: 20-29 rg: 30-39 rg: 40-49 rg: 50-59 ";
+ RunTransactionTest(cache.http_cache(), transaction);
+
+ EXPECT_EQ(6, cache.network_layer()->transaction_count());
+ EXPECT_EQ(3, cache.disk_cache()->open_count());
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+
+ RemoveMockTransaction(&kRangeGET_TransactionOK);
+}
+
TEST(HttpCache, SyncRead) {
MockHttpCache cache;
diff --git a/net/http/http_response_headers.cc b/net/http/http_response_headers.cc
index 7f75466..4038aff 100644
--- a/net/http/http_response_headers.cc
+++ b/net/http/http_response_headers.cc
@@ -121,6 +121,9 @@ void HttpResponseHeaders::Persist(Pickle* pickle, PersistOptions options) {
if ((options & PERSIST_SANS_HOP_BY_HOP) == PERSIST_SANS_HOP_BY_HOP)
AddHopByHopHeaders(&filter_headers);
+ if ((options & PERSIST_SANS_RANGES) == PERSIST_SANS_RANGES)
+ AddHopContentRangeHeaders(&filter_headers);
+
std::string blob;
blob.reserve(raw_headers_.size());
@@ -645,6 +648,10 @@ void HttpResponseHeaders::AddChallengeHeaders(HeaderSet* result) {
result->insert(std::string(kChallengeResponseHeaders[i]));
}
+void HttpResponseHeaders::AddHopContentRangeHeaders(HeaderSet* result) {
+ result->insert("content-range");
+}
+
void HttpResponseHeaders::GetMimeTypeAndCharset(std::string* mime_type,
std::string* charset) const {
mime_type->clear();
diff --git a/net/http/http_response_headers.h b/net/http/http_response_headers.h
index 830c09c..1989d07 100644
--- a/net/http/http_response_headers.h
+++ b/net/http/http_response_headers.h
@@ -52,6 +52,7 @@ class HttpResponseHeaders :
static const PersistOptions PERSIST_SANS_CHALLENGES = 1 << 1;
static const PersistOptions PERSIST_SANS_HOP_BY_HOP = 1 << 2;
static const PersistOptions PERSIST_SANS_NON_CACHEABLE = 1 << 3;
+ static const PersistOptions PERSIST_SANS_RANGES = 1 << 4;
// Appends a representation of this object to the given pickle.
// The options argument can be a combination of PersistOptions.
@@ -276,6 +277,9 @@ class HttpResponseHeaders :
// Adds the set of cookie response headers.
static void AddCookieHeaders(HeaderSet* header_names);
+ // Adds the set of content range response headers.
+ static void AddHopContentRangeHeaders(HeaderSet* header_names);
+
// The members of this structure point into raw_headers_.
struct ParsedHeader {
std::string::const_iterator name_begin;
diff --git a/net/http/partial_data.cc b/net/http/partial_data.cc
new file mode 100644
index 0000000..44ba086
--- /dev/null
+++ b/net/http/partial_data.cc
@@ -0,0 +1,131 @@
+// Copyright (c) 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/partial_data.h"
+
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/http/http_util.h"
+
+namespace net {
+
+bool PartialData::Init(const std::string& headers,
+ const std::string& new_headers) {
+ std::vector<HttpByteRange> ranges;
+ if (!HttpUtil::ParseRanges(headers, &ranges) || ranges.size() != 1)
+ return false;
+
+ // We can handle this range request.
+ byte_range_ = ranges[0];
+ if (!byte_range_.IsValid())
+ return false;
+
+ extra_headers_ = new_headers;
+
+ // TODO(rvargas): Handle requests without explicit start or end.
+ DCHECK(byte_range_.HasFirstBytePosition());
+ current_range_start_ = byte_range_.first_byte_position();
+ return true;
+}
+
+void PartialData::RestoreHeaders(std::string* headers) const {
+ DCHECK(current_range_start_ >= 0);
+
+ // TODO(rvargas): Handle requests without explicit start or end.
+ AddRangeHeader(current_range_start_, byte_range_.last_byte_position(),
+ headers);
+}
+
+int PartialData::PrepareCacheValidation(disk_cache::Entry* entry,
+ std::string* headers) {
+ DCHECK(current_range_start_ >= 0);
+
+ // Scan the disk cache for the first cached portion within this range.
+ int64 range_len = byte_range_.HasLastBytePosition() ?
+ byte_range_.last_byte_position() - current_range_start_ + 1: kint32max;
+ if (range_len > kint32max)
+ range_len = kint32max;
+ int len = static_cast<int32>(range_len);
+ if (!len)
+ return 0;
+ range_present_ = false;
+
+ cached_min_len_ = entry->GetAvailableRange(current_range_start_, len,
+ &cached_start_);
+ if (cached_min_len_ < 0) {
+ DCHECK(cached_min_len_ != ERR_IO_PENDING);
+ return cached_min_len_;
+ }
+
+ headers->assign(extra_headers_);
+
+ if (!cached_min_len_) {
+ // We don't have anything else stored.
+ final_range_ = true;
+ cached_start_ = current_range_start_ + len;
+ }
+
+ if (current_range_start_ == cached_start_) {
+ // The data lives in the cache.
+ range_present_ = true;
+ if (len == cached_min_len_)
+ final_range_ = true;
+ AddRangeHeader(current_range_start_, cached_start_ + cached_min_len_ - 1,
+ headers);
+ } else {
+ // This range is not in the cache.
+ AddRangeHeader(current_range_start_, cached_start_ - 1, headers);
+ }
+
+ // Return a positive number to indicate success (versus error or finished).
+ return 1;
+}
+
+bool PartialData::IsCurrentRangeCached() const {
+ return range_present_;
+}
+
+bool PartialData::IsLastRange() const {
+ return final_range_;
+}
+
+int PartialData::CacheRead(disk_cache::Entry* entry, IOBuffer* data,
+ int data_len, CompletionCallback* callback) {
+ int read_len = std::min(data_len, cached_min_len_);
+ int rv = entry->ReadSparseData(current_range_start_, data, read_len,
+ callback);
+ return rv;
+}
+
+int PartialData::CacheWrite(disk_cache::Entry* entry, IOBuffer* data,
+ int data_len, CompletionCallback* callback) {
+ return entry->WriteSparseData(current_range_start_, data, data_len,
+ callback);
+}
+
+void PartialData::OnCacheReadCompleted(int result) {
+ if (result > 0) {
+ current_range_start_ += result;
+ cached_min_len_ -= result;
+ DCHECK(cached_min_len_ >= 0);
+ } else if (!result) {
+ // TODO(rvargas): we can detect this error and make sure that we are not
+ // in a loop of failure/retry.
+ }
+}
+
+void PartialData::OnNetworkReadCompleted(int result) {
+ if (result > 0)
+ current_range_start_ += result;
+}
+
+// Static.
+void PartialData::AddRangeHeader(int64 start, int64 end, std::string* headers) {
+ headers->append(StringPrintf("Range: bytes=%lld-%lld\r\n", start, end));
+}
+
+
+} // namespace net
diff --git a/net/http/partial_data.h b/net/http/partial_data.h
new file mode 100644
index 0000000..ced53ab
--- /dev/null
+++ b/net/http/partial_data.h
@@ -0,0 +1,99 @@
+// Copyright (c) 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.
+
+#ifndef NET_HTTP_PARTIAL_DATA_H_
+#define NET_HTTP_PARTIAL_DATA_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "net/base/completion_callback.h"
+#include "net/http/http_byte_range.h"
+
+namespace disk_cache {
+class Entry;
+}
+
+namespace net {
+
+class IOBuffer;
+
+// This class provides support for dealing with range requests and the
+// subsequent partial-content responses. We use sparse cache entries to store
+// these requests. This class is tightly integrated with HttpCache::Transaction
+// and it is intended to allow a cleaner implementation of that class.
+//
+// In order to fulfill range requests, we may have to perform a sequence of
+// reads from the cache, interleaved with reads from the network / writes to the
+// cache. This class basically keeps track of the data required to perform each
+// of those individual network / cache requests.
+class PartialData {
+ public:
+ PartialData() : range_present_(false), final_range_(false) {}
+ ~PartialData() {}
+
+ // Performs initialization of the object by parsing the request |headers|
+ // and verifying that we can process the requested range. Returns true if
+ // we can process the requested range, and false otherwise. |new_headers| is
+ // a subset of the request extra headers, with byte-range related headers
+ // removed so that we can easily add any byte-range that we need.
+ bool Init(const std::string& headers, const std::string& new_headers);
+
+ // Restores the byte-range header that was removed during Init(), by appending
+ // the data to the provided |headers|.
+ void RestoreHeaders(std::string* headers) const;
+
+ // Builds the required |headers| to perform the proper cache validation for
+ // the next range to be fetched. Returns 0 when there is no need to perform
+ // more operations because we reached the end of the request (so 0 bytes
+ // should be actually returned to the user), a positive number to indicate
+ // that |headers| should be used to validate the cache, or an appropriate
+ // error code.
+ int PrepareCacheValidation(disk_cache::Entry* entry, std::string* headers);
+
+ // Returns true if the current range is stored in the cache.
+ bool IsCurrentRangeCached() const;
+
+ // Returns true if the current range is the last one needed to fulfill the
+ // user's request.
+ bool IsLastRange() const;
+
+ // Reads up to |data_len| bytes from the cache and stores them in the provided
+ // buffer (|data|). Basically, this is just a wrapper around the API of the
+ // cache that provides the right arguments for the current range. When the IO
+ // operation completes, OnCacheReadCompleted() must be called with the result
+ // of the operation.
+ int CacheRead(disk_cache::Entry* entry, IOBuffer* data, int data_len,
+ CompletionCallback* callback);
+
+ // Writes |data_len| bytes to cache. This is basically a wrapper around the
+ // API of the cache that provides the right arguments for the current range.
+ int CacheWrite(disk_cache::Entry* entry, IOBuffer* data, int data_len,
+ CompletionCallback* callback);
+
+ // This method should be called when CacheRead() finishes the read, to update
+ // the internal state about the current range.
+ void OnCacheReadCompleted(int result);
+
+ // This method should be called after receiving data from the network, to
+ // update the internal state about the current range.
+ void OnNetworkReadCompleted(int result);
+
+ private:
+ static void AddRangeHeader(int64 start, int64 end, std::string* headers);
+
+ int64 current_range_start_;
+ int64 cached_start_;
+ int cached_min_len_;
+ HttpByteRange byte_range_; // The range requested by the user.
+ std::string extra_headers_; // The clean set of extra headers (no ranges).
+ bool range_present_; // True if next range entry is already stored.
+ bool final_range_;
+
+ DISALLOW_COPY_AND_ASSIGN(PartialData);
+};
+
+} // namespace net
+
+#endif // NET_HTTP_PARTIAL_DATA_H_
diff --git a/net/net.gyp b/net/net.gyp
index fbac3e0..08d197c 100644
--- a/net/net.gyp
+++ b/net/net.gyp
@@ -246,6 +246,8 @@
'http/http_vary_data.h',
'http/md4.cc',
'http/md4.h',
+ 'http/partial_data.cc',
+ 'http/partial_data.h',
'proxy/proxy_config.cc',
'proxy/proxy_config.h',
'proxy/proxy_config_service.h',