diff options
-rw-r--r-- | chrome/browser/renderer_host/resource_dispatcher_host.cc | 6 | ||||
-rw-r--r-- | chrome/browser/renderer_host/resource_dispatcher_host.h | 21 | ||||
-rw-r--r-- | net/url_request/url_request.cc | 52 | ||||
-rw-r--r-- | net/url_request/url_request.h | 64 | ||||
-rw-r--r-- | net/url_request/url_request_job.cc | 17 | ||||
-rw-r--r-- | net/url_request/url_request_job.h | 4 | ||||
-rw-r--r-- | net/url_request/url_request_job_manager.cc | 44 | ||||
-rw-r--r-- | net/url_request/url_request_job_manager.h | 12 | ||||
-rw-r--r-- | net/url_request/url_request_test_job.cc | 150 | ||||
-rw-r--r-- | net/url_request/url_request_test_job.h | 70 | ||||
-rw-r--r-- | net/url_request/url_request_unittest.cc | 447 |
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 |