summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/renderer_host/resource_dispatcher_host.cc6
-rw-r--r--chrome/browser/renderer_host/resource_dispatcher_host.h21
-rw-r--r--net/url_request/url_request.cc52
-rw-r--r--net/url_request/url_request.h64
-rw-r--r--net/url_request/url_request_job.cc17
-rw-r--r--net/url_request/url_request_job.h4
-rw-r--r--net/url_request/url_request_job_manager.cc44
-rw-r--r--net/url_request/url_request_job_manager.h12
-rw-r--r--net/url_request/url_request_test_job.cc150
-rw-r--r--net/url_request/url_request_test_job.h70
-rw-r--r--net/url_request/url_request_unittest.cc447
11 files changed, 802 insertions, 85 deletions
diff --git a/chrome/browser/renderer_host/resource_dispatcher_host.cc b/chrome/browser/renderer_host/resource_dispatcher_host.cc
index f73d507..db852b4 100644
--- a/chrome/browser/renderer_host/resource_dispatcher_host.cc
+++ b/chrome/browser/renderer_host/resource_dispatcher_host.cc
@@ -402,7 +402,7 @@ void ResourceDispatcherHost::BeginRequest(
upload_size);
extra_info->allow_download =
ResourceType::IsFrame(request_data.resource_type);
- request->set_user_data(extra_info); // takes pointer ownership
+ SetExtraInfoForRequest(request, extra_info); // request takes ownership
BeginRequestInternal(request);
}
@@ -541,7 +541,7 @@ void ResourceDispatcherHost::BeginDownload(const GURL& url,
0 /* upload_size */);
extra_info->allow_download = true;
extra_info->is_download = true;
- request->set_user_data(extra_info); // Takes pointer ownership.
+ SetExtraInfoForRequest(request, extra_info); // request takes ownership
BeginRequestInternal(request);
}
@@ -596,7 +596,7 @@ void ResourceDispatcherHost::BeginSaveFile(const GURL& url,
// Just saving some resources we need, disallow downloading.
extra_info->allow_download = false;
extra_info->is_download = false;
- request->set_user_data(extra_info); // Takes pointer ownership.
+ SetExtraInfoForRequest(request, extra_info); // request takes ownership
BeginRequestInternal(request);
}
diff --git a/chrome/browser/renderer_host/resource_dispatcher_host.h b/chrome/browser/renderer_host/resource_dispatcher_host.h
index 052c157..ba699bc 100644
--- a/chrome/browser/renderer_host/resource_dispatcher_host.h
+++ b/chrome/browser/renderer_host/resource_dispatcher_host.h
@@ -309,20 +309,21 @@ class ResourceDispatcherHost : public URLRequest::Delegate {
virtual void OnReadCompleted(URLRequest* request, int bytes_read);
void OnResponseCompleted(URLRequest* request);
- // Helper function to get our extra data out of a request. The given request
+ // Helper functions to get our extra data out of a request. The given request
// must have been one we created so that it has the proper extra data pointer.
static ExtraRequestInfo* ExtraInfoForRequest(URLRequest* request) {
- ExtraRequestInfo* r = static_cast<ExtraRequestInfo*>(request->user_data());
- DLOG_IF(WARNING, !r) << "Request doesn't seem to have our data";
- return r;
+ ExtraRequestInfo* info
+ = static_cast<ExtraRequestInfo*>(request->GetUserData(NULL));
+ DLOG_IF(WARNING, !info) << "Request doesn't seem to have our data";
+ return info;
}
static const ExtraRequestInfo* ExtraInfoForRequest(
const URLRequest* request) {
- const ExtraRequestInfo* r =
- static_cast<const ExtraRequestInfo*>(request->user_data());
- DLOG_IF(WARNING, !r) << "Request doesn't seem to have our data";
- return r;
+ const ExtraRequestInfo* info =
+ static_cast<const ExtraRequestInfo*>(request->GetUserData(NULL));
+ DLOG_IF(WARNING, !info) << "Request doesn't seem to have our data";
+ return info;
}
// Adds an observer. The observer will be called on the IO thread. To
@@ -380,6 +381,10 @@ class ResourceDispatcherHost : public URLRequest::Delegate {
friend class ShutdownTask;
+ void SetExtraInfoForRequest(URLRequest* request, ExtraRequestInfo* info) {
+ request->SetUserData(NULL, info);
+ }
+
// A shutdown helper that runs on the IO thread.
void OnShutdown();
diff --git a/net/url_request/url_request.cc b/net/url_request/url_request.cc
index acf70dd..45043e9 100644
--- a/net/url_request/url_request.cc
+++ b/net/url_request/url_request.cc
@@ -44,7 +44,6 @@ URLRequest::URLRequest(const GURL& url, Delegate* delegate)
load_flags_(net::LOAD_NORMAL),
delegate_(delegate),
is_pending_(false),
- user_data_(NULL),
enable_profiling_(false),
redirect_limit_(kMaxRedirects),
final_upload_progress_(0),
@@ -67,8 +66,6 @@ URLRequest::~URLRequest() {
if (job_)
OrphanJob();
-
- delete user_data_; // NULL check unnecessary for delete
}
// static
@@ -240,10 +237,14 @@ void URLRequest::set_referrer(const std::string& referrer) {
}
void URLRequest::Start() {
+ StartJob(GetJobManager()->CreateJob(this));
+}
+
+void URLRequest::StartJob(URLRequestJob* job) {
DCHECK(!is_pending_);
DCHECK(!job_);
- job_ = GetJobManager()->CreateJob(this);
+ job_ = job;
job_->SetExtraRequestHeaders(extra_request_headers_);
if (upload_.get())
@@ -260,6 +261,20 @@ void URLRequest::Start() {
job_->Start();
}
+void URLRequest::Restart() {
+ // Should only be called if the original job didn't make any progress.
+ DCHECK(job_ && !job_->has_response_started());
+ RestartWithJob(GetJobManager()->CreateJob(this));
+}
+
+void URLRequest::RestartWithJob(URLRequestJob *job) {
+ DCHECK(job->request() == this);
+ OrphanJob();
+ status_ = URLRequestStatus();
+ is_pending_ = false;
+ StartJob(job);
+}
+
void URLRequest::Cancel() {
DoCancel(net::ERR_ABORTED, net::SSLInfo());
}
@@ -319,6 +334,24 @@ bool URLRequest::Read(net::IOBuffer* dest, int dest_size, int *bytes_read) {
return job_->Read(dest, dest_size, bytes_read);
}
+void URLRequest::ReceivedRedirect(const GURL& location) {
+ URLRequestJob* job = GetJobManager()->MaybeInterceptRedirect(this, location);
+ if (job) {
+ RestartWithJob(job);
+ } else if (delegate_) {
+ delegate_->OnReceivedRedirect(this, location);
+ }
+}
+
+void URLRequest::ResponseStarted() {
+ URLRequestJob* job = GetJobManager()->MaybeInterceptResponse(this);
+ if (job) {
+ RestartWithJob(job);
+ } else if (delegate_) {
+ delegate_->OnResponseStarted(this);
+ }
+}
+
void URLRequest::SetAuth(const wstring& username, const wstring& password) {
DCHECK(job_);
DCHECK(job_->NeedsAuth());
@@ -422,6 +455,17 @@ int64 URLRequest::GetExpectedContentSize() const {
return expected_content_size;
}
+URLRequest::UserData* URLRequest::GetUserData(void* key) const {
+ UserDataMap::const_iterator found = user_data_.find(key);
+ if (found != user_data_.end())
+ return found->second.get();
+ return NULL;
+}
+
+void URLRequest::SetUserData(void* key, UserData* data) {
+ user_data_[key] = linked_ptr<UserData>(data);
+}
+
#ifndef NDEBUG
URLRequestMetrics::~URLRequestMetrics() {
diff --git a/net/url_request/url_request.h b/net/url_request/url_request.h
index a075764..b9863b1 100644
--- a/net/url_request/url_request.h
+++ b/net/url_request/url_request.h
@@ -5,12 +5,15 @@
#ifndef NET_URL_REQUEST_URL_REQUEST_H_
#define NET_URL_REQUEST_URL_REQUEST_H_
+#include <map>
#include <string>
#include <vector>
#include "base/file_path.h"
+#include "base/linked_ptr.h"
#include "base/logging.h"
#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
#include "googleurl/src/gurl.h"
#include "net/base/load_states.h"
#include "net/http/http_response_info.h"
@@ -48,7 +51,7 @@ typedef std::vector<std::string> ResponseCookies;
class URLRequest {
public:
// Derive from this class and add your own data members to associate extra
- // information with a URLRequest. Use user_data() and set_user_data()
+ // information with a URLRequest. Use GetUserData(key) and SetUserData()
class UserData {
public:
UserData() {}
@@ -72,6 +75,29 @@ class URLRequest {
// request if it should be intercepted, or NULL to allow the request to
// be handled in the normal manner.
virtual URLRequestJob* MaybeIntercept(URLRequest* request) = 0;
+
+ // Called after having received a redirect response, but prior to the
+ // the request delegate being informed of the redirect. Can return a new
+ // job to replace the existing job if it should be intercepted, or NULL
+ // to allow the normal handling to continue. If a new job is provided,
+ // the delegate never sees the original redirect response, instead the
+ // response produced by the intercept job will be returned.
+ virtual URLRequestJob* MaybeInterceptRedirect(URLRequest* request,
+ const GURL& location) {
+ return NULL;
+ }
+
+ // Called after having received a final response, but prior to the
+ // the request delegate being informed of the response. This is also
+ // called when there is no server response at all to allow interception
+ // on dns or network errors. Can return a new job to replace the existing
+ // job if it should be intercepted, or NULL to allow the normal handling to
+ // continue. If a new job is provided, the delegate never sees the original
+ // response, instead the response produced by the intercept job will be
+ // returned.
+ virtual URLRequestJob* MaybeInterceptResponse(URLRequest* request) {
+ return NULL;
+ }
};
// The delegate's methods are called from the message loop of the thread
@@ -157,17 +183,12 @@ class URLRequest {
// will not have any more of its methods called.
~URLRequest();
- // The user data allows the owner to associate data with this request.
- // This request will TAKE OWNERSHIP of the given pointer, and will delete
- // the object if it is changed or the request is destroyed.
- UserData* user_data() const {
- return user_data_;
- }
- void set_user_data(UserData* user_data) {
- if (user_data_)
- delete user_data_;
- user_data_ = user_data;
- }
+ // The user data allows the clients to associate data with this request.
+ // Multiple user data values can be stored under different keys.
+ // This request will TAKE OWNERSHIP of the given data pointer, and will
+ // delete the object if it is changed or the request is destroyed.
+ UserData* GetUserData(void* key) const;
+ void SetUserData(void* key, UserData* data);
// Registers a new protocol handler for the given scheme. If the scheme is
// already handled, this will overwrite the given factory. To delete the
@@ -450,9 +471,23 @@ class URLRequest {
// successful, otherwise an error code is returned.
int Redirect(const GURL& location, int http_status_code);
+ // Called by URLRequestJob to allow interception when a redirect occurs.
+ void ReceivedRedirect(const GURL& location);
+
+ // Called by URLRequestJob to allow interception when the final response
+ // occurs.
+ void ResponseStarted();
+
+ // Allow an interceptor's URLRequestJob to restart this request.
+ // Should only be called if the original job has not started a resposne.
+ void Restart();
+
private:
friend class URLRequestJob;
+ void StartJob(URLRequestJob* job);
+ void RestartWithJob(URLRequestJob *job);
+
// Detaches the job from this request in preparation for this object going
// away or the job being replaced. The job will not call us back when it has
// been orphaned.
@@ -496,8 +531,9 @@ class URLRequest {
// whether the job is active.
bool is_pending_;
- // Externally-defined data associated with this request
- UserData* user_data_;
+ // Externally-defined data accessible by key
+ typedef std::map<void*, linked_ptr<UserData> > UserDataMap;
+ UserDataMap user_data_;
// Whether to enable performance profiling on the job serving this request.
bool enable_profiling_;
diff --git a/net/url_request/url_request_job.cc b/net/url_request/url_request_job.cc
index b5309c2..544913d 100644
--- a/net/url_request/url_request_job.cc
+++ b/net/url_request/url_request_job.cc
@@ -337,9 +337,9 @@ void URLRequestJob::NotifyHeadersComplete() {
// Toggle this flag to true so the consumer can access response headers.
// Then toggle it back if we choose to follow the redirect.
has_handled_response_ = true;
- request_->delegate()->OnReceivedRedirect(request_, new_location);
+ request_->ReceivedRedirect(new_location);
- // Ensure that the request wasn't destroyed in OnReceivedRedirect
+ // Ensure that the request wasn't detached or destroyed in ReceivedRedirect
if (!request_ || !request_->delegate())
return;
@@ -372,7 +372,7 @@ void URLRequestJob::NotifyHeadersComplete() {
expected_content_size_ = StringToInt64(content_length);
}
- request_->delegate()->OnResponseStarted(request_);
+ request_->ResponseStarted();
}
void URLRequestJob::NotifyStartError(const URLRequestStatus &status) {
@@ -380,8 +380,7 @@ void URLRequestJob::NotifyStartError(const URLRequestStatus &status) {
has_handled_response_ = true;
if (request_) {
request_->set_status(status);
- if (request_->delegate())
- request_->delegate()->OnResponseStarted(request_);
+ request_->ResponseStarted();
}
}
@@ -482,7 +481,7 @@ void URLRequestJob::CompleteNotifyDone() {
request_->delegate()->OnReadCompleted(request_, -1);
} else {
has_handled_response_ = true;
- request_->delegate()->OnResponseStarted(request_);
+ request_->ResponseStarted();
}
}
}
@@ -494,6 +493,12 @@ void URLRequestJob::NotifyCanceled() {
}
}
+void URLRequestJob::NotifyRestartRequired() {
+ DCHECK(!has_handled_response_);
+ if (GetStatus().status() != URLRequestStatus::CANCELED)
+ request_->Restart();
+}
+
bool URLRequestJob::FilterHasData() {
return filter_.get() && filter_->stream_data_len();
}
diff --git a/net/url_request/url_request_job.h b/net/url_request/url_request_job.h
index 2289ce7..133af7e 100644
--- a/net/url_request/url_request_job.h
+++ b/net/url_request/url_request_job.h
@@ -227,6 +227,10 @@ class URLRequestJob : public base::RefCountedThreadSafe<URLRequestJob>,
// we were canceled.
void NotifyCanceled();
+ // Notifies the job the request should be restarted.
+ // Should only be called if the job has not started a resposne.
+ void NotifyRestartRequired();
+
// Called to get more data from the request response. Returns true if there
// is data immediately available to read. Return false otherwise.
// Internally this function may initiate I/O operations to get more data.
diff --git a/net/url_request/url_request_job_manager.cc b/net/url_request/url_request_job_manager.cc
index 9b550d1..bbc40e1 100644
--- a/net/url_request/url_request_job_manager.cc
+++ b/net/url_request/url_request_job_manager.cc
@@ -59,9 +59,8 @@ URLRequestJob* URLRequestJobManager::CreateJob(URLRequest* request) const {
if (!request->url().is_valid())
return new URLRequestErrorJob(request, net::ERR_INVALID_URL);
- const std::string& scheme = request->url().scheme(); // already lowercase
-
// We do this here to avoid asking interceptors about unsupported schemes.
+ const std::string& scheme = request->url().scheme(); // already lowercase
if (!SupportsScheme(scheme))
return new URLRequestErrorJob(request, net::ERR_UNKNOWN_URL_SCHEME);
@@ -104,6 +103,47 @@ URLRequestJob* URLRequestJobManager::CreateJob(URLRequest* request) const {
return new URLRequestErrorJob(request, net::ERR_FAILED);
}
+URLRequestJob* URLRequestJobManager::MaybeInterceptRedirect(
+ URLRequest* request,
+ const GURL& location) const {
+#ifndef NDEBUG
+ DCHECK(IsAllowedThread());
+#endif
+ if ((request->load_flags() & net::LOAD_DISABLE_INTERCEPT) ||
+ (request->status().status() == URLRequestStatus::CANCELED) ||
+ !request->url().is_valid() ||
+ !SupportsScheme(request->url().scheme()))
+ return NULL;
+
+ InterceptorList::const_iterator i;
+ for (i = interceptors_.begin(); i != interceptors_.end(); ++i) {
+ URLRequestJob* job = (*i)->MaybeInterceptRedirect(request, location);
+ if (job)
+ return job;
+ }
+ return NULL;
+}
+
+URLRequestJob* URLRequestJobManager::MaybeInterceptResponse(
+ URLRequest* request) const {
+#ifndef NDEBUG
+ DCHECK(IsAllowedThread());
+#endif
+ if ((request->load_flags() & net::LOAD_DISABLE_INTERCEPT) ||
+ (request->status().status() == URLRequestStatus::CANCELED) ||
+ !request->url().is_valid() ||
+ !SupportsScheme(request->url().scheme()))
+ return NULL;
+
+ InterceptorList::const_iterator i;
+ for (i = interceptors_.begin(); i != interceptors_.end(); ++i) {
+ URLRequestJob* job = (*i)->MaybeInterceptResponse(request);
+ if (job)
+ return job;
+ }
+ return NULL;
+}
+
bool URLRequestJobManager::SupportsScheme(const std::string& scheme) const {
// The set of registered factories may change on another thread.
{
diff --git a/net/url_request/url_request_job_manager.h b/net/url_request/url_request_job_manager.h
index 2dfbe13..9930e17 100644
--- a/net/url_request/url_request_job_manager.h
+++ b/net/url_request/url_request_job_manager.h
@@ -6,6 +6,7 @@
#define NET_URL_REQUEST_URL_REQUEST_JOB_MANAGER_H__
#include <map>
+#include <vector>
#include "base/lock.h"
#include "base/platform_thread.h"
@@ -31,6 +32,17 @@ class URLRequestJobManager {
// returning a job unless we are--in the extreme case--out of memory.
URLRequestJob* CreateJob(URLRequest* request) const;
+ // Allows interceptors to hijack the request after examining the new location
+ // of a redirect. Returns NULL if no interceptor intervenes.
+ URLRequestJob* MaybeInterceptRedirect(URLRequest* request,
+ const GURL& location) const;
+
+ // Allows interceptors to hijack the request after examining the response
+ // status and headers. This is also called when there is no server response
+ // at all to allow interception of failed requests due to network errors.
+ // Returns NULL if no interceptor intervenes.
+ URLRequestJob* MaybeInterceptResponse(URLRequest* request) const;
+
// Returns true if there is a protocol factory registered for the given
// scheme. Note: also returns true if there is a built-in handler for the
// given scheme.
diff --git a/net/url_request/url_request_test_job.cc b/net/url_request/url_request_test_job.cc
index d1db02d..4953b19 100644
--- a/net/url_request/url_request_test_job.cc
+++ b/net/url_request/url_request_test_job.cc
@@ -42,6 +42,32 @@ std::string URLRequestTestJob::test_data_3() {
return std::string("<html><title>Test Three Three Three</title></html>");
}
+// static getter for simple response headers
+std::string URLRequestTestJob::test_headers() {
+ const char headers[] =
+ "HTTP/1.1 200 OK\0"
+ "Content-type: text/html\0"
+ "\0";
+ return std::string(headers, arraysize(headers));
+}
+
+// static getter for redirect response headers
+std::string URLRequestTestJob::test_redirect_headers() {
+ const char headers[] =
+ "HTTP/1.1 302 MOVED\0"
+ "Location: somewhere\0"
+ "\0";
+ return std::string(headers, arraysize(headers));
+}
+
+// static getter for error response headers
+std::string URLRequestTestJob::test_error_headers() {
+ const char headers[] =
+ "HTTP/1.1 500 BOO HOO\0"
+ "\0";
+ return std::string(headers, arraysize(headers));
+}
+
// static
URLRequestJob* URLRequestTestJob::Factory(URLRequest* request,
const std::string& scheme) {
@@ -50,17 +76,44 @@ URLRequestJob* URLRequestTestJob::Factory(URLRequest* request,
URLRequestTestJob::URLRequestTestJob(URLRequest* request)
: URLRequestJob(request),
+ auto_advance_(false),
stage_(WAITING),
offset_(0),
async_buf_(NULL),
async_buf_size_(0) {
}
-// Force the response to set a reasonable MIME type
+URLRequestTestJob::URLRequestTestJob(URLRequest* request, bool auto_advance)
+ : URLRequestJob(request),
+ auto_advance_(auto_advance),
+ stage_(WAITING),
+ offset_(0),
+ async_buf_(NULL),
+ async_buf_size_(0) {
+}
+
+URLRequestTestJob::URLRequestTestJob(URLRequest* request,
+ const std::string& response_headers,
+ const std::string& response_data,
+ bool auto_advance)
+ : URLRequestJob(request),
+ auto_advance_(auto_advance),
+ stage_(WAITING),
+ response_headers_(new net::HttpResponseHeaders(response_headers)),
+ response_data_(response_data),
+ offset_(0),
+ async_buf_(NULL),
+ async_buf_size_(0) {
+}
+
+URLRequestTestJob::~URLRequestTestJob() {
+}
+
bool URLRequestTestJob::GetMimeType(std::string* mime_type) const {
DCHECK(mime_type);
- *mime_type = "text/html";
- return true;
+ if (!response_headers_)
+ return false;
+ return response_headers_->GetMimeType(mime_type);
}
void URLRequestTestJob::Start() {
@@ -71,25 +124,28 @@ void URLRequestTestJob::Start() {
}
void URLRequestTestJob::StartAsync() {
- if (request_->url().spec() == test_url_1().spec()) {
- data_ = test_data_1();
- stage_ = DATA_AVAILABLE; // Simulate a synchronous response for this one.
- } else if (request_->url().spec() == test_url_2().spec()) {
- data_ = test_data_2();
- } else if (request_->url().spec() == test_url_3().spec()) {
- data_ = test_data_3();
- } else {
- // unexpected url, return error
- // FIXME(brettw) we may want to use WININET errors or have some more types
- // of errors
- NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
- net::ERR_INVALID_URL));
- // FIXME(brettw): this should emulate a network error, and not just fail
- // initiating a connection
- return;
+ if (!response_headers_) {
+ response_headers_ = new net::HttpResponseHeaders(test_headers());
+ if (request_->url().spec() == test_url_1().spec()) {
+ response_data_ = test_data_1();
+ stage_ = DATA_AVAILABLE; // Simulate a synchronous response for this one.
+ } else if (request_->url().spec() == test_url_2().spec()) {
+ response_data_ = test_data_2();
+ } else if (request_->url().spec() == test_url_3().spec()) {
+ response_data_ = test_data_3();
+ } else {
+ // unexpected url, return error
+ // FIXME(brettw) we may want to use WININET errors or have some more types
+ // of errors
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
+ net::ERR_INVALID_URL));
+ // FIXME(brettw): this should emulate a network error, and not just fail
+ // initiating a connection
+ return;
+ }
}
- pending_jobs.push_back(scoped_refptr<URLRequestTestJob>(this));
+ AdvanceJob();
this->NotifyHeadersComplete();
}
@@ -106,15 +162,15 @@ bool URLRequestTestJob::ReadRawData(net::IOBuffer* buf, int buf_size,
DCHECK(bytes_read);
*bytes_read = 0;
- if (offset_ >= static_cast<int>(data_.length())) {
+ if (offset_ >= static_cast<int>(response_data_.length())) {
return true; // done reading
}
int to_read = buf_size;
- if (to_read + offset_ > static_cast<int>(data_.length()))
- to_read = static_cast<int>(data_.length()) - offset_;
+ if (to_read + offset_ > static_cast<int>(response_data_.length()))
+ to_read = static_cast<int>(response_data_.length()) - offset_;
- memcpy(buf->data(), &data_.c_str()[offset_], to_read);
+ memcpy(buf->data(), &response_data_.c_str()[offset_], to_read);
offset_ += to_read;
*bytes_read = to_read;
@@ -122,19 +178,31 @@ bool URLRequestTestJob::ReadRawData(net::IOBuffer* buf, int buf_size,
}
void URLRequestTestJob::GetResponseInfo(net::HttpResponseInfo* info) {
- const std::string kResponseHeaders = StringPrintf(
- "HTTP/1.1 200 OK%c"
- "Content-type: text/html%c"
- "%c", 0, 0, 0);
- info->headers = new net::HttpResponseHeaders(kResponseHeaders);
+ if (response_headers_)
+ info->headers = response_headers_;
+}
+
+bool URLRequestTestJob::IsRedirectResponse(GURL* location,
+ int* http_status_code) {
+ if (!response_headers_)
+ return false;
+
+ std::string value;
+ if (!response_headers_->IsRedirect(&value))
+ return false;
+
+ *location = request_->url().Resolve(value);
+ *http_status_code = response_headers_->response_code();
+ return true;
}
+
void URLRequestTestJob::Kill() {
stage_ = DONE;
URLRequestJob::Kill();
}
-bool URLRequestTestJob::ProcessNextOperation() {
+void URLRequestTestJob::ProcessNextOperation() {
switch (stage_) {
case WAITING:
stage_ = DATA_AVAILABLE;
@@ -152,14 +220,23 @@ bool URLRequestTestJob::ProcessNextOperation() {
break;
case ALL_DATA:
stage_ = DONE;
- return false;
+ return;
case DONE:
- return false;
+ return;
default:
NOTREACHED() << "Invalid stage";
- return false;
+ return;
}
- return true;
+ AdvanceJob();
+}
+
+void URLRequestTestJob::AdvanceJob() {
+ if (auto_advance_) {
+ MessageLoop::current()->PostTask(FROM_HERE, NewRunnableMethod(
+ this, &URLRequestTestJob::ProcessNextOperation));
+ return;
+ }
+ pending_jobs.push_back(scoped_refptr<URLRequestTestJob>(this));
}
// static
@@ -170,8 +247,7 @@ bool URLRequestTestJob::ProcessOnePendingMessage() {
scoped_refptr<URLRequestTestJob> next_job(pending_jobs[0]);
pending_jobs.erase(pending_jobs.begin());
- if (next_job->ProcessNextOperation())
- pending_jobs.push_back(next_job);
-
+ DCHECK(!next_job->auto_advance()); // auto_advance jobs should be in this q
+ next_job->ProcessNextOperation();
return true;
}
diff --git a/net/url_request/url_request_test_job.h b/net/url_request/url_request_test_job.h
index c5b9fae..bfde712a 100644
--- a/net/url_request/url_request_test_job.h
+++ b/net/url_request/url_request_test_job.h
@@ -22,32 +22,70 @@
//
// You can override the known URLs or the response data by overriding Start().
//
+// Optionally, you can also construct test jobs to return a headers and data
+// provided to the contstructor in response to any request url.
+//
// When a job is created, it gets put on a queue of pending test jobs. To
// process jobs on this queue, use ProcessOnePendingMessage, which will process
// one step of the next job. If the job is incomplete, it will be added to the
// end of the queue.
+//
+// Optionally, you can also construct test jobs that advance automatically
+// without having to call ProcessOnePendingMessage.
class URLRequestTestJob : public URLRequestJob {
public:
+ // Constructs a job to return one of the canned responses depending on the
+ // request url, with auto advance disabled.
explicit URLRequestTestJob(URLRequest* request);
- virtual ~URLRequestTestJob() {}
- // the three URLs this handler will respond to
+ // Constructs a job to return one of the canned responses depending on the
+ // request url, optionally with auto advance enabled.
+ explicit URLRequestTestJob(URLRequest* request, bool auto_advance);
+
+ // Constructs a job to return the given response regardless of the request
+ // url. The headers should include the HTTP status line and be formatted as
+ // expected by net::HttpResponseHeaders.
+ explicit URLRequestTestJob(URLRequest* request,
+ const std::string& response_headers,
+ const std::string& response_data,
+ bool auto_advance);
+
+ virtual ~URLRequestTestJob();
+
+ // The three canned URLs this handler will respond to without having been
+ // explicitly initialized with response headers and data.
// FIXME(brettw): we should probably also have a redirect one
static GURL test_url_1();
static GURL test_url_2();
static GURL test_url_3();
static GURL test_url_error();
- // the data that corresponds to each of the URLs above
+ // The data that corresponds to each of the URLs above
static std::string test_data_1();
static std::string test_data_2();
static std::string test_data_3();
+ // The headers that correspond to each of the URLs above
+ static std::string test_headers();
+
+ // The headers for a redirect response
+ static std::string test_redirect_headers();
+
+ // The headers for a server error response
+ static std::string test_error_headers();
+
// Processes one pending message from the stack, returning true if any
// message was processed, or false if there are no more pending request
- // notifications to send.
+ // notifications to send. This is not applicable when using auto_advance.
static bool ProcessOnePendingMessage();
+ // With auto advance enabled, the job will advance thru the stages without
+ // the caller having to call ProcessOnePendingMessage. Auto advance depends
+ // on having a message loop running. The default is to not auto advance.
+ // Should not be altered after the job has started.
+ bool auto_advance() { return auto_advance_; }
+ void set_auto_advance(bool auto_advance) { auto_advance_ = auto_advance; }
+
// Factory method for protocol factory registration if callers don't subclass
static URLRequest::ProtocolFactory Factory;
@@ -57,6 +95,7 @@ class URLRequestTestJob : public URLRequestJob {
virtual void Kill();
virtual bool GetMimeType(std::string* mime_type) const;
virtual void GetResponseInfo(net::HttpResponseInfo* info);
+ virtual bool IsRedirectResponse(GURL* location, int* http_status_code);
protected:
// This is what operation we are going to do next when this job is handled.
@@ -64,19 +103,28 @@ class URLRequestTestJob : public URLRequestJob {
enum Stage { WAITING, DATA_AVAILABLE, ALL_DATA, DONE };
// Call to process the next opeation, usually sending a notification, and
- // advancing the stage if necessary. THIS MAY DELETE THE OBJECT, we will
- // return false if the operations are complete, true if there are more.
- bool ProcessNextOperation();
+ // advancing the stage if necessary. THIS MAY DELETE THE OBJECT.
+ void ProcessNextOperation();
+
+ // Call to move the job along to the next operation.
+ void AdvanceJob();
// Called via InvokeLater to cause callbacks to occur after Start() returns.
- void StartAsync();
+ virtual void StartAsync();
+
+ bool auto_advance_;
Stage stage_;
- // The data to send, will be set in Start()
- std::string data_;
+ // The headers the job should return, will be set in Start() if not provided
+ // in the explicit ctor.
+ scoped_refptr<net::HttpResponseHeaders> response_headers_;
+
+ // The data to send, will be set in Start() if not provided in the explicit
+ // ctor.
+ std::string response_data_;
- // current offset within data_
+ // current offset within response_data_
int offset_;
// Holds the buffer for an asynchronous ReadRawData call
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc
index 372ff43..86b5b9c 100644
--- a/net/url_request/url_request_unittest.cc
+++ b/net/url_request/url_request_unittest.cc
@@ -34,6 +34,7 @@
#include "net/http/http_response_headers.h"
#include "net/proxy/proxy_service.h"
#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_test_job.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
@@ -1111,6 +1112,452 @@ TEST_F(URLRequestTest, Post307RedirectPost) {
EXPECT_EQ(req.method(), "POST");
}
+// Custom URLRequestJobs for use with interceptor tests
+class RestartTestJob : public URLRequestTestJob {
+ public:
+ RestartTestJob(URLRequest* request) : URLRequestTestJob(request, true) {}
+ protected:
+ virtual void StartAsync() {
+ this->NotifyRestartRequired();
+ }
+};
+
+class CancelTestJob : public URLRequestTestJob {
+ public:
+ CancelTestJob(URLRequest* request) : URLRequestTestJob(request, true) {}
+ protected:
+ virtual void StartAsync() {
+ request_->Cancel();
+ }
+};
+
+class CancelThenRestartTestJob : public URLRequestTestJob {
+ public:
+ CancelThenRestartTestJob(URLRequest* request)
+ : URLRequestTestJob(request, true) {
+ }
+ protected:
+ virtual void StartAsync() {
+ request_->Cancel();
+ this->NotifyRestartRequired();
+ }
+};
+
+// An Interceptor for use with interceptor tests
+class TestInterceptor : URLRequest::Interceptor {
+ public:
+ TestInterceptor()
+ : intercept_main_request_(false), restart_main_request_(false),
+ cancel_main_request_(false), cancel_then_restart_main_request_(false),
+ simulate_main_network_error_(false),
+ intercept_redirect_(false), cancel_redirect_request_(false),
+ intercept_final_response_(false), cancel_final_request_(false),
+ did_intercept_main_(false), did_restart_main_(false),
+ did_cancel_main_(false), did_cancel_then_restart_main_(false),
+ did_simulate_error_main_(false),
+ did_intercept_redirect_(false), did_cancel_redirect_(false),
+ did_intercept_final_(false), did_cancel_final_(false) {
+ URLRequest::RegisterRequestInterceptor(this);
+ }
+
+ ~TestInterceptor() {
+ URLRequest::UnregisterRequestInterceptor(this);
+ }
+
+ virtual URLRequestJob* MaybeIntercept(URLRequest* request) {
+ if (restart_main_request_) {
+ restart_main_request_ = false;
+ did_restart_main_ = true;
+ return new RestartTestJob(request);
+ }
+ if (cancel_main_request_) {
+ cancel_main_request_ = false;
+ did_cancel_main_ = true;
+ return new CancelTestJob(request);
+ }
+ if (cancel_then_restart_main_request_) {
+ cancel_then_restart_main_request_ = false;
+ did_cancel_then_restart_main_ = true;
+ return new CancelThenRestartTestJob(request);
+ }
+ if (simulate_main_network_error_) {
+ simulate_main_network_error_ = false;
+ did_simulate_error_main_ = true;
+ // will error since the requeted url is not one of its canned urls
+ return new URLRequestTestJob(request, true);
+ }
+ if (!intercept_main_request_)
+ return NULL;
+ intercept_main_request_ = false;
+ did_intercept_main_ = true;
+ return new URLRequestTestJob(request,
+ main_headers_,
+ main_data_,
+ true);
+ }
+
+ virtual URLRequestJob* MaybeInterceptRedirect(URLRequest* request,
+ const GURL& location) {
+ if (cancel_redirect_request_) {
+ cancel_redirect_request_ = false;
+ did_cancel_redirect_ = true;
+ return new CancelTestJob(request);
+ }
+ if (!intercept_redirect_)
+ return NULL;
+ intercept_redirect_ = false;
+ did_intercept_redirect_ = true;
+ return new URLRequestTestJob(request,
+ redirect_headers_,
+ redirect_data_,
+ true);
+ }
+
+ virtual URLRequestJob* MaybeInterceptResponse(URLRequest* request) {
+ if (cancel_final_request_) {
+ cancel_final_request_ = false;
+ did_cancel_final_ = true;
+ return new CancelTestJob(request);
+ }
+ if (!intercept_final_response_)
+ return NULL;
+ intercept_final_response_ = false;
+ did_intercept_final_ = true;
+ return new URLRequestTestJob(request,
+ final_headers_,
+ final_data_,
+ true);
+ }
+
+ // Whether to intercept the main request, and if so the response to return.
+ bool intercept_main_request_;
+ std::string main_headers_;
+ std::string main_data_;
+
+ // Other actions we take at MaybeIntercept time
+ bool restart_main_request_;
+ bool cancel_main_request_;
+ bool cancel_then_restart_main_request_;
+ bool simulate_main_network_error_;
+
+ // Whether to intercept redirects, and if so the response to return.
+ bool intercept_redirect_;
+ std::string redirect_headers_;
+ std::string redirect_data_;
+
+ // Other actions we can take at MaybeInterceptRedirect time
+ bool cancel_redirect_request_;
+
+ // Whether to intercept final response, and if so the response to return.
+ bool intercept_final_response_;
+ std::string final_headers_;
+ std::string final_data_;
+
+ // Other actions we can take at MaybeInterceptResponse time
+ bool cancel_final_request_;
+
+ // If we did something or not
+ bool did_intercept_main_;
+ bool did_restart_main_;
+ bool did_cancel_main_;
+ bool did_cancel_then_restart_main_;
+ bool did_simulate_error_main_;
+ bool did_intercept_redirect_;
+ bool did_cancel_redirect_;
+ bool did_intercept_final_;
+ bool did_cancel_final_;
+
+ // Static getters for canned response header and data strings
+
+ static std::string ok_data() {
+ return URLRequestTestJob::test_data_1();
+ }
+
+ static std::string ok_headers() {
+ return URLRequestTestJob::test_headers();
+ }
+
+ static std::string redirect_data() {
+ return std::string();
+ }
+
+ static std::string redirect_headers() {
+ return URLRequestTestJob::test_redirect_headers();
+ }
+
+ static std::string error_data() {
+ return std::string("ohhh nooooo mr. bill!");
+ }
+
+ static std::string error_headers() {
+ return URLRequestTestJob::test_error_headers();
+ }
+};
+
+TEST_F(URLRequestTest, Intercept) {
+ TestInterceptor interceptor;
+
+ // intercept the main request and respond with a simple response
+ interceptor.intercept_main_request_ = true;
+ interceptor.main_headers_ = TestInterceptor::ok_headers();
+ interceptor.main_data_ = TestInterceptor::ok_data();
+
+ TestDelegate d;
+ TestURLRequest req(GURL("http://test_intercept/foo"), &d);
+ URLRequest::UserData* user_data0 = new URLRequest::UserData();
+ URLRequest::UserData* user_data1 = new URLRequest::UserData();
+ URLRequest::UserData* user_data2 = new URLRequest::UserData();
+ req.SetUserData(NULL, user_data0);
+ req.SetUserData(&user_data1, user_data1);
+ req.SetUserData(&user_data2, user_data2);
+ req.set_method("GET");
+ req.Start();
+ MessageLoop::current()->Run();
+
+ // Make sure we can retrieve our specific user data
+ EXPECT_EQ(user_data0, req.GetUserData(NULL));
+ EXPECT_EQ(user_data1, req.GetUserData(&user_data1));
+ EXPECT_EQ(user_data2, req.GetUserData(&user_data2));
+
+ // Check the interceptor got called as expected
+ EXPECT_TRUE(interceptor.did_intercept_main_);
+
+ // Check we got one good response
+ EXPECT_TRUE(req.status().is_success());
+ EXPECT_EQ(200, req.response_headers()->response_code());
+ EXPECT_EQ(TestInterceptor::ok_data(), d.data_received());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(0, d.received_redirect_count());
+}
+
+TEST_F(URLRequestTest, InterceptRedirect) {
+ TestInterceptor interceptor;
+
+ // intercept the main request and respond with a redirect
+ interceptor.intercept_main_request_ = true;
+ interceptor.main_headers_ = TestInterceptor::redirect_headers();
+ interceptor.main_data_ = TestInterceptor::redirect_data();
+
+ // intercept that redirect and respond a final OK response
+ interceptor.intercept_redirect_ = true;
+ interceptor.redirect_headers_ = TestInterceptor::ok_headers();
+ interceptor.redirect_data_ = TestInterceptor::ok_data();
+
+ TestDelegate d;
+ TestURLRequest req(GURL("http://test_intercept/foo"), &d);
+ req.set_method("GET");
+ req.Start();
+ MessageLoop::current()->Run();
+
+ // Check the interceptor got called as expected
+ EXPECT_TRUE(interceptor.did_intercept_main_);
+ EXPECT_TRUE(interceptor.did_intercept_redirect_);
+
+ // Check we got one good response
+ EXPECT_TRUE(req.status().is_success());
+ EXPECT_EQ(200, req.response_headers()->response_code());
+ EXPECT_EQ(TestInterceptor::ok_data(), d.data_received());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(0, d.received_redirect_count());
+}
+
+TEST_F(URLRequestTest, InterceptServerError) {
+ TestInterceptor interceptor;
+
+ // intercept the main request to generate a server error response
+ interceptor.intercept_main_request_ = true;
+ interceptor.main_headers_ = TestInterceptor::error_headers();
+ interceptor.main_data_ = TestInterceptor::error_data();
+
+ // intercept that error and respond with an OK response
+ interceptor.intercept_final_response_ = true;
+ interceptor.final_headers_ = TestInterceptor::ok_headers();
+ interceptor.final_data_ = TestInterceptor::ok_data();
+
+ TestDelegate d;
+ TestURLRequest req(GURL("http://test_intercept/foo"), &d);
+ req.set_method("GET");
+ req.Start();
+ MessageLoop::current()->Run();
+
+ // Check the interceptor got called as expected
+ EXPECT_TRUE(interceptor.did_intercept_main_);
+ EXPECT_TRUE(interceptor.did_intercept_final_);
+
+ // Check we got one good response
+ EXPECT_TRUE(req.status().is_success());
+ EXPECT_EQ(200, req.response_headers()->response_code());
+ EXPECT_EQ(TestInterceptor::ok_data(), d.data_received());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(0, d.received_redirect_count());
+}
+
+TEST_F(URLRequestTest, InterceptNetworkError) {
+ TestInterceptor interceptor;
+
+ // intercept the main request to simulate a network error
+ interceptor.simulate_main_network_error_ = true;
+
+ // intercept that error and respond with an OK response
+ interceptor.intercept_final_response_ = true;
+ interceptor.final_headers_ = TestInterceptor::ok_headers();
+ interceptor.final_data_ = TestInterceptor::ok_data();
+
+ TestDelegate d;
+ TestURLRequest req(GURL("http://test_intercept/foo"), &d);
+ req.set_method("GET");
+ req.Start();
+ MessageLoop::current()->Run();
+
+ // Check the interceptor got called as expected
+ EXPECT_TRUE(interceptor.did_simulate_error_main_);
+ EXPECT_TRUE(interceptor.did_intercept_final_);
+
+ // Check we received one good response
+ EXPECT_TRUE(req.status().is_success());
+ EXPECT_EQ(200, req.response_headers()->response_code());
+ EXPECT_EQ(TestInterceptor::ok_data(), d.data_received());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(0, d.received_redirect_count());
+}
+
+TEST_F(URLRequestTest, InterceptRestartRequired) {
+ TestInterceptor interceptor;
+
+ // restart the main request
+ interceptor.restart_main_request_ = true;
+
+ // then intercept the new main request and respond with an OK response
+ interceptor.intercept_main_request_ = true;
+ interceptor.main_headers_ = TestInterceptor::ok_headers();
+ interceptor.main_data_ = TestInterceptor::ok_data();
+
+ TestDelegate d;
+ TestURLRequest req(GURL("http://test_intercept/foo"), &d);
+ req.set_method("GET");
+ req.Start();
+ MessageLoop::current()->Run();
+
+ // Check the interceptor got called as expected
+ EXPECT_TRUE(interceptor.did_restart_main_);
+ EXPECT_TRUE(interceptor.did_intercept_main_);
+
+ // Check we received one good response
+ EXPECT_TRUE(req.status().is_success());
+ EXPECT_EQ(200, req.response_headers()->response_code());
+ EXPECT_EQ(TestInterceptor::ok_data(), d.data_received());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(0, d.received_redirect_count());
+}
+
+TEST_F(URLRequestTest, InterceptRespectsCancelMain) {
+ TestInterceptor interceptor;
+
+ // intercept the main request and cancel from within the restarted job
+ interceptor.cancel_main_request_ = true;
+
+ // setup to intercept final response and override it with an OK response
+ interceptor.intercept_final_response_ = true;
+ interceptor.final_headers_ = TestInterceptor::ok_headers();
+ interceptor.final_data_ = TestInterceptor::ok_data();
+
+ TestDelegate d;
+ TestURLRequest req(GURL("http://test_intercept/foo"), &d);
+ req.set_method("GET");
+ req.Start();
+ MessageLoop::current()->Run();
+
+ // Check the interceptor got called as expected
+ EXPECT_TRUE(interceptor.did_cancel_main_);
+ EXPECT_FALSE(interceptor.did_intercept_final_);
+
+ // Check we see a canceled request
+ EXPECT_FALSE(req.status().is_success());
+ EXPECT_EQ(URLRequestStatus::CANCELED, req.status().status());
+}
+
+TEST_F(URLRequestTest, InterceptRespectsCancelRedirect) {
+ TestInterceptor interceptor;
+
+ // intercept the main request and respond with a redirect
+ interceptor.intercept_main_request_ = true;
+ interceptor.main_headers_ = TestInterceptor::redirect_headers();
+ interceptor.main_data_ = TestInterceptor::redirect_data();
+
+ // intercept the redirect and cancel from within that job
+ interceptor.cancel_redirect_request_ = true;
+
+ // setup to intercept final response and override it with an OK response
+ interceptor.intercept_final_response_ = true;
+ interceptor.final_headers_ = TestInterceptor::ok_headers();
+ interceptor.final_data_ = TestInterceptor::ok_data();
+
+ TestDelegate d;
+ TestURLRequest req(GURL("http://test_intercept/foo"), &d);
+ req.set_method("GET");
+ req.Start();
+ MessageLoop::current()->Run();
+
+ // Check the interceptor got called as expected
+ EXPECT_TRUE(interceptor.did_intercept_main_);
+ EXPECT_TRUE(interceptor.did_cancel_redirect_);
+ EXPECT_FALSE(interceptor.did_intercept_final_);
+
+ // Check we see a canceled request
+ EXPECT_FALSE(req.status().is_success());
+ EXPECT_EQ(URLRequestStatus::CANCELED, req.status().status());
+}
+
+TEST_F(URLRequestTest, InterceptRespectsCancelFinal) {
+ TestInterceptor interceptor;
+
+ // intercept the main request to simulate a network error
+ interceptor.simulate_main_network_error_ = true;
+
+ // setup to intercept final response and cancel from within that job
+ interceptor.cancel_final_request_ = true;
+
+ TestDelegate d;
+ TestURLRequest req(GURL("http://test_intercept/foo"), &d);
+ req.set_method("GET");
+ req.Start();
+ MessageLoop::current()->Run();
+
+ // Check the interceptor got called as expected
+ EXPECT_TRUE(interceptor.did_simulate_error_main_);
+ EXPECT_TRUE(interceptor.did_cancel_final_);
+
+ // Check we see a canceled request
+ EXPECT_FALSE(req.status().is_success());
+ EXPECT_EQ(URLRequestStatus::CANCELED, req.status().status());
+}
+
+TEST_F(URLRequestTest, InterceptRespectsCancelInRestart) {
+ TestInterceptor interceptor;
+
+ // intercept the main request and cancel then restart from within that job
+ interceptor.cancel_then_restart_main_request_ = true;
+
+ // setup to intercept final response and override it with an OK response
+ interceptor.intercept_final_response_ = true;
+ interceptor.final_headers_ = TestInterceptor::ok_headers();
+ interceptor.final_data_ = TestInterceptor::ok_data();
+
+ TestDelegate d;
+ TestURLRequest req(GURL("http://test_intercept/foo"), &d);
+ req.set_method("GET");
+ req.Start();
+ MessageLoop::current()->Run();
+
+ // Check the interceptor got called as expected
+ EXPECT_TRUE(interceptor.did_cancel_then_restart_main_);
+ EXPECT_FALSE(interceptor.did_intercept_final_);
+
+ // Check we see a canceled request
+ EXPECT_FALSE(req.status().is_success());
+ EXPECT_EQ(URLRequestStatus::CANCELED, req.status().status());
+}
+
// FTP tests appear to be hanging some of the time
#if 1 // !defined(OS_WIN)
#define MAYBE_FTPGetTestAnonymous DISABLED_FTPGetTestAnonymous