diff options
author | mkosiba@chromium.org <mkosiba@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-11 10:46:53 +0000 |
---|---|---|
committer | mkosiba@chromium.org <mkosiba@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-11 10:46:53 +0000 |
commit | a7003bb3a86e01875c2437608e5e0a6b0252dad9 (patch) | |
tree | c8dfa6d8f1ac03a9eb633b390d93c594d2663bbf | |
parent | 83e518d3bad1f95318f1e2a1ef3260843ff1c935 (diff) | |
download | chromium_src-a7003bb3a86e01875c2437608e5e0a6b0252dad9.zip chromium_src-a7003bb3a86e01875c2437608e5e0a6b0252dad9.tar.gz chromium_src-a7003bb3a86e01875c2437608e5e0a6b0252dad9.tar.bz2 |
[android_webview] Make intercepted URLRequests have status codes.
This changes the way AndroidStreamReaderUrlRequestJob handles requests which
don't have an InputStream. Rather than immediately failing the Job we pretend
to have HTTP response headers that say 404 Not Found and empty contents (much
like fetching the data from a real server would).
BUG=180542
Review URL: https://chromiumcodereview.appspot.com/12531002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@187270 0039d316-1c4b-4281-b951-d872f2087c98
4 files changed, 151 insertions, 6 deletions
diff --git a/android_webview/browser/net/android_stream_reader_url_request_job.cc b/android_webview/browser/net/android_stream_reader_url_request_job.cc index 835758b..c2c4783 100644 --- a/android_webview/browser/net/android_stream_reader_url_request_job.cc +++ b/android_webview/browser/net/android_stream_reader_url_request_job.cc @@ -4,6 +4,8 @@ #include "android_webview/browser/net/android_stream_reader_url_request_job.h" +#include <string> + #include "android_webview/browser/input_stream.h" #include "android_webview/browser/net/input_stream_reader.h" #include "base/android/jni_android.h" @@ -13,6 +15,7 @@ #include "base/lazy_instance.h" #include "base/message_loop.h" #include "base/message_loop_proxy.h" +#include "base/strings/string_number_conversions.h" #include "base/task_runner.h" #include "base/threading/sequenced_worker_pool.h" #include "base/threading/thread.h" @@ -21,6 +24,8 @@ #include "net/base/mime_util.h" #include "net/base/net_errors.h" #include "net/base/net_util.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_response_info.h" #include "net/http/http_util.h" #include "net/url_request/url_request.h" #include "net/url_request/url_request_job_manager.h" @@ -31,6 +36,16 @@ using base::android::AttachCurrentThread; using base::PostTaskAndReplyWithResult; using content::BrowserThread; +namespace { + +const int kHTTPOk = 200; +const int kHTTPNotFound = 404; + +const char kHTTPOkText[] = "OK"; +const char kHTTPNotFoundText[] = "Not Found"; + +} // namespace + // The requests posted to the worker thread might outlive the job. Thread-safe // ref counting is used to ensure that the InputStream and InputStreamReader // members of this class are still there when the closure is run on the worker @@ -155,8 +170,9 @@ void AndroidStreamReaderURLRequestJob::OnInputStreamOpened( if (restart_required) { NotifyRestartRequired(); } else { - NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, - net::ERR_FAILED)); + // Clear the IO_PENDING status set in Start(). + SetStatus(net::URLRequestStatus()); + HeadersComplete(kHTTPNotFound, kHTTPNotFoundText); } return; } @@ -185,7 +201,7 @@ void AndroidStreamReaderURLRequestJob::OnReaderSeekCompleted(int result) { SetStatus(net::URLRequestStatus()); if (result >= 0) { set_expected_content_size(result); - NotifyHeadersComplete(); + HeadersComplete(kHTTPOk, kHTTPOkText); } else { NotifyDone(net::URLRequestStatus(net::URLRequestStatus::FAILED, result)); } @@ -220,7 +236,13 @@ bool AndroidStreamReaderURLRequestJob::ReadRawData(net::IOBuffer* dest, int dest_size, int* bytes_read) { DCHECK(thread_checker_.CalledOnValidThread()); - DCHECK(input_stream_reader_wrapper_); + if (!input_stream_reader_wrapper_) { + // This will happen if opening the InputStream fails in which case the + // error is communicated by setting the HTTP response status header rather + // than failing the request during the header fetch phase. + *bytes_read = 0; + return true; + } PostTaskAndReplyWithResult( GetWorkerThreadRunner(), @@ -270,6 +292,54 @@ bool AndroidStreamReaderURLRequestJob::GetCharset(std::string* charset) { env, request(), input_stream_reader_wrapper_->input_stream(), charset); } +void AndroidStreamReaderURLRequestJob::HeadersComplete( + int status_code, + const std::string& status_text) { + std::string status("HTTP/1.1 "); + status.append(base::IntToString(status_code)); + status.append(" "); + status.append(status_text); + // HttpResponseHeaders expects its input string to be terminated by two NULs. + status.append("\0\0", 2); + net::HttpResponseHeaders* headers = new net::HttpResponseHeaders(status); + + if (status_code == kHTTPOk) { + if (expected_content_size() != -1) { + std::string content_length_header( + net::HttpRequestHeaders::kContentLength); + content_length_header.append(": "); + content_length_header.append( + base::Int64ToString(expected_content_size())); + headers->AddHeader(content_length_header); + } + + std::string mime_type; + if (GetMimeType(&mime_type) && !mime_type.empty()) { + std::string content_type_header(net::HttpRequestHeaders::kContentType); + content_type_header.append(": "); + content_type_header.append(mime_type); + headers->AddHeader(content_type_header); + } + } + + response_info_.reset(new net::HttpResponseInfo()); + response_info_->headers = headers; + + NotifyHeadersComplete(); +} + +int AndroidStreamReaderURLRequestJob::GetResponseCode() const { + if (response_info_) + return response_info_->headers->response_code(); + return URLRequestJob::GetResponseCode(); +} + +void AndroidStreamReaderURLRequestJob::GetResponseInfo( + net::HttpResponseInfo* info) { + if (response_info_) + *info = *response_info_; +} + void AndroidStreamReaderURLRequestJob::SetExtraRequestHeaders( const net::HttpRequestHeaders& headers) { std::string range_header; diff --git a/android_webview/browser/net/android_stream_reader_url_request_job.h b/android_webview/browser/net/android_stream_reader_url_request_job.h index 0cb0bd3..c516b76 100644 --- a/android_webview/browser/net/android_stream_reader_url_request_job.h +++ b/android_webview/browser/net/android_stream_reader_url_request_job.h @@ -5,6 +5,8 @@ #ifndef ANDROID_WEBVIEW_NATIVE_ANDROID_STREAM_READER_URL_REQUEST_JOB_H_ #define ANDROID_WEBVIEW_NATIVE_ANDROID_STREAM_READER_URL_REQUEST_JOB_H_ +#include <string> + #include "base/android/scoped_java_ref.h" #include "base/location.h" #include "base/memory/ref_counted.h" @@ -24,6 +26,7 @@ class TaskRunner; } namespace net { +class HttpResponseInfo; class URLRequest; } @@ -81,6 +84,8 @@ class AndroidStreamReaderURLRequestJob : public net::URLRequestJob { const net::HttpRequestHeaders& headers) OVERRIDE; virtual bool GetMimeType(std::string* mime_type) const OVERRIDE; virtual bool GetCharset(std::string* charset) OVERRIDE; + virtual int GetResponseCode() const OVERRIDE; + virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE; protected: virtual ~AndroidStreamReaderURLRequestJob(); @@ -95,6 +100,8 @@ class AndroidStreamReaderURLRequestJob : public net::URLRequestJob { CreateStreamReader(android_webview::InputStream* stream); private: + void HeadersComplete(int status_code, const std::string& status_text); + void OnInputStreamOpened( scoped_ptr<Delegate> delegate, scoped_ptr<android_webview::InputStream> input_stream); @@ -102,6 +109,7 @@ class AndroidStreamReaderURLRequestJob : public net::URLRequestJob { void OnReaderReadCompleted(int bytes_read); net::HttpByteRange byte_range_; + scoped_ptr<net::HttpResponseInfo> response_info_; scoped_ptr<Delegate> delegate_; scoped_refptr<InputStreamReaderWrapper> input_stream_reader_wrapper_; base::WeakPtrFactory<AndroidStreamReaderURLRequestJob> weak_factory_; diff --git a/android_webview/browser/net/android_stream_reader_url_request_job_unittest.cc b/android_webview/browser/net/android_stream_reader_url_request_job_unittest.cc index df7931d..027615c 100644 --- a/android_webview/browser/net/android_stream_reader_url_request_job_unittest.cc +++ b/android_webview/browser/net/android_stream_reader_url_request_job_unittest.cc @@ -95,6 +95,17 @@ class StreamReaderDelegate : } }; +class NullStreamReaderDelegate : public StreamReaderDelegate { + public: + NullStreamReaderDelegate() {} + + virtual scoped_ptr<InputStream> OpenInputStream( + JNIEnv* env, + const GURL& url) { + return make_scoped_ptr<InputStream>(NULL); + } +}; + class MockInputStreamReader : public InputStreamReader { public: MockInputStreamReader() : InputStreamReader(new NotImplInputStream()) {} @@ -160,8 +171,14 @@ class AndroidStreamReaderURLRequestJobTest : public Test { } void SetUpTestJob(scoped_ptr<InputStreamReader> stream_reader) { - scoped_ptr<AndroidStreamReaderURLRequestJob::Delegate> - stream_reader_delegate(new StreamReaderDelegate()); + SetUpTestJob(stream_reader.Pass(), + make_scoped_ptr(new StreamReaderDelegate()) + .PassAs<AndroidStreamReaderURLRequestJob::Delegate>()); + } + + void SetUpTestJob(scoped_ptr<InputStreamReader> stream_reader, + scoped_ptr<AndroidStreamReaderURLRequestJob::Delegate> + stream_reader_delegate) { TestStreamReaderJob* test_stream_reader_job = new TestStreamReaderJob( req_.get(), @@ -208,6 +225,26 @@ TEST_F(AndroidStreamReaderURLRequestJobTest, ReadEmptyStream) { EXPECT_FALSE(url_request_delegate_.request_failed()); EXPECT_EQ(1, network_delegate_.completed_requests()); + EXPECT_EQ(0, network_delegate_.error_count()); + EXPECT_EQ(200, req_->GetResponseCode()); +} + +TEST_F(AndroidStreamReaderURLRequestJobTest, ReadWithNullStream) { + SetUpTestJob(scoped_ptr<InputStreamReader>(), + make_scoped_ptr(new NullStreamReaderDelegate()) + .PassAs<AndroidStreamReaderURLRequestJob::Delegate>()); + req_->Start(); + + // The TestDelegate will quit the message loop on request completion. + MessageLoop::current()->Run(); + + // The request_failed() method is named confusingly but all it checks is + // whether the request got as far as calling NotifyHeadersComplete. + EXPECT_FALSE(url_request_delegate_.request_failed()); + EXPECT_EQ(1, network_delegate_.completed_requests()); + // A null input stream shouldn't result in an error. See crbug.com/180950. + EXPECT_EQ(0, network_delegate_.error_count()); + EXPECT_EQ(404, req_->GetResponseCode()); } TEST_F(AndroidStreamReaderURLRequestJobTest, ReadPartOfStream) { @@ -238,6 +275,7 @@ TEST_F(AndroidStreamReaderURLRequestJobTest, ReadPartOfStream) { EXPECT_FALSE(url_request_delegate_.request_failed()); EXPECT_EQ(bytes_to_read, url_request_delegate_.bytes_received()); EXPECT_EQ(1, network_delegate_.completed_requests()); + EXPECT_EQ(0, network_delegate_.error_count()); } TEST_F(AndroidStreamReaderURLRequestJobTest, @@ -268,6 +306,7 @@ TEST_F(AndroidStreamReaderURLRequestJobTest, EXPECT_FALSE(url_request_delegate_.request_failed()); EXPECT_EQ(bytes_to_read, url_request_delegate_.bytes_received()); EXPECT_EQ(1, network_delegate_.completed_requests()); + EXPECT_EQ(0, network_delegate_.error_count()); } TEST_F(AndroidStreamReaderURLRequestJobTest, DeleteJobMidWaySeek) { diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldInterceptRequestTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldInterceptRequestTest.java index d000ddf..d7fefd4 100644 --- a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldInterceptRequestTest.java +++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldInterceptRequestTest.java @@ -246,6 +246,34 @@ public class AwContentsClientShouldInterceptRequestTest extends AndroidWebViewTe mContentsClient.getOnPageFinishedHelper().waitForCallback(onPageFinishedCallCount); } + @SmallTest + @Feature({"AndroidWebView"}) + public void testHttpStatusField() throws Throwable { + final String syncGetUrl = mWebServer.getResponseUrl("/intercept_me"); + final String syncGetJs = + "(function() {" + + " var xhr = new XMLHttpRequest();" + + " xhr.open('GET', '" + syncGetUrl + "', false);" + + " xhr.send(null);" + + " console.info('xhr.status = ' + xhr.status);" + + " return xhr.status;" + + "})();"; + enableJavaScriptOnUiThread(mAwContents); + + final String aboutPageUrl = addAboutPageToTestServer(mWebServer); + loadUrlSync(mAwContents, mContentsClient.getOnPageFinishedHelper(), aboutPageUrl); + + mShouldInterceptRequestHelper.setReturnValue( + new InterceptedRequestData("text/html", "UTF-8", null)); + assertEquals("404", + executeJavaScriptAndWaitForResult(mAwContents, mContentsClient, syncGetJs)); + + mShouldInterceptRequestHelper.setReturnValue( + new InterceptedRequestData("text/html", "UTF-8", new EmptyInputStream())); + assertEquals("200", + executeJavaScriptAndWaitForResult(mAwContents, mContentsClient, syncGetJs)); + } + private String makePageWithTitle(String title) { return CommonResources.makeHtmlPageFrom("<title>" + title + "</title>", |