diff options
author | michaeln@google.com <michaeln@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-04-16 21:05:47 +0000 |
---|---|---|
committer | michaeln@google.com <michaeln@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-04-16 21:05:47 +0000 |
commit | a5c713fdc8d0bf21f65ebdae95827e423d14b07c (patch) | |
tree | 82e553e97f38aa159978805e415fa683189201e0 /net | |
parent | 2cb1ecb7aa5b6bf5c17cfaa75293121c8bd73130 (diff) | |
download | chromium_src-a5c713fdc8d0bf21f65ebdae95827e423d14b07c.zip chromium_src-a5c713fdc8d0bf21f65ebdae95827e423d14b07c.tar.gz chromium_src-a5c713fdc8d0bf21f65ebdae95827e423d14b07c.tar.bz2 |
URLRequest::Interceptor enhancements1) Allow an interceptor to change its mind and not intercept after all. This allows the decision to start or not to start to be made asynchronously.2) Allow an interceptor to intercept on error conditions if the original job fails. This is to support the FALLBACK semantics in the appcache.Info about where this is going can be found in the appcache design doc at https://docs.google.com/a/google.com/Doc?docid=agv6ghfsqr_15f749cgt3&hl=enI still have to put together test cases, so I'm not ready to submit this yet, but wanted to get some feedback at this point.
Review URL: http://codereview.chromium.org/67019
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@13877 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-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 |
9 files changed, 786 insertions, 74 deletions
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 |