summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormkosiba@chromium.org <mkosiba@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-11 10:46:53 +0000
committermkosiba@chromium.org <mkosiba@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-11 10:46:53 +0000
commita7003bb3a86e01875c2437608e5e0a6b0252dad9 (patch)
treec8dfa6d8f1ac03a9eb633b390d93c594d2663bbf
parent83e518d3bad1f95318f1e2a1ef3260843ff1c935 (diff)
downloadchromium_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
-rw-r--r--android_webview/browser/net/android_stream_reader_url_request_job.cc78
-rw-r--r--android_webview/browser/net/android_stream_reader_url_request_job.h8
-rw-r--r--android_webview/browser/net/android_stream_reader_url_request_job_unittest.cc43
-rw-r--r--android_webview/javatests/src/org/chromium/android_webview/test/AwContentsClientShouldInterceptRequestTest.java28
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>",