From 661376a6c1eb3bd501398f225363c2d021288245 Mon Sep 17 00:00:00 2001 From: "hclam@chromium.org" Date: Wed, 29 Apr 2009 02:04:23 +0000 Subject: Supports single range request with file protocol Added range request support in URLRequestFileJob to respect "Range" HTTP header. Fail with ERR_REQUESTED_RANGE_NOT_SATISFIABLE if range is bad. The following range request modes are supported: 1. Fully specified: bytes=x-y 2. With first byte position only: bytes=x- 3. With suffix length: bytes=-y Multiple ranges in a single request is not supported as we need to multipart encoding.. Last review was here: http://codereview.chromium.org/92149 Review URL: http://codereview.chromium.org/102006 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@14817 0039d316-1c4b-4281-b951-d872f2087c98 --- net/url_request/url_request_file_job.cc | 68 ++++++++++++++-- net/url_request/url_request_file_job.h | 7 +- net/url_request/url_request_unittest.cc | 135 +++++++++++++++++++++++++++++++- 3 files changed, 202 insertions(+), 8 deletions(-) (limited to 'net/url_request') diff --git a/net/url_request/url_request_file_job.cc b/net/url_request/url_request_file_job.cc index db3ff13..8430fc9 100644 --- a/net/url_request/url_request_file_job.cc +++ b/net/url_request/url_request_file_job.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. @@ -29,6 +29,7 @@ #include "net/base/mime_util.h" #include "net/base/net_errors.h" #include "net/base/net_util.h" +#include "net/http/http_util.h" #include "net/url_request/url_request.h" #include "net/url_request/url_request_file_dir_job.h" @@ -90,7 +91,8 @@ URLRequestFileJob::URLRequestFileJob(URLRequest* request, file_path_(file_path), ALLOW_THIS_IN_INITIALIZER_LIST( io_callback_(this, &URLRequestFileJob::DidRead)), - is_directory_(false) { + is_directory_(false), + remaining_bytes_(0) { } URLRequestFileJob::~URLRequestFileJob() { @@ -135,11 +137,24 @@ bool URLRequestFileJob::ReadRawData(net::IOBuffer* dest, int dest_size, int *bytes_read) { DCHECK_NE(dest_size, 0); DCHECK(bytes_read); + DCHECK_GE(remaining_bytes_, 0); + + if (remaining_bytes_ < dest_size) + dest_size = static_cast(remaining_bytes_); + + // If we should copy zero bytes because |remaining_bytes_| is zero, short + // circuit here. + if (!dest_size) { + *bytes_read = 0; + return true; + } int rv = stream_.Read(dest->data(), dest_size, &io_callback_); if (rv >= 0) { // Data is immediately available. *bytes_read = rv; + remaining_bytes_ -= rv; + DCHECK_GE(remaining_bytes_, 0); return true; } @@ -175,6 +190,22 @@ void URLRequestFileJob::GetResponseInfo(net::HttpResponseInfo* info) { } } +void URLRequestFileJob::SetExtraRequestHeaders(const std::string& headers) { + // We only care about "Range" header here. + std::vector ranges; + if (net::HttpUtil::ParseRanges(headers, &ranges)) { + if (ranges.size() == 1) { + byte_range_ = ranges[0]; + } else { + // We don't support multiple range requests in one single URL request, + // because we need to do multipart encoding here. + // TODO(hclam): decide whether we want to support multiple range requests. + NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, + net::ERR_REQUEST_RANGE_NOT_SATISFIABLE)); + } + } +} + void URLRequestFileJob::DidResolve( bool exists, const file_util::FileInfo& file_info) { #if defined(OS_WIN) @@ -200,12 +231,33 @@ void URLRequestFileJob::DidResolve( rv = stream_.Open(file_path_, flags); } - if (rv == net::OK) { - set_expected_content_size(file_info.size); - NotifyHeadersComplete(); - } else { + if (rv != net::OK) { NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv)); + return; } + + if (!byte_range_.ComputeBounds(file_info.size)) { + NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, + net::ERR_REQUEST_RANGE_NOT_SATISFIABLE)); + return; + } + + remaining_bytes_ = byte_range_.last_byte_position() - + byte_range_.first_byte_position() + 1; + DCHECK_GE(remaining_bytes_, 0); + + // Do the seek at the beginning of the request. + if (remaining_bytes_ > 0 && + byte_range_.first_byte_position() != 0 && + byte_range_.first_byte_position() != + stream_.Seek(net::FROM_BEGIN, byte_range_.first_byte_position())) { + NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, + net::ERR_REQUEST_RANGE_NOT_SATISFIABLE)); + return; + } + + set_expected_content_size(remaining_bytes_); + NotifyHeadersComplete(); } void URLRequestFileJob::DidRead(int result) { @@ -216,6 +268,10 @@ void URLRequestFileJob::DidRead(int result) { } else { NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result)); } + + remaining_bytes_ -= result; + DCHECK_GE(remaining_bytes_, 0); + NotifyReadComplete(result); } diff --git a/net/url_request/url_request_file_job.h b/net/url_request/url_request_file_job.h index 94b65c4..3091d2a 100644 --- a/net/url_request/url_request_file_job.h +++ b/net/url_request/url_request_file_job.h @@ -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. @@ -11,6 +11,7 @@ #include "base/file_util.h" #include "net/base/completion_callback.h" #include "net/base/file_stream.h" +#include "net/http/http_byte_range.h" #include "net/url_request/url_request.h" #include "net/url_request/url_request_job.h" @@ -26,6 +27,7 @@ class URLRequestFileJob : public URLRequestJob { virtual bool IsRedirectResponse(GURL* location, int* http_status_code); virtual bool GetMimeType(std::string* mime_type) const; virtual void GetResponseInfo(net::HttpResponseInfo* info); + virtual void SetExtraRequestHeaders(const std::string& headers); static URLRequest::ProtocolFactory Factory; @@ -41,6 +43,9 @@ class URLRequestFileJob : public URLRequestJob { net::FileStream stream_; bool is_directory_; + net::HttpByteRange byte_range_; + int64 remaining_bytes_; + #if defined(OS_WIN) class AsyncResolver; friend class AsyncResolver; diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc index 86b5b9c..eb67a94 100644 --- a/net/url_request/url_request_unittest.cc +++ b/net/url_request/url_request_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. @@ -83,6 +83,21 @@ bool ContainsString(const std::string& haystack, const char* needle) { return it != haystack.end(); } +void FillBuffer(char* buffer, size_t len) { + static bool called = false; + if (!called) { + called = true; + int seed = static_cast(Time::Now().ToInternalValue()); + srand(seed); + } + + for (size_t i = 0; i < len; i++) { + buffer[i] = static_cast(rand()); + if (!buffer[i]) + buffer[i] = 'g'; + } +} + } // namespace // Inherit PlatformTest since we require the autorelease pool on Mac OS X.f @@ -613,6 +628,124 @@ TEST_F(URLRequestTest, FileTest) { #endif } +TEST_F(URLRequestTest, FileTestFullSpecifiedRange) { + const size_t buffer_size = 4000; + scoped_array buffer(new char[buffer_size]); + FillBuffer(buffer.get(), buffer_size); + + FilePath temp_path; + EXPECT_TRUE(file_util::CreateTemporaryFileName(&temp_path)); + GURL temp_url = net::FilePathToFileURL(temp_path); + file_util::WriteFile(temp_path, buffer.get(), buffer_size); + + int64 file_size; + EXPECT_TRUE(file_util::GetFileSize(temp_path, &file_size)); + + const size_t first_byte_position = 500; + const size_t last_byte_position = buffer_size - first_byte_position; + const size_t content_length = last_byte_position - first_byte_position + 1; + std::string partial_buffer_string(buffer.get() + first_byte_position, + buffer.get() + last_byte_position + 1); + + TestDelegate d; + { + TestURLRequest r(temp_url, &d); + + r.SetExtraRequestHeaders(StringPrintf("Range: bytes=%d-%d\n", + first_byte_position, + last_byte_position)); + r.Start(); + EXPECT_TRUE(r.is_pending()); + + MessageLoop::current()->Run(); + EXPECT_TRUE(!r.is_pending()); + EXPECT_EQ(1, d.response_started_count()); + EXPECT_FALSE(d.received_data_before_response()); + EXPECT_EQ(static_cast(content_length), d.bytes_received()); + // Don't use EXPECT_EQ, it will print out a lot of garbage if check failed. + EXPECT_TRUE(partial_buffer_string == d.data_received()); + } + + EXPECT_TRUE(file_util::Delete(temp_path, false)); +#ifndef NDEBUG + DCHECK_EQ(url_request_metrics.object_count, 0); +#endif +} + +TEST_F(URLRequestTest, FileTestHalfSpecifiedRange) { + const size_t buffer_size = 4000; + scoped_array buffer(new char[buffer_size]); + FillBuffer(buffer.get(), buffer_size); + + FilePath temp_path; + EXPECT_TRUE(file_util::CreateTemporaryFileName(&temp_path)); + GURL temp_url = net::FilePathToFileURL(temp_path); + file_util::WriteFile(temp_path, buffer.get(), buffer_size); + + int64 file_size; + EXPECT_TRUE(file_util::GetFileSize(temp_path, &file_size)); + + const size_t first_byte_position = 500; + const size_t last_byte_position = buffer_size - 1; + const size_t content_length = last_byte_position - first_byte_position + 1; + std::string partial_buffer_string(buffer.get() + first_byte_position, + buffer.get() + last_byte_position + 1); + + TestDelegate d; + { + TestURLRequest r(temp_url, &d); + + r.SetExtraRequestHeaders(StringPrintf("Range: bytes=%d-\n", + first_byte_position)); + r.Start(); + EXPECT_TRUE(r.is_pending()); + + MessageLoop::current()->Run(); + EXPECT_TRUE(!r.is_pending()); + EXPECT_EQ(1, d.response_started_count()); + EXPECT_FALSE(d.received_data_before_response()); + EXPECT_EQ(static_cast(content_length), d.bytes_received()); + // Don't use EXPECT_EQ, it will print out a lot of garbage if check failed. + EXPECT_TRUE(partial_buffer_string == d.data_received()); + } + + EXPECT_TRUE(file_util::Delete(temp_path, false)); +#ifndef NDEBUG + DCHECK_EQ(url_request_metrics.object_count, 0); +#endif +} + +TEST_F(URLRequestTest, FileTestMultipleRanges) { + const size_t buffer_size = 400000; + scoped_array buffer(new char[buffer_size]); + FillBuffer(buffer.get(), buffer_size); + + FilePath temp_path; + EXPECT_TRUE(file_util::CreateTemporaryFileName(&temp_path)); + GURL temp_url = net::FilePathToFileURL(temp_path); + file_util::WriteFile(temp_path, buffer.get(), buffer_size); + + int64 file_size; + EXPECT_TRUE(file_util::GetFileSize(temp_path, &file_size)); + + TestDelegate d; + { + TestURLRequest r(temp_url, &d); + + r.SetExtraRequestHeaders(StringPrintf("Range: bytes=0-0,10-200,200-300\n")); + r.Start(); + EXPECT_TRUE(r.is_pending()); + + MessageLoop::current()->Run(); + EXPECT_TRUE(d.request_failed()); + } + + EXPECT_TRUE(file_util::Delete(temp_path, false)); +#ifndef NDEBUG + DCHECK_EQ(url_request_metrics.object_count, 0); +#endif +} + TEST_F(URLRequestTest, InvalidUrlTest) { TestDelegate d; { -- cgit v1.1