Skip to content

Commit

Permalink
[android_webview] Make intercepted URLRequests have status codes.
Browse files Browse the repository at this point in the history
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
  • Loading branch information
mkosiba@chromium.org committed Mar 11, 2013
1 parent 83e518d commit a7003bb
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand All @@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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));
}
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -24,6 +26,7 @@ class TaskRunner;
}

namespace net {
class HttpResponseInfo;
class URLRequest;
}

Expand Down Expand Up @@ -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();
Expand All @@ -95,13 +100,16 @@ 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);
void OnReaderSeekCompleted(int content_size);
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_;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {}
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,34 @@ public void testDoesNotCrashOnEmptyStream() throws Throwable {
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>",
Expand Down

0 comments on commit a7003bb

Please sign in to comment.