diff options
author | mmenke@chromium.org <mmenke@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-23 18:10:54 +0000 |
---|---|---|
committer | mmenke@chromium.org <mmenke@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-23 18:10:54 +0000 |
commit | 02d74a036e548de4b9e81e4c3d3d6ec59b5b91b4 (patch) | |
tree | 936fa416fed20a30669bae155619d9cd7907fcd4 /net | |
parent | b788de0bdc88d7b8f61e7985aafd5253df91fe05 (diff) | |
download | chromium_src-02d74a036e548de4b9e81e4c3d3d6ec59b5b91b4.zip chromium_src-02d74a036e548de4b9e81e4c3d3d6ec59b5b91b4.tar.gz chromium_src-02d74a036e548de4b9e81e4c3d3d6ec59b5b91b4.tar.bz2 |
Let HttpStreamParser read 4xx/5xx HTTP responses after CONNECTION_RESET on uploads.
BUG=357631
R=willchan@chromium.org
Review URL: https://codereview.chromium.org/240423002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@265686 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r-- | net/http/http_network_transaction_unittest.cc | 483 | ||||
-rw-r--r-- | net/http/http_stream_parser.cc | 356 | ||||
-rw-r--r-- | net/http/http_stream_parser.h | 23 |
3 files changed, 724 insertions, 138 deletions
diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc index 3100297..c1112d0 100644 --- a/net/http/http_network_transaction_unittest.cc +++ b/net/http/http_network_transaction_unittest.cc @@ -12605,4 +12605,487 @@ TEST_P(HttpNetworkTransactionTest, CloseSSLSocketOnIdleForHttpRequest2) { EXPECT_EQ(1, GetIdleSocketCountInTransportSocketPool(session)); } +TEST_P(HttpNetworkTransactionTest, PostReadsErrorResponseAfterReset) { + ScopedVector<UploadElementReader> element_readers; + element_readers.push_back(new UploadBytesElementReader("foo", 3)); + UploadDataStream upload_data_stream(element_readers.Pass(), 0); + + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.foo.com/"); + request.upload_data_stream = &upload_data_stream; + request.load_flags = 0; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_)); + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(DEFAULT_PRIORITY, session)); + // Send headers successfully, but get an error while sending the body. + MockWrite data_writes[] = { + MockWrite("POST / HTTP/1.1\r\n" + "Host: www.foo.com\r\n" + "Connection: keep-alive\r\n" + "Content-Length: 3\r\n\r\n"), + MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET), + }; + + MockRead data_reads[] = { + MockRead("HTTP/1.0 400 Not OK\r\n\r\n"), + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), data_writes, + arraysize(data_writes)); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers.get() != NULL); + EXPECT_EQ("HTTP/1.0 400 Not OK", response->headers->GetStatusLine()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("hello world", response_data); +} + +// This test makes sure the retry logic doesn't trigger when reading an error +// response from a server that rejected a POST with a CONNECTION_RESET. +TEST_P(HttpNetworkTransactionTest, + PostReadsErrorResponseAfterResetOnReusedSocket) { + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_)); + MockWrite data_writes[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: www.foo.com\r\n" + "Connection: keep-alive\r\n\r\n"), + MockWrite("POST / HTTP/1.1\r\n" + "Host: www.foo.com\r\n" + "Connection: keep-alive\r\n" + "Content-Length: 3\r\n\r\n"), + MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET), + }; + + MockRead data_reads[] = { + MockRead("HTTP/1.1 200 Peachy\r\n" + "Content-Length: 14\r\n\r\n"), + MockRead("first response"), + MockRead("HTTP/1.1 400 Not OK\r\n" + "Content-Length: 15\r\n\r\n"), + MockRead("second response"), + MockRead(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), data_writes, + arraysize(data_writes)); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + TestCompletionCallback callback; + HttpRequestInfo request1; + request1.method = "GET"; + request1.url = GURL("http://www.foo.com/"); + request1.load_flags = 0; + + scoped_ptr<HttpTransaction> trans1( + new HttpNetworkTransaction(DEFAULT_PRIORITY, session)); + int rv = trans1->Start(&request1, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response1 = trans1->GetResponseInfo(); + ASSERT_TRUE(response1 != NULL); + + EXPECT_TRUE(response1->headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 200 Peachy", response1->headers->GetStatusLine()); + + std::string response_data1; + rv = ReadTransaction(trans1.get(), &response_data1); + EXPECT_EQ(OK, rv); + EXPECT_EQ("first response", response_data1); + // Delete the transaction to release the socket back into the socket pool. + trans1.reset(); + + ScopedVector<UploadElementReader> element_readers; + element_readers.push_back(new UploadBytesElementReader("foo", 3)); + UploadDataStream upload_data_stream(element_readers.Pass(), 0); + + HttpRequestInfo request2; + request2.method = "POST"; + request2.url = GURL("http://www.foo.com/"); + request2.upload_data_stream = &upload_data_stream; + request2.load_flags = 0; + + scoped_ptr<HttpTransaction> trans2( + new HttpNetworkTransaction(DEFAULT_PRIORITY, session)); + rv = trans2->Start(&request2, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response2 = trans2->GetResponseInfo(); + ASSERT_TRUE(response2 != NULL); + + EXPECT_TRUE(response2->headers.get() != NULL); + EXPECT_EQ("HTTP/1.1 400 Not OK", response2->headers->GetStatusLine()); + + std::string response_data2; + rv = ReadTransaction(trans2.get(), &response_data2); + EXPECT_EQ(OK, rv); + EXPECT_EQ("second response", response_data2); +} + +TEST_P(HttpNetworkTransactionTest, + PostReadsErrorResponseAfterResetPartialBodySent) { + ScopedVector<UploadElementReader> element_readers; + element_readers.push_back(new UploadBytesElementReader("foo", 3)); + UploadDataStream upload_data_stream(element_readers.Pass(), 0); + + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.foo.com/"); + request.upload_data_stream = &upload_data_stream; + request.load_flags = 0; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_)); + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(DEFAULT_PRIORITY, session)); + // Send headers successfully, but get an error while sending the body. + MockWrite data_writes[] = { + MockWrite("POST / HTTP/1.1\r\n" + "Host: www.foo.com\r\n" + "Connection: keep-alive\r\n" + "Content-Length: 3\r\n\r\n" + "fo"), + MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET), + }; + + MockRead data_reads[] = { + MockRead("HTTP/1.0 400 Not OK\r\n\r\n"), + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), data_writes, + arraysize(data_writes)); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers.get() != NULL); + EXPECT_EQ("HTTP/1.0 400 Not OK", response->headers->GetStatusLine()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("hello world", response_data); +} + +// This tests the more common case than the previous test, where headers and +// body are not merged into a single request. +TEST_P(HttpNetworkTransactionTest, ChunkedPostReadsErrorResponseAfterReset) { + ScopedVector<UploadElementReader> element_readers; + element_readers.push_back(new UploadBytesElementReader("foo", 3)); + UploadDataStream upload_data_stream(UploadDataStream::CHUNKED, 0); + + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.foo.com/"); + request.upload_data_stream = &upload_data_stream; + request.load_flags = 0; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_)); + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(DEFAULT_PRIORITY, session)); + // Send headers successfully, but get an error while sending the body. + MockWrite data_writes[] = { + MockWrite("POST / HTTP/1.1\r\n" + "Host: www.foo.com\r\n" + "Connection: keep-alive\r\n" + "Transfer-Encoding: chunked\r\n\r\n"), + MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET), + }; + + MockRead data_reads[] = { + MockRead("HTTP/1.0 400 Not OK\r\n\r\n"), + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), data_writes, + arraysize(data_writes)); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + // Make sure the headers are sent before adding a chunk. This ensures that + // they can't be merged with the body in a single send. Not currently + // necessary since a chunked body is never merged with headers, but this makes + // the test more future proof. + base::RunLoop().RunUntilIdle(); + + upload_data_stream.AppendChunk("last chunk", 10, true); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers.get() != NULL); + EXPECT_EQ("HTTP/1.0 400 Not OK", response->headers->GetStatusLine()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("hello world", response_data); +} + +TEST_P(HttpNetworkTransactionTest, PostReadsErrorResponseAfterResetAnd100) { + ScopedVector<UploadElementReader> element_readers; + element_readers.push_back(new UploadBytesElementReader("foo", 3)); + UploadDataStream upload_data_stream(element_readers.Pass(), 0); + + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.foo.com/"); + request.upload_data_stream = &upload_data_stream; + request.load_flags = 0; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_)); + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(DEFAULT_PRIORITY, session)); + + MockWrite data_writes[] = { + MockWrite("POST / HTTP/1.1\r\n" + "Host: www.foo.com\r\n" + "Connection: keep-alive\r\n" + "Content-Length: 3\r\n\r\n"), + MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET), + }; + + MockRead data_reads[] = { + MockRead("HTTP/1.0 100 Continue\r\n\r\n"), + MockRead("HTTP/1.0 400 Not OK\r\n\r\n"), + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), data_writes, + arraysize(data_writes)); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + + EXPECT_TRUE(response->headers.get() != NULL); + EXPECT_EQ("HTTP/1.0 400 Not OK", response->headers->GetStatusLine()); + + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("hello world", response_data); +} + +TEST_P(HttpNetworkTransactionTest, PostIgnoresNonErrorResponseAfterReset) { + ScopedVector<UploadElementReader> element_readers; + element_readers.push_back(new UploadBytesElementReader("foo", 3)); + UploadDataStream upload_data_stream(element_readers.Pass(), 0); + + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.foo.com/"); + request.upload_data_stream = &upload_data_stream; + request.load_flags = 0; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_)); + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(DEFAULT_PRIORITY, session)); + // Send headers successfully, but get an error while sending the body. + MockWrite data_writes[] = { + MockWrite("POST / HTTP/1.1\r\n" + "Host: www.foo.com\r\n" + "Connection: keep-alive\r\n" + "Content-Length: 3\r\n\r\n"), + MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET), + }; + + MockRead data_reads[] = { + MockRead("HTTP/1.0 200 Just Dandy\r\n\r\n"), + MockRead("hello world"), + MockRead(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), data_writes, + arraysize(data_writes)); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_CONNECTION_RESET, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_TRUE(response == NULL); +} + +TEST_P(HttpNetworkTransactionTest, + PostIgnoresNonErrorResponseAfterResetAnd100) { + ScopedVector<UploadElementReader> element_readers; + element_readers.push_back(new UploadBytesElementReader("foo", 3)); + UploadDataStream upload_data_stream(element_readers.Pass(), 0); + + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.foo.com/"); + request.upload_data_stream = &upload_data_stream; + request.load_flags = 0; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_)); + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(DEFAULT_PRIORITY, session)); + // Send headers successfully, but get an error while sending the body. + MockWrite data_writes[] = { + MockWrite("POST / HTTP/1.1\r\n" + "Host: www.foo.com\r\n" + "Connection: keep-alive\r\n" + "Content-Length: 3\r\n\r\n"), + MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET), + }; + + MockRead data_reads[] = { + MockRead("HTTP/1.0 100 Continue\r\n\r\n"), + MockRead("HTTP/1.0 302 Redirect\r\n"), + MockRead("Location: http://somewhere-else.com/\r\n"), + MockRead("Content-Length: 0\r\n\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), data_writes, + arraysize(data_writes)); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_CONNECTION_RESET, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_TRUE(response == NULL); +} + +TEST_P(HttpNetworkTransactionTest, PostIgnoresHttp09ResponseAfterReset) { + ScopedVector<UploadElementReader> element_readers; + element_readers.push_back(new UploadBytesElementReader("foo", 3)); + UploadDataStream upload_data_stream(element_readers.Pass(), 0); + + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.foo.com/"); + request.upload_data_stream = &upload_data_stream; + request.load_flags = 0; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_)); + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(DEFAULT_PRIORITY, session)); + // Send headers successfully, but get an error while sending the body. + MockWrite data_writes[] = { + MockWrite("POST / HTTP/1.1\r\n" + "Host: www.foo.com\r\n" + "Connection: keep-alive\r\n" + "Content-Length: 3\r\n\r\n"), + MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET), + }; + + MockRead data_reads[] = { + MockRead("HTTP 0.9 rocks!"), + MockRead(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), data_writes, + arraysize(data_writes)); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_CONNECTION_RESET, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_TRUE(response == NULL); +} + +TEST_P(HttpNetworkTransactionTest, PostIgnoresPartial400HeadersAfterReset) { + ScopedVector<UploadElementReader> element_readers; + element_readers.push_back(new UploadBytesElementReader("foo", 3)); + UploadDataStream upload_data_stream(element_readers.Pass(), 0); + + HttpRequestInfo request; + request.method = "POST"; + request.url = GURL("http://www.foo.com/"); + request.upload_data_stream = &upload_data_stream; + request.load_flags = 0; + + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps_)); + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(DEFAULT_PRIORITY, session)); + // Send headers successfully, but get an error while sending the body. + MockWrite data_writes[] = { + MockWrite("POST / HTTP/1.1\r\n" + "Host: www.foo.com\r\n" + "Connection: keep-alive\r\n" + "Content-Length: 3\r\n\r\n"), + MockWrite(SYNCHRONOUS, ERR_CONNECTION_RESET), + }; + + MockRead data_reads[] = { + MockRead("HTTP/1.0 400 Not a Full Response\r\n"), + MockRead(SYNCHRONOUS, OK), + }; + StaticSocketDataProvider data(data_reads, arraysize(data_reads), data_writes, + arraysize(data_writes)); + session_deps_.socket_factory->AddSocketDataProvider(&data); + + TestCompletionCallback callback; + + int rv = trans->Start(&request, callback.callback(), BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback.WaitForResult(); + EXPECT_EQ(ERR_CONNECTION_RESET, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_TRUE(response == NULL); +} + } // namespace net diff --git a/net/http/http_stream_parser.cc b/net/http/http_stream_parser.cc index fb158cc..820c1ba 100644 --- a/net/http/http_stream_parser.cc +++ b/net/http/http_stream_parser.cc @@ -6,6 +6,7 @@ #include "base/bind.h" #include "base/compiler_specific.h" +#include "base/logging.h" #include "base/strings/string_util.h" #include "base/values.h" #include "net/base/io_buffer.h" @@ -19,12 +20,14 @@ #include "net/socket/client_socket_handle.h" #include "net/socket/ssl_client_socket.h" +namespace net { + namespace { const size_t kMaxMergedHeaderAndBodySize = 1400; const size_t kRequestBodyBufferSize = 1 << 14; // 16KB -std::string GetResponseHeaderLines(const net::HttpResponseHeaders& headers) { +std::string GetResponseHeaderLines(const HttpResponseHeaders& headers) { std::string raw_headers = headers.raw_headers(); const char* null_separated_headers = raw_headers.c_str(); const char* header_line = null_separated_headers; @@ -39,9 +42,8 @@ std::string GetResponseHeaderLines(const net::HttpResponseHeaders& headers) { // Return true if |headers| contain multiple |field_name| fields with different // values. -bool HeadersContainMultipleCopiesOfField( - const net::HttpResponseHeaders& headers, - const std::string& field_name) { +bool HeadersContainMultipleCopiesOfField(const HttpResponseHeaders& headers, + const std::string& field_name) { void* it = NULL; std::string field_value; if (!headers.EnumerateHeader(&it, field_name, &field_value)) @@ -56,11 +58,10 @@ bool HeadersContainMultipleCopiesOfField( return false; } -base::Value* NetLogSendRequestBodyCallback( - int length, - bool is_chunked, - bool did_merge, - net::NetLog::LogLevel /* log_level */) { +base::Value* NetLogSendRequestBodyCallback(int length, + bool is_chunked, + bool did_merge, + NetLog::LogLevel /* log_level */) { base::DictionaryValue* dict = new base::DictionaryValue(); dict->SetInteger("length", length); dict->SetBoolean("is_chunked", is_chunked); @@ -68,9 +69,14 @@ base::Value* NetLogSendRequestBodyCallback( return dict; } -} // namespace +// Returns true if |error_code| is an error for which we give the server a +// chance to send a body containing error information, if the error was received +// while trying to upload a request body. +bool ShouldTryReadingOnUploadError(int error_code) { + return (error_code == ERR_CONNECTION_RESET); +} -namespace net { +} // namespace // Similar to DrainableIOBuffer(), but this version comes with its own // storage. The motivation is to avoid repeated allocations of @@ -101,7 +107,7 @@ namespace net { // // size() == BytesRemaining() == BytesConsumed() == 0. // // data() points to the beginning of the buffer. // -class HttpStreamParser::SeekableIOBuffer : public net::IOBuffer { +class HttpStreamParser::SeekableIOBuffer : public IOBuffer { public: explicit SeekableIOBuffer(int capacity) : IOBuffer(capacity), @@ -176,6 +182,7 @@ HttpStreamParser::HttpStreamParser(ClientSocketHandle* connection, : io_state_(STATE_NONE), request_(request), request_headers_(NULL), + request_headers_length_(0), read_buf_(read_buffer), read_buf_unused_offset_(0), response_header_start_offset_(-1), @@ -187,6 +194,7 @@ HttpStreamParser::HttpStreamParser(ClientSocketHandle* connection, connection_(connection), net_log_(net_log), sent_last_chunk_(false), + upload_error_(OK), weak_ptr_factory_(this) { io_callback_ = base::Bind(&HttpStreamParser::OnIOComplete, weak_ptr_factory_.GetWeakPtr()); @@ -223,6 +231,7 @@ int HttpStreamParser::SendRequest(const std::string& request_line, response_->socket_address = HostPortPair::FromIPEndPoint(ip_endpoint); std::string request = request_line + headers.ToString(); + request_headers_length_ = request.size(); if (request_->upload_data_stream != NULL) { request_body_send_buf_ = new SeekableIOBuffer(kRequestBodyBufferSize); @@ -243,7 +252,8 @@ int HttpStreamParser::SendRequest(const std::string& request_line, // single write. bool did_merge = false; if (ShouldMergeRequestHeadersAndBody(request, request_->upload_data_stream)) { - size_t merged_size = request.size() + request_->upload_data_stream->size(); + size_t merged_size = + request_headers_length_ + request_->upload_data_stream->size(); scoped_refptr<IOBuffer> merged_request_headers_and_body( new IOBuffer(merged_size)); // We'll repurpose |request_headers_| to store the merged headers and @@ -251,8 +261,8 @@ int HttpStreamParser::SendRequest(const std::string& request_line, request_headers_ = new DrainableIOBuffer( merged_request_headers_and_body.get(), merged_size); - memcpy(request_headers_->data(), request.data(), request.size()); - request_headers_->DidConsume(request.size()); + memcpy(request_headers_->data(), request.data(), request_headers_length_); + request_headers_->DidConsume(request_headers_length_); size_t todo = request_->upload_data_stream->size(); while (todo) { @@ -370,10 +380,18 @@ int HttpStreamParser::DoLoop(int result) { io_state_ = STATE_NONE; switch (state) { case STATE_SEND_HEADERS: - result = DoSendHeaders(result); + DCHECK_EQ(OK, result); + result = DoSendHeaders(); + break; + case STATE_SEND_HEADERS_COMPLETE: + result = DoSendHeadersComplete(result); break; case STATE_SEND_BODY: - result = DoSendBody(result); + DCHECK_EQ(OK, result); + result = DoSendBody(); + break; + case STATE_SEND_BODY_COMPLETE: + result = DoSendBodyComplete(result); break; case STATE_SEND_REQUEST_READ_BODY_COMPLETE: result = DoSendRequestReadBodyComplete(result); @@ -405,21 +423,38 @@ int HttpStreamParser::DoLoop(int result) { return result; } -int HttpStreamParser::DoSendHeaders(int result) { - // If there was a problem writing to the socket, just return the error code. - if (result < 0) - return result; - request_headers_->DidConsume(result); +int HttpStreamParser::DoSendHeaders() { int bytes_remaining = request_headers_->BytesRemaining(); - if (bytes_remaining > 0) { - // Record our best estimate of the 'request time' as the time when we send - // out the first bytes of the request headers. - if (bytes_remaining == request_headers_->size()) { - response_->request_time = base::Time::Now(); + DCHECK_GT(bytes_remaining, 0); + + // Record our best estimate of the 'request time' as the time when we send + // out the first bytes of the request headers. + if (bytes_remaining == request_headers_->size()) + response_->request_time = base::Time::Now(); + + io_state_ = STATE_SEND_HEADERS_COMPLETE; + return connection_->socket() + ->Write(request_headers_.get(), bytes_remaining, io_callback_); +} + +int HttpStreamParser::DoSendHeadersComplete(int result) { + if (result < 0) { + // In the unlikely case that the headers and body were merged, all the + // the headers were sent, but not all of the body way, and |result| is + // an error that this should try reading after, stash the error for now and + // act like the request was successfully sent. + if (request_headers_->BytesConsumed() >= request_headers_length_ && + ShouldTryReadingOnUploadError(result)) { + upload_error_ = result; + return OK; } + return result; + } + + request_headers_->DidConsume(result); + if (request_headers_->BytesRemaining() > 0) { io_state_ = STATE_SEND_HEADERS; - return connection_->socket() - ->Write(request_headers_.get(), bytes_remaining, io_callback_); + return OK; } if (request_->upload_data_stream != NULL && @@ -441,16 +476,9 @@ int HttpStreamParser::DoSendHeaders(int result) { return OK; } -int HttpStreamParser::DoSendBody(int result) { - // |result| is the number of bytes sent from the last call to - // DoSendBody(), 0 (i.e. OK), or negative if the last write failed. - if (result < 0) - return result; - - // Send the remaining data in the request body buffer. - request_body_send_buf_->DidConsume(result); +int HttpStreamParser::DoSendBody() { if (request_body_send_buf_->BytesRemaining() > 0) { - io_state_ = STATE_SEND_BODY; + io_state_ = STATE_SEND_BODY_COMPLETE; return connection_->socket() ->Write(request_body_send_buf_.get(), request_body_send_buf_->BytesRemaining(), @@ -469,6 +497,23 @@ int HttpStreamParser::DoSendBody(int result) { io_callback_); } +int HttpStreamParser::DoSendBodyComplete(int result) { + if (result < 0) { + // If |result| is an error that this should try reading after, stash the + // error for now and act like the request was successfully sent. + if (ShouldTryReadingOnUploadError(result)) { + upload_error_ = result; + return OK; + } + return result; + } + + request_body_send_buf_->DidConsume(result); + + io_state_ = STATE_SEND_BODY; + return OK; +} + int HttpStreamParser::DoSendRequestReadBodyComplete(int result) { // |result| is the result of read from the request body from the last call to // DoSendBody(). @@ -518,110 +563,50 @@ int HttpStreamParser::DoReadHeaders() { } int HttpStreamParser::DoReadHeadersComplete(int result) { - DCHECK_EQ(0, read_buf_unused_offset_); + result = HandleReadHeaderResult(result); - if (result == 0) - result = ERR_CONNECTION_CLOSED; + // TODO(mmenke): The code below is ugly and hacky. A much better and more + // flexible long term solution would be to separate out the read and write + // loops, though this would involve significant changes, both here and + // elsewhere (WebSockets, for instance). - if (result < 0 && result != ERR_CONNECTION_CLOSED) { - io_state_ = STATE_DONE; + // If still reading the headers, or there was no error uploading the request + // body, just return the result. + if (io_state_ == STATE_READ_HEADERS || upload_error_ == OK) return result; - } - // If we've used the connection before, then we know it is not a HTTP/0.9 - // response and return ERR_CONNECTION_CLOSED. - if (result == ERR_CONNECTION_CLOSED && read_buf_->offset() == 0 && - connection_->is_reused()) { + + // If the result is ERR_IO_PENDING, |io_state_| should be STATE_READ_HEADERS. + DCHECK_NE(ERR_IO_PENDING, result); + + // On errors, use the original error received when sending the request. + // The main cases where these are different is when there's a header-related + // error code, or when there's an ERR_CONNECTION_CLOSED, which can result in + // special handling of partial responses and HTTP/0.9 responses. + if (result < 0) { + // Nothing else to do. In the HTTP/0.9 or only partial headers received + // cases, can normally go to other states after an error reading headers. io_state_ = STATE_DONE; - return result; + // Don't let caller see the headers. + response_->headers = NULL; + return upload_error_; } - // Record our best estimate of the 'response time' as the time when we read - // the first bytes of the response headers. - if (read_buf_->offset() == 0 && result != ERR_CONNECTION_CLOSED) - response_->response_time = base::Time::Now(); - - if (result == ERR_CONNECTION_CLOSED) { - // The connection closed before we detected the end of the headers. - if (read_buf_->offset() == 0) { - // The connection was closed before any data was sent. Likely an error - // rather than empty HTTP/0.9 response. - io_state_ = STATE_DONE; - return ERR_EMPTY_RESPONSE; - } else if (request_->url.SchemeIsSecure()) { - // The connection was closed in the middle of the headers. For HTTPS we - // don't parse partial headers. Return a different error code so that we - // know that we shouldn't attempt to retry the request. - io_state_ = STATE_DONE; - return ERR_RESPONSE_HEADERS_TRUNCATED; - } - // Parse things as well as we can and let the caller decide what to do. - int end_offset; - if (response_header_start_offset_ >= 0) { - io_state_ = STATE_READ_BODY_COMPLETE; - end_offset = read_buf_->offset(); - } else { - // Now waiting for the body to be read. - end_offset = 0; - } - int rv = DoParseResponseHeaders(end_offset); - if (rv < 0) - return rv; + // Skip over 1xx responses as usual, and allow 4xx/5xx error responses to + // override the error received while uploading the body. + int response_code_class = response_->headers->response_code() / 100; + if (response_code_class == 1 || response_code_class == 4 || + response_code_class == 5) { return result; } - read_buf_->set_offset(read_buf_->offset() + result); - DCHECK_LE(read_buf_->offset(), read_buf_->capacity()); - DCHECK_GE(result, 0); - - int end_of_header_offset = ParseResponseHeaders(); - - // Note: -1 is special, it indicates we haven't found the end of headers. - // Anything less than -1 is a net::Error, so we bail out. - if (end_of_header_offset < -1) - return end_of_header_offset; - - if (end_of_header_offset == -1) { - io_state_ = STATE_READ_HEADERS; - // Prevent growing the headers buffer indefinitely. - if (read_buf_->offset() >= kMaxHeaderBufSize) { - io_state_ = STATE_DONE; - return ERR_RESPONSE_HEADERS_TOO_BIG; - } - } else { - CalculateResponseBodySize(); - // If the body is zero length, the caller may not call ReadResponseBody, - // which is where any extra data is copied to read_buf_, so we move the - // data here. - if (response_body_length_ == 0) { - int extra_bytes = read_buf_->offset() - end_of_header_offset; - if (extra_bytes) { - CHECK_GT(extra_bytes, 0); - memmove(read_buf_->StartOfBuffer(), - read_buf_->StartOfBuffer() + end_of_header_offset, - extra_bytes); - } - read_buf_->SetCapacity(extra_bytes); - if (response_->headers->response_code() / 100 == 1) { - // After processing a 1xx response, the caller will ask for the next - // header, so reset state to support that. We don't completely ignore a - // 1xx response because it cannot be returned in reply to a CONNECT - // request so we return OK here, which lets the caller inspect the - // response and reject it in the event that we're setting up a CONNECT - // tunnel. - response_header_start_offset_ = -1; - response_body_length_ = -1; - // Now waiting for the second set of headers to be read. - } else { - io_state_ = STATE_DONE; - } - return OK; - } + // All other status codes are not allowed after an error during upload, to + // make sure the consumer has some indication there was an error. - // Note where the headers stop. - read_buf_unused_offset_ = end_of_header_offset; - // Now waiting for the body to be read. - } - return result; + // Nothing else to do. + io_state_ = STATE_DONE; + // Don't let caller see the headers. + response_->headers = NULL; + return upload_error_; } int HttpStreamParser::DoReadBody() { @@ -758,6 +743,113 @@ int HttpStreamParser::DoReadBodyComplete(int result) { return result; } +int HttpStreamParser::HandleReadHeaderResult(int result) { + DCHECK_EQ(0, read_buf_unused_offset_); + + if (result == 0) + result = ERR_CONNECTION_CLOSED; + + if (result < 0 && result != ERR_CONNECTION_CLOSED) { + io_state_ = STATE_DONE; + return result; + } + // If we've used the connection before, then we know it is not a HTTP/0.9 + // response and return ERR_CONNECTION_CLOSED. + if (result == ERR_CONNECTION_CLOSED && read_buf_->offset() == 0 && + connection_->is_reused()) { + io_state_ = STATE_DONE; + return result; + } + + // Record our best estimate of the 'response time' as the time when we read + // the first bytes of the response headers. + if (read_buf_->offset() == 0 && result != ERR_CONNECTION_CLOSED) + response_->response_time = base::Time::Now(); + + if (result == ERR_CONNECTION_CLOSED) { + // The connection closed before we detected the end of the headers. + if (read_buf_->offset() == 0) { + // The connection was closed before any data was sent. Likely an error + // rather than empty HTTP/0.9 response. + io_state_ = STATE_DONE; + return ERR_EMPTY_RESPONSE; + } else if (request_->url.SchemeIsSecure()) { + // The connection was closed in the middle of the headers. For HTTPS we + // don't parse partial headers. Return a different error code so that we + // know that we shouldn't attempt to retry the request. + io_state_ = STATE_DONE; + return ERR_RESPONSE_HEADERS_TRUNCATED; + } + // Parse things as well as we can and let the caller decide what to do. + int end_offset; + if (response_header_start_offset_ >= 0) { + io_state_ = STATE_READ_BODY_COMPLETE; + end_offset = read_buf_->offset(); + } else { + // Now waiting for the body to be read. + end_offset = 0; + } + int rv = DoParseResponseHeaders(end_offset); + if (rv < 0) + return rv; + return result; + } + + read_buf_->set_offset(read_buf_->offset() + result); + DCHECK_LE(read_buf_->offset(), read_buf_->capacity()); + DCHECK_GE(result, 0); + + int end_of_header_offset = ParseResponseHeaders(); + + // Note: -1 is special, it indicates we haven't found the end of headers. + // Anything less than -1 is a net::Error, so we bail out. + if (end_of_header_offset < -1) + return end_of_header_offset; + + if (end_of_header_offset == -1) { + io_state_ = STATE_READ_HEADERS; + // Prevent growing the headers buffer indefinitely. + if (read_buf_->offset() >= kMaxHeaderBufSize) { + io_state_ = STATE_DONE; + return ERR_RESPONSE_HEADERS_TOO_BIG; + } + } else { + CalculateResponseBodySize(); + // If the body is zero length, the caller may not call ReadResponseBody, + // which is where any extra data is copied to read_buf_, so we move the + // data here. + if (response_body_length_ == 0) { + int extra_bytes = read_buf_->offset() - end_of_header_offset; + if (extra_bytes) { + CHECK_GT(extra_bytes, 0); + memmove(read_buf_->StartOfBuffer(), + read_buf_->StartOfBuffer() + end_of_header_offset, + extra_bytes); + } + read_buf_->SetCapacity(extra_bytes); + if (response_->headers->response_code() / 100 == 1) { + // After processing a 1xx response, the caller will ask for the next + // header, so reset state to support that. We don't completely ignore a + // 1xx response because it cannot be returned in reply to a CONNECT + // request so we return OK here, which lets the caller inspect the + // response and reject it in the event that we're setting up a CONNECT + // tunnel. + response_header_start_offset_ = -1; + response_body_length_ = -1; + // Now waiting for the second set of headers to be read. + } else { + io_state_ = STATE_DONE; + } + return OK; + } + + // Note where the headers stop. + read_buf_unused_offset_ = end_of_header_offset; + // Now waiting for the body to be read. + } + return result; +} + int HttpStreamParser::ParseResponseHeaders() { int end_offset = -1; DCHECK_EQ(0, read_buf_unused_offset_); diff --git a/net/http/http_stream_parser.h b/net/http/http_stream_parser.h index 4fac0b9..afaf6c0 100644 --- a/net/http/http_stream_parser.h +++ b/net/http/http_stream_parser.h @@ -114,10 +114,9 @@ class NET_EXPORT_PRIVATE HttpStreamParser { // continuing. STATE_NONE, STATE_SEND_HEADERS, - // If the request comes with a body, either of the following two - // states will be executed, depending on whether the body is chunked - // or not. + STATE_SEND_HEADERS_COMPLETE, STATE_SEND_BODY, + STATE_SEND_BODY_COMPLETE, STATE_SEND_REQUEST_READ_BODY_COMPLETE, STATE_READ_HEADERS, STATE_READ_HEADERS_COMPLETE, @@ -146,14 +145,19 @@ class NET_EXPORT_PRIVATE HttpStreamParser { int DoLoop(int result); // The implementations of each state of the state machine. - int DoSendHeaders(int result); - int DoSendBody(int result); + int DoSendHeaders(); + int DoSendHeadersComplete(int result); + int DoSendBody(); + int DoSendBodyComplete(int result); int DoSendRequestReadBodyComplete(int result); int DoReadHeaders(); int DoReadHeadersComplete(int result); int DoReadBody(); int DoReadBodyComplete(int result); + // This handles most of the logic for DoReadHeadersComplete. + int HandleReadHeaderResult(int result); + // Examines |read_buf_| to find the start and end of the headers. If they are // found, parse them with DoParseResponseHeaders(). Return the offset for // the end of the headers, or -1 if the complete headers were not found, or @@ -173,9 +177,13 @@ class NET_EXPORT_PRIVATE HttpStreamParser { // The request to send. const HttpRequestInfo* request_; - // The request header data. + // The request header data. May include a merged request body. scoped_refptr<DrainableIOBuffer> request_headers_; + // Size of just the request headers. May be less than the length of + // |request_headers_| if the body was merged with the headers. + int request_headers_length_; + // Temporary buffer for reading. scoped_refptr<GrowableIOBuffer> read_buf_; @@ -235,6 +243,9 @@ class NET_EXPORT_PRIVATE HttpStreamParser { scoped_refptr<SeekableIOBuffer> request_body_send_buf_; bool sent_last_chunk_; + // Error received when uploading the body, if any. + int upload_error_; + base::WeakPtrFactory<HttpStreamParser> weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(HttpStreamParser); |