diff options
-rw-r--r-- | net/base/net_error_list.h | 3 | ||||
-rw-r--r-- | net/http/http_network_transaction.cc | 55 | ||||
-rw-r--r-- | net/http/http_network_transaction_unittest.cc | 209 | ||||
-rw-r--r-- | net/http/http_response_body_drainer.cc | 119 | ||||
-rw-r--r-- | net/http/http_response_body_drainer.h | 66 | ||||
-rw-r--r-- | net/http/http_response_body_drainer_unittest.cc | 216 | ||||
-rw-r--r-- | net/http/http_util.cc | 10 | ||||
-rw-r--r-- | net/http/http_util.h | 7 | ||||
-rw-r--r-- | net/net.gyp | 3 | ||||
-rw-r--r-- | net/spdy/spdy_network_transaction_unittest.cc | 5 |
10 files changed, 590 insertions, 103 deletions
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h index 7f2a6c2..abf18d1 100644 --- a/net/base/net_error_list.h +++ b/net/base/net_error_list.h @@ -379,6 +379,9 @@ NET_ERROR(MISCONFIGURED_AUTH_ENVIRONMENT, -343) // An undocumented SSPI or GSSAPI status code was returned. NET_ERROR(UNDOCUMENTED_SECURITY_LIBRARY_STATUS, -344) +// The HTTP response was too big to drain. +NET_ERROR(RESPONSE_BODY_TOO_BIG_TO_DRAIN, -345) + // The cache does not have the requested entry. NET_ERROR(CACHE_MISS, -400) diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc index e04ed1d..e6084bc 100644 --- a/net/http/http_network_transaction.cc +++ b/net/http/http_network_transaction.cc @@ -166,6 +166,40 @@ HttpNetworkTransaction::HttpNetworkTransaction(HttpNetworkSession* session) } +HttpNetworkTransaction::~HttpNetworkTransaction() { + if (stream_.get()) { + HttpResponseHeaders* headers = GetResponseHeaders(); + // TODO(mbelshe): The stream_ should be able to compute whether or not the + // stream should be kept alive. No reason to compute here + // and pass it in. + bool try_to_keep_alive = + next_state_ == STATE_NONE && + stream_->CanFindEndOfResponse() && + (!headers || headers->IsKeepAlive()); + if (!try_to_keep_alive) { + stream_->Close(true /* not reusable */); + } else { + if (stream_->IsResponseBodyComplete()) { + // If the response body is complete, we can just reuse the socket. + stream_->Close(false /* reusable */); + } else { + // Otherwise, we try to drain the response body. + // TODO(willchan): Consider moving this response body draining to the + // stream implementation. For SPDY, there's clearly no point. For + // HTTP, it can vary depending on whether or not we're pipelining. It's + // stream dependent, so the different subtypes should be implementing + // their solutions. + HttpUtil::DrainStreamBodyAndClose(stream_.release()); + } + } + } + + if (stream_request_.get()) { + stream_request_->Cancel(); + stream_request_ = NULL; + } +} + int HttpNetworkTransaction::Start(const HttpRequestInfo* request_info, CompletionCallback* callback, const BoundNetLog& net_log) { @@ -440,31 +474,12 @@ void HttpNetworkTransaction::OnNeedsClientAuth( OnIOComplete(ERR_SSL_CLIENT_AUTH_CERT_NEEDED); } -HttpNetworkTransaction::~HttpNetworkTransaction() { - if (stream_.get()) { - HttpResponseHeaders* headers = GetResponseHeaders(); - // TODO(mbelshe): The stream_ should be able to compute whether or not the - // stream should be kept alive. No reason to compute here - // and pass it in. - bool keep_alive = next_state_ == STATE_NONE && - stream_->IsResponseBodyComplete() && - stream_->CanFindEndOfResponse() && - (!headers || headers->IsKeepAlive()); - stream_->Close(!keep_alive); - } - - if (stream_request_.get()) { - stream_request_->Cancel(); - stream_request_ = NULL; - } -} - bool HttpNetworkTransaction::is_https_request() const { return request_->url.SchemeIs("https"); } void HttpNetworkTransaction::DoCallback(int rv) { - DCHECK(rv != ERR_IO_PENDING); + DCHECK_NE(rv, ERR_IO_PENDING); DCHECK(user_callback_); // Since Run may result in Read being called, clear user_callback_ up front. diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc index d2a35bc..a8d9ea5 100644 --- a/net/http/http_network_transaction_unittest.cc +++ b/net/http/http_network_transaction_unittest.cc @@ -154,14 +154,20 @@ HttpNetworkSession* CreateSession(SessionDependencies* session_deps) { class HttpNetworkTransactionTest : public PlatformTest { public: virtual void SetUp() { + NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests(); + MessageLoop::current()->RunAllPending(); spdy::SpdyFramer::set_enable_compression_default(false); } virtual void TearDown() { + NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests(); + MessageLoop::current()->RunAllPending(); spdy::SpdyFramer::set_enable_compression_default(true); // Empty the current queue. MessageLoop::current()->RunAllPending(); PlatformTest::TearDown(); + NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests(); + MessageLoop::current()->RunAllPending(); } protected: @@ -560,7 +566,7 @@ TEST_F(HttpNetworkTransactionTest, ReuseConnection) { StaticSocketDataProvider data(data_reads, arraysize(data_reads), NULL, 0); session_deps.socket_factory.AddSocketDataProvider(&data); - const char* kExpectedResponseData[] = { + const char* const kExpectedResponseData[] = { "hello", "world" }; @@ -850,8 +856,9 @@ TEST_F(HttpNetworkTransactionTest, NonKeepAliveConnectionEOF) { EXPECT_EQ(ERR_EMPTY_RESPONSE, out.rv); } -// Test that we correctly reuse a keep-alive connection after receiving a 304. -TEST_F(HttpNetworkTransactionTest, KeepAliveAfter304) { +// Test that we correctly reuse a keep-alive connection after not explicitly +// reading the body. +TEST_F(HttpNetworkTransactionTest, KeepAliveAfterUnreadBody) { SessionDependencies session_deps; scoped_refptr<HttpNetworkSession> session = CreateSession(&session_deps); @@ -860,8 +867,23 @@ TEST_F(HttpNetworkTransactionTest, KeepAliveAfter304) { request.url = GURL("http://www.foo.com/"); request.load_flags = 0; + // Note that because all these reads happen in the same + // StaticSocketDataProvider, it shows that the same socket is being reused for + // all transactions. MockRead data1_reads[] = { + MockRead("HTTP/1.1 204 No Content\r\n\r\n"), + MockRead("HTTP/1.1 205 Reset Content\r\n\r\n"), MockRead("HTTP/1.1 304 Not Modified\r\n\r\n"), + MockRead("HTTP/1.1 302 Found\r\n" + "Content-Length: 0\r\n\r\n"), + MockRead("HTTP/1.1 302 Found\r\n" + "Content-Length: 5\r\n\r\n" + "hello"), + MockRead("HTTP/1.1 301 Moved Permanently\r\n" + "Content-Length: 0\r\n\r\n"), + MockRead("HTTP/1.1 301 Moved Permanently\r\n" + "Content-Length: 5\r\n\r\n" + "hello"), MockRead("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n"), MockRead("hello"), }; @@ -874,7 +896,10 @@ TEST_F(HttpNetworkTransactionTest, KeepAliveAfter304) { StaticSocketDataProvider data2(data2_reads, arraysize(data2_reads), NULL, 0); session_deps.socket_factory.AddSocketDataProvider(&data2); - for (int i = 0; i < 2; ++i) { + const int kNumUnreadBodies = arraysize(data1_reads) - 2; + std::string response_lines[kNumUnreadBodies]; + + for (size_t i = 0; i < arraysize(data1_reads) - 2; ++i) { TestCompletionCallback callback; scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); @@ -886,22 +911,44 @@ TEST_F(HttpNetworkTransactionTest, KeepAliveAfter304) { EXPECT_EQ(OK, rv); const HttpResponseInfo* response = trans->GetResponseInfo(); - EXPECT_TRUE(response != NULL); + ASSERT_TRUE(response != NULL); - EXPECT_TRUE(response->headers != NULL); - if (i == 0) { - EXPECT_EQ("HTTP/1.1 304 Not Modified", - response->headers->GetStatusLine()); - // We intentionally don't read the response in this case, to reflect how - // HttpCache::Transaction uses HttpNetworkTransaction. - } else { - EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); - std::string response_data; - rv = ReadTransaction(trans.get(), &response_data); - EXPECT_EQ(OK, rv); - EXPECT_EQ("hello", response_data); - } + ASSERT_TRUE(response->headers != NULL); + response_lines[i] = response->headers->GetStatusLine(); + + // We intentionally don't read the response bodies. } + + const char* const kStatusLines[] = { + "HTTP/1.1 204 No Content", + "HTTP/1.1 205 Reset Content", + "HTTP/1.1 304 Not Modified", + "HTTP/1.1 302 Found", + "HTTP/1.1 302 Found", + "HTTP/1.1 301 Moved Permanently", + "HTTP/1.1 301 Moved Permanently", + }; + + COMPILE_ASSERT(kNumUnreadBodies == arraysize(kStatusLines), + forgot_to_update_kStatusLines); + + for (int i = 0; i < kNumUnreadBodies; ++i) + EXPECT_EQ(kStatusLines[i], response_lines[i]); + + TestCompletionCallback callback; + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + int rv = trans->Start(&request, &callback, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + EXPECT_EQ(OK, rv); + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + std::string response_data; + rv = ReadTransaction(trans.get(), &response_data); + EXPECT_EQ(OK, rv); + EXPECT_EQ("hello", response_data); } // Test the request-challenge-retry sequence for basic auth. @@ -1036,8 +1083,7 @@ TEST_F(HttpNetworkTransactionTest, DoNotSendAuth) { // connection. TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAlive) { SessionDependencies session_deps; - scoped_ptr<HttpTransaction> trans( - new HttpNetworkTransaction(CreateSession(&session_deps))); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); HttpRequestInfo request; request.method = "GET"; @@ -1067,8 +1113,8 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAlive) { // Lastly, the server responds with the actual content. MockRead("HTTP/1.1 200 OK\r\n"), MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), - MockRead("Content-Length: 100\r\n\r\n"), - MockRead(false, OK), + MockRead("Content-Length: 5\r\n\r\n"), + MockRead("Hello"), }; StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), @@ -1077,6 +1123,7 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAlive) { TestCompletionCallback callback1; + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); int rv = trans->Start(&request, &callback1, BoundNetLog()); EXPECT_EQ(ERR_IO_PENDING, rv); @@ -1104,15 +1151,14 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAlive) { response = trans->GetResponseInfo(); EXPECT_FALSE(response == NULL); EXPECT_TRUE(response->auth_challenge.get() == NULL); - EXPECT_EQ(100, response->headers->GetContentLength()); + EXPECT_EQ(5, response->headers->GetContentLength()); } // Test the request-challenge-retry sequence for basic auth, over a keep-alive // connection and with no response body to drain. TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAliveNoBody) { SessionDependencies session_deps; - scoped_ptr<HttpTransaction> trans( - new HttpNetworkTransaction(CreateSession(&session_deps))); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); HttpRequestInfo request; request.method = "GET"; @@ -1140,8 +1186,8 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAliveNoBody) { // Lastly, the server responds with the actual content. MockRead("HTTP/1.1 200 OK\r\n"), MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), - MockRead("Content-Length: 100\r\n\r\n"), - MockRead(false, OK), + MockRead("Content-Length: 5\r\n\r\n"), + MockRead("hello"), }; StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), @@ -1150,6 +1196,7 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAliveNoBody) { TestCompletionCallback callback1; + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); int rv = trans->Start(&request, &callback1, BoundNetLog()); EXPECT_EQ(ERR_IO_PENDING, rv); @@ -1177,15 +1224,14 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAliveNoBody) { response = trans->GetResponseInfo(); EXPECT_FALSE(response == NULL); EXPECT_TRUE(response->auth_challenge.get() == NULL); - EXPECT_EQ(100, response->headers->GetContentLength()); + EXPECT_EQ(5, response->headers->GetContentLength()); } // Test the request-challenge-retry sequence for basic auth, over a keep-alive // connection and with a large response body to drain. TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAliveLargeBody) { SessionDependencies session_deps; - scoped_ptr<HttpTransaction> trans( - new HttpNetworkTransaction(CreateSession(&session_deps))); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); HttpRequestInfo request; request.method = "GET"; @@ -1221,8 +1267,8 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAliveLargeBody) { // Lastly, the server responds with the actual content. MockRead("HTTP/1.1 200 OK\r\n"), MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), - MockRead("Content-Length: 100\r\n\r\n"), - MockRead(false, OK), + MockRead("Content-Length: 5\r\n\r\n"), + MockRead("hello"), }; StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), @@ -1231,6 +1277,7 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAliveLargeBody) { TestCompletionCallback callback1; + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); int rv = trans->Start(&request, &callback1, BoundNetLog()); EXPECT_EQ(ERR_IO_PENDING, rv); @@ -1258,15 +1305,14 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAliveLargeBody) { response = trans->GetResponseInfo(); EXPECT_FALSE(response == NULL); EXPECT_TRUE(response->auth_challenge.get() == NULL); - EXPECT_EQ(100, response->headers->GetContentLength()); + EXPECT_EQ(5, response->headers->GetContentLength()); } // Test the request-challenge-retry sequence for basic auth, over a keep-alive // connection, but the server gets impatient and closes the connection. TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAliveImpatientServer) { SessionDependencies session_deps; - scoped_ptr<HttpTransaction> trans( - new HttpNetworkTransaction(CreateSession(&session_deps))); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); HttpRequestInfo request; request.method = "GET"; @@ -1309,8 +1355,8 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAliveImpatientServer) { MockRead data_reads2[] = { MockRead("HTTP/1.1 200 OK\r\n"), MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), - MockRead("Content-Length: 100\r\n\r\n"), - MockRead(false, OK), + MockRead("Content-Length: 5\r\n\r\n"), + MockRead("hello"), }; StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), @@ -1322,6 +1368,7 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAliveImpatientServer) { TestCompletionCallback callback1; + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); int rv = trans->Start(&request, &callback1, BoundNetLog()); EXPECT_EQ(ERR_IO_PENDING, rv); @@ -1349,7 +1396,7 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthKeepAliveImpatientServer) { response = trans->GetResponseInfo(); ASSERT_FALSE(response == NULL); EXPECT_TRUE(response->auth_challenge.get() == NULL); - EXPECT_EQ(100, response->headers->GetContentLength()); + EXPECT_EQ(5, response->headers->GetContentLength()); } // Test the request-challenge-retry sequence for basic auth, over a connection @@ -1361,8 +1408,6 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthProxyNoKeepAlive) { session_deps.net_log = log.bound().net_log(); scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); - scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); - HttpRequestInfo request; request.method = "GET"; request.url = GURL("https://www.google.com/"); @@ -1399,8 +1444,8 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthProxyNoKeepAlive) { MockRead("HTTP/1.1 200 OK\r\n"), MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), - MockRead("Content-Length: 100\r\n\r\n"), - MockRead(false, OK), + MockRead("Content-Length: 5\r\n\r\n"), + MockRead(false, "hello"), }; StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), @@ -1411,6 +1456,8 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthProxyNoKeepAlive) { TestCompletionCallback callback1; + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + int rv = trans->Start(&request, &callback1, log.bound()); EXPECT_EQ(ERR_IO_PENDING, rv); @@ -1450,11 +1497,14 @@ TEST_F(HttpNetworkTransactionTest, BasicAuthProxyNoKeepAlive) { EXPECT_TRUE(response->headers->IsKeepAlive()); EXPECT_EQ(200, response->headers->response_code()); - EXPECT_EQ(100, response->headers->GetContentLength()); + EXPECT_EQ(5, response->headers->GetContentLength()); EXPECT_TRUE(HttpVersion(1, 1) == response->headers->GetHttpVersion()); // The password prompt info should not be set. EXPECT_TRUE(response->auth_challenge.get() == NULL); + + trans.reset(); + session->FlushSocketPools(); } // Test the request-challenge-retry sequence for basic auth, over a keep-alive @@ -1676,8 +1726,6 @@ TEST_F(HttpNetworkTransactionTest, HttpsProxyGet) { session_deps.net_log = log.bound().net_log(); scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); - scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); - HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); @@ -1704,6 +1752,8 @@ TEST_F(HttpNetworkTransactionTest, HttpsProxyGet) { TestCompletionCallback callback1; + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + int rv = trans->Start(&request, &callback1, log.bound()); EXPECT_EQ(ERR_IO_PENDING, rv); @@ -1730,8 +1780,6 @@ TEST_F(HttpNetworkTransactionTest, HttpsProxySpdyGet) { session_deps.net_log = log.bound().net_log(); scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); - scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); - HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); @@ -1765,6 +1813,8 @@ TEST_F(HttpNetworkTransactionTest, HttpsProxySpdyGet) { TestCompletionCallback callback1; + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + int rv = trans->Start(&request, &callback1, log.bound()); EXPECT_EQ(ERR_IO_PENDING, rv); @@ -1789,8 +1839,6 @@ TEST_F(HttpNetworkTransactionTest, HttpsProxyAuthRetry) { session_deps.net_log = log.bound().net_log(); scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); - scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); - HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); @@ -1834,6 +1882,8 @@ TEST_F(HttpNetworkTransactionTest, HttpsProxyAuthRetry) { TestCompletionCallback callback1; + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + int rv = trans->Start(&request, &callback1, log.bound()); EXPECT_EQ(ERR_IO_PENDING, rv); @@ -1880,8 +1930,6 @@ void HttpNetworkTransactionTest::ConnectStatusHelperWithExpectedStatus( scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); - scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); - HttpRequestInfo request; request.method = "GET"; request.url = GURL("https://www.google.com/"); @@ -1907,6 +1955,8 @@ void HttpNetworkTransactionTest::ConnectStatusHelperWithExpectedStatus( TestCompletionCallback callback; + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + int rv = trans->Start(&request, &callback, BoundNetLog()); EXPECT_EQ(ERR_IO_PENDING, rv); @@ -2227,8 +2277,7 @@ TEST_F(HttpNetworkTransactionTest, NTLMAuth1) { HttpAuthHandlerNTLM::ScopedProcSetter proc_setter(MockGenerateRandom1, MockGetHostName); SessionDependencies session_deps; - scoped_ptr<HttpTransaction> trans( - new HttpNetworkTransaction(CreateSession(&session_deps))); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); HttpRequestInfo request; request.method = "GET"; @@ -2309,6 +2358,8 @@ TEST_F(HttpNetworkTransactionTest, NTLMAuth1) { TestCompletionCallback callback1; + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + int rv = trans->Start(&request, &callback1, BoundNetLog()); EXPECT_EQ(ERR_IO_PENDING, rv); @@ -2354,8 +2405,7 @@ TEST_F(HttpNetworkTransactionTest, NTLMAuth2) { HttpAuthHandlerNTLM::ScopedProcSetter proc_setter(MockGenerateRandom2, MockGetHostName); SessionDependencies session_deps; - scoped_ptr<HttpTransaction> trans( - new HttpNetworkTransaction(CreateSession(&session_deps))); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); HttpRequestInfo request; request.method = "GET"; @@ -2487,6 +2537,8 @@ TEST_F(HttpNetworkTransactionTest, NTLMAuth2) { TestCompletionCallback callback1; + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + int rv = trans->Start(&request, &callback1, BoundNetLog()); EXPECT_EQ(ERR_IO_PENDING, rv); @@ -5134,8 +5186,7 @@ TEST_F(HttpNetworkTransactionTest, ConnectionClosedAfterStartOfHeaders) { // restart does the right thing. TEST_F(HttpNetworkTransactionTest, DrainResetOK) { SessionDependencies session_deps; - scoped_ptr<HttpTransaction> trans( - new HttpNetworkTransaction(CreateSession(&session_deps))); + scoped_refptr<HttpNetworkSession> session = CreateSession(&session_deps); HttpRequestInfo request; request.method = "GET"; @@ -5184,6 +5235,8 @@ TEST_F(HttpNetworkTransactionTest, DrainResetOK) { TestCompletionCallback callback1; + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + int rv = trans->Start(&request, &callback1, BoundNetLog()); EXPECT_EQ(ERR_IO_PENDING, rv); @@ -5488,8 +5541,6 @@ TEST_F(HttpNetworkTransactionTest, UnreadableUploadFileAfterAuthRestart) { // Tests that changes to Auth realms are treated like auth rejections. TEST_F(HttpNetworkTransactionTest, ChangeAuthRealms) { SessionDependencies session_deps; - scoped_ptr<HttpTransaction> trans( - new HttpNetworkTransaction(CreateSession(&session_deps))); HttpRequestInfo request; request.method = "GET"; @@ -5552,8 +5603,9 @@ TEST_F(HttpNetworkTransactionTest, ChangeAuthRealms) { MockRead data_reads4[] = { MockRead("HTTP/1.1 200 OK\r\n" "Content-Type: text/html; charset=iso-8859-1\r\n" - "Content-Length: 100\r\n" - "\r\n"), + "Content-Length: 5\r\n" + "\r\n" + "hello"), }; StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), @@ -5571,6 +5623,9 @@ TEST_F(HttpNetworkTransactionTest, ChangeAuthRealms) { TestCompletionCallback callback1; + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + // Issue the first request with Authorize headers. There should be a // password prompt for first_realm waiting to be filled in after the // transaction completes. @@ -6195,9 +6250,9 @@ TEST_F(HttpNetworkTransactionTest, // specifies both the configuration for the test as well as the expectations // for the results. TEST_F(HttpNetworkTransactionTest, GenerateAuthToken) { - const char* kServer = "http://www.example.com"; - const char* kSecureServer = "https://www.example.com"; - const char* kProxy = "myproxy:70"; + static const char kServer[] = "http://www.example.com"; + static const char kSecureServer[] = "https://www.example.com"; + static const char kProxy[] = "myproxy:70"; const int kAuthErr = ERR_INVALID_AUTH_CREDENTIALS; enum AuthTiming { @@ -6482,7 +6537,6 @@ TEST_F(HttpNetworkTransactionTest, GenerateAuthToken) { }; SessionDependencies session_deps; - scoped_refptr<HttpNetworkSession> session = CreateSession(&session_deps); HttpAuthHandlerMock::Factory* auth_factory( new HttpAuthHandlerMock::Factory()); session_deps.http_auth_handler_factory.reset(auth_factory); @@ -6529,8 +6583,8 @@ TEST_F(HttpNetworkTransactionTest, GenerateAuthToken) { request.url = GURL(test_config.server_url); request.load_flags = 0; - scoped_ptr<HttpTransaction> trans( - new HttpNetworkTransaction(CreateSession(&session_deps))); + scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); + HttpNetworkTransaction trans(CreateSession(&session_deps)); for (int round = 0; round < test_config.num_auth_rounds; ++round) { const TestRound& read_write_round = test_config.rounds[round]; @@ -6565,16 +6619,16 @@ TEST_F(HttpNetworkTransactionTest, GenerateAuthToken) { TestCompletionCallback callback; int rv; if (round == 0) { - rv = trans->Start(&request, &callback, BoundNetLog()); + rv = trans.Start(&request, &callback, BoundNetLog()); } else { - rv = trans->RestartWithAuth(kFoo, kBar, &callback); + rv = trans.RestartWithAuth(kFoo, kBar, &callback); } if (rv == ERR_IO_PENDING) rv = callback.WaitForResult(); // Compare results with expected data. EXPECT_EQ(read_write_round.expected_rv, rv); - const HttpResponseInfo* response = trans->GetResponseInfo(); + const HttpResponseInfo* response = trans.GetResponseInfo(); if (read_write_round.expected_rv == OK) { EXPECT_FALSE(response == NULL); } else { @@ -6589,9 +6643,6 @@ TEST_F(HttpNetworkTransactionTest, GenerateAuthToken) { } } } - - // Flush the idle socket before the HttpNetworkTransaction goes out of scope. - session->FlushSocketPools(); } TEST_F(HttpNetworkTransactionTest, MultiRoundAuth) { @@ -7120,8 +7171,6 @@ TEST_F(HttpNetworkTransactionTest, ProxyGet) { session_deps.net_log = log.bound().net_log(); scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); - scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); - HttpRequestInfo request; request.method = "GET"; request.url = GURL("http://www.google.com/"); @@ -7145,6 +7194,8 @@ TEST_F(HttpNetworkTransactionTest, ProxyGet) { TestCompletionCallback callback1; + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + int rv = trans->Start(&request, &callback1, log.bound()); EXPECT_EQ(ERR_IO_PENDING, rv); @@ -7168,8 +7219,6 @@ TEST_F(HttpNetworkTransactionTest, ProxyTunnelGet) { session_deps.net_log = log.bound().net_log(); scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); - scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); - HttpRequestInfo request; request.method = "GET"; request.url = GURL("https://www.google.com/"); @@ -7202,6 +7251,8 @@ TEST_F(HttpNetworkTransactionTest, ProxyTunnelGet) { TestCompletionCallback callback1; + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + int rv = trans->Start(&request, &callback1, log.bound()); EXPECT_EQ(ERR_IO_PENDING, rv); @@ -7233,8 +7284,6 @@ TEST_F(HttpNetworkTransactionTest, ProxyTunnelGetHangup) { session_deps.net_log = log.bound().net_log(); scoped_refptr<HttpNetworkSession> session(CreateSession(&session_deps)); - scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); - HttpRequestInfo request; request.method = "GET"; request.url = GURL("https://www.google.com/"); @@ -7264,6 +7313,8 @@ TEST_F(HttpNetworkTransactionTest, ProxyTunnelGetHangup) { TestCompletionCallback callback1; + scoped_ptr<HttpTransaction> trans(new HttpNetworkTransaction(session)); + int rv = trans->Start(&request, &callback1, log.bound()); EXPECT_EQ(ERR_IO_PENDING, rv); diff --git a/net/http/http_response_body_drainer.cc b/net/http/http_response_body_drainer.cc new file mode 100644 index 0000000..599c534 --- /dev/null +++ b/net/http/http_response_body_drainer.cc @@ -0,0 +1,119 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http/http_response_body_drainer.h" + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/http/http_stream.h" + +namespace net { + +HttpResponseBodyDrainer::HttpResponseBodyDrainer(HttpStream* stream) + : stream_(stream), + next_state_(STATE_NONE), + total_read_(0), + ALLOW_THIS_IN_INITIALIZER_LIST( + io_callback_(this, &HttpResponseBodyDrainer::OnIOComplete)), + user_callback_(NULL) {} + +HttpResponseBodyDrainer::~HttpResponseBodyDrainer() {} + +void HttpResponseBodyDrainer::Start() { + read_buf_ = new IOBuffer(kDrainBodyBufferSize); + next_state_ = STATE_DRAIN_RESPONSE_BODY; + int rv = DoLoop(OK); + + if (rv == ERR_IO_PENDING) { + timer_.Start(base::TimeDelta::FromSeconds(kTimeoutInSeconds), + this, + &HttpResponseBodyDrainer::OnTimerFired); + return; + } + + Finish(rv); +} + +int HttpResponseBodyDrainer::DoLoop(int result) { + DCHECK_NE(next_state_, STATE_NONE); + + int rv = result; + do { + State state = next_state_; + next_state_ = STATE_NONE; + switch (state) { + case STATE_DRAIN_RESPONSE_BODY: + DCHECK_EQ(OK, rv); + rv = DoDrainResponseBody(); + break; + case STATE_DRAIN_RESPONSE_BODY_COMPLETE: + rv = DoDrainResponseBodyComplete(rv); + break; + default: + NOTREACHED() << "bad state"; + rv = ERR_UNEXPECTED; + break; + } + } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE); + + return rv; +} + +int HttpResponseBodyDrainer::DoDrainResponseBody() { + next_state_ = STATE_DRAIN_RESPONSE_BODY_COMPLETE; + + return stream_->ReadResponseBody( + read_buf_, kDrainBodyBufferSize - total_read_, + &io_callback_); +} + +int HttpResponseBodyDrainer::DoDrainResponseBodyComplete(int result) { + DCHECK_NE(ERR_IO_PENDING, result); + + if (result < 0) + return result; + + if (result == 0) + return ERR_CONNECTION_CLOSED; + + total_read_ += result; + if (stream_->IsResponseBodyComplete()) + return OK; + + DCHECK_LE(total_read_, kDrainBodyBufferSize); + if (total_read_ >= kDrainBodyBufferSize) + return ERR_RESPONSE_BODY_TOO_BIG_TO_DRAIN; + + next_state_ = STATE_DRAIN_RESPONSE_BODY; + return OK; +} + +void HttpResponseBodyDrainer::OnIOComplete(int result) { + int rv = DoLoop(result); + if (rv != ERR_IO_PENDING) { + timer_.Stop(); + Finish(rv); + } +} + +void HttpResponseBodyDrainer::OnTimerFired() { + Finish(ERR_TIMED_OUT); +} + +void HttpResponseBodyDrainer::Finish(int result) { + DCHECK_NE(ERR_IO_PENDING, result); + + if (result < 0) { + stream_->Close(true /* no keep-alive */); + } else { + DCHECK_EQ(OK, result); + stream_->Close(false /* keep-alive */); + } + + delete this; +} + +} // namespace net diff --git a/net/http/http_response_body_drainer.h b/net/http/http_response_body_drainer.h new file mode 100644 index 0000000..dbcdd99 --- /dev/null +++ b/net/http/http_response_body_drainer.h @@ -0,0 +1,66 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_HTTP_HTTP_RESPONSE_BODY_DRAINER_H_ +#define NET_HTTP_HTTP_RESPONSE_BODY_DRAINER_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/ref_counted.h" +#include "base/scoped_ptr.h" +#include "base/timer.h" +#include "net/base/completion_callback.h" + +namespace net { + +class HttpStream; +class IOBuffer; + +class HttpResponseBodyDrainer { + public: + // The size in bytes of the buffer we use to drain the response body that + // we want to throw away. The response body is typically a small page just a + // few hundred bytes long. We set a limit to prevent it from taking too long, + // since we may as well just create a new socket then. + enum { kDrainBodyBufferSize = 16384 }; + enum { kTimeoutInSeconds = 5 }; + + explicit HttpResponseBodyDrainer(HttpStream* stream); + + // Starts reading the body until completion, or we hit the buffer limit, or we + // timeout. After Start(), |this| will eventually delete itself. + void Start(); + + private: + enum State { + STATE_DRAIN_RESPONSE_BODY, + STATE_DRAIN_RESPONSE_BODY_COMPLETE, + STATE_NONE, + }; + + ~HttpResponseBodyDrainer(); + + int DoLoop(int result); + + int DoDrainResponseBody(); + int DoDrainResponseBodyComplete(int result); + + void OnIOComplete(int result); + void OnTimerFired(); + void Finish(int result); + + const scoped_ptr<HttpStream> stream_; + State next_state_; + scoped_refptr<IOBuffer> read_buf_; + int total_read_; + CompletionCallbackImpl<HttpResponseBodyDrainer> io_callback_; + CompletionCallback* user_callback_; + base::OneShotTimer<HttpResponseBodyDrainer> timer_; + + DISALLOW_COPY_AND_ASSIGN(HttpResponseBodyDrainer); +}; + +} // namespace net + +#endif // NET_HTTP_HTTP_RESPONSE_BODY_DRAINER_H_ diff --git a/net/http/http_response_body_drainer_unittest.cc b/net/http/http_response_body_drainer_unittest.cc new file mode 100644 index 0000000..b8e0934 --- /dev/null +++ b/net/http/http_response_body_drainer_unittest.cc @@ -0,0 +1,216 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/http/http_response_body_drainer.h" + +#include <cstring> + +#include "base/compiler_specific.h" +#include "base/message_loop.h" +#include "base/task.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/base/test_completion_callback.h" +#include "net/http/http_stream.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +namespace { + +const int kMagicChunkSize = 1024; +COMPILE_ASSERT( + (HttpResponseBodyDrainer::kDrainBodyBufferSize % kMagicChunkSize) == 0, + chunk_size_needs_to_divide_evenly_into_buffer_size); + +class CloseResultWaiter { + public: + CloseResultWaiter() + : result_(false), + have_result_(false), + waiting_for_result_(false) {} + + int WaitForResult() { + DCHECK(!waiting_for_result_); + while (!have_result_) { + waiting_for_result_ = true; + MessageLoop::current()->Run(); + waiting_for_result_ = false; + } + return result_; + } + + void set_result(bool result) { + result_ = result; + have_result_ = true; + if (waiting_for_result_) + MessageLoop::current()->Quit(); + } + + private: + int result_; + bool have_result_; + bool waiting_for_result_; + + DISALLOW_COPY_AND_ASSIGN(CloseResultWaiter); +}; + +class MockHttpStream : public HttpStream { + public: + MockHttpStream(CloseResultWaiter* result_waiter) + : result_waiter_(result_waiter), + user_callback_(NULL), + closed_(false), + stall_reads_forever_(false), + num_chunks_(0), + is_complete_(false), + ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {} + virtual ~MockHttpStream() {} + + // HttpStream implementation: + virtual int InitializeStream(const HttpRequestInfo* request_info, + const BoundNetLog& net_log, + CompletionCallback* callback) { + return ERR_UNEXPECTED; + } + virtual int SendRequest(const std::string& request_headers, + UploadDataStream* request_body, + HttpResponseInfo* response, + CompletionCallback* callback) { + return ERR_UNEXPECTED; + } + virtual uint64 GetUploadProgress() const { return 0; } + virtual int ReadResponseHeaders(CompletionCallback* callback) { + return ERR_UNEXPECTED; + } + virtual const HttpResponseInfo* GetResponseInfo() const { return NULL; } + + virtual bool CanFindEndOfResponse() const { return true; } + virtual bool IsMoreDataBuffered() const { return false; } + virtual bool IsConnectionReused() const { return false; } + virtual void SetConnectionReused() {} + virtual void GetSSLInfo(SSLInfo* ssl_info) {} + virtual void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) {} + + // Mocked API + virtual int ReadResponseBody(IOBuffer* buf, int buf_len, + CompletionCallback* callback); + virtual void Close(bool not_reusable) { + DCHECK(!closed_); + closed_ = true; + result_waiter_->set_result(not_reusable); + } + virtual bool IsResponseBodyComplete() const { return is_complete_; } + + // Methods to tweak/observer mock behavior: + void StallReadsForever() { stall_reads_forever_ = true; } + + void set_num_chunks(int num_chunks) { num_chunks_ = num_chunks; } + + private: + void CompleteRead(); + + bool closed() const { return closed_; } + + CloseResultWaiter* const result_waiter_; + scoped_refptr<IOBuffer> user_buf_; + CompletionCallback* user_callback_; + bool closed_; + bool stall_reads_forever_; + int num_chunks_; + bool is_complete_; + ScopedRunnableMethodFactory<MockHttpStream> method_factory_; +}; + +int MockHttpStream::ReadResponseBody( + IOBuffer* buf, int buf_len, CompletionCallback* callback) { + DCHECK(callback); + DCHECK(!user_callback_); + DCHECK(buf); + + if (stall_reads_forever_) + return ERR_IO_PENDING; + + if (num_chunks_ == 0) + return ERR_UNEXPECTED; + + if (buf_len > kMagicChunkSize && num_chunks_ > 1) { + user_buf_ = buf; + user_callback_ = callback; + MessageLoop::current()->PostTask( + FROM_HERE, + method_factory_.NewRunnableMethod(&MockHttpStream::CompleteRead)); + return ERR_IO_PENDING; + } + + num_chunks_--; + if (!num_chunks_) + is_complete_ = true; + + return buf_len; +} + +void MockHttpStream::CompleteRead() { + CompletionCallback* callback = user_callback_; + std::memset(user_buf_->data(), 1, kMagicChunkSize); + user_buf_ = NULL; + user_callback_ = NULL; + num_chunks_--; + if (!num_chunks_) + is_complete_ = true; + callback->Run(kMagicChunkSize); +} + +class HttpResponseBodyDrainerTest : public testing::Test { + protected: + HttpResponseBodyDrainerTest() + : mock_stream_(new MockHttpStream(&result_waiter_)), + drainer_(new HttpResponseBodyDrainer(mock_stream_)) {} + ~HttpResponseBodyDrainerTest() {} + + CloseResultWaiter result_waiter_; + MockHttpStream* const mock_stream_; // Owned by |drainer_|. + HttpResponseBodyDrainer* const drainer_; // Deletes itself. +}; + +TEST_F(HttpResponseBodyDrainerTest, DrainBodySyncOK) { + mock_stream_->set_num_chunks(1); + drainer_->Start(); + EXPECT_FALSE(result_waiter_.WaitForResult()); +} + +TEST_F(HttpResponseBodyDrainerTest, DrainBodyAsyncOK) { + mock_stream_->set_num_chunks(3); + drainer_->Start(); + EXPECT_FALSE(result_waiter_.WaitForResult()); +} + +TEST_F(HttpResponseBodyDrainerTest, DrainBodySizeEqualsDrainBuffer) { + mock_stream_->set_num_chunks( + HttpResponseBodyDrainer::kDrainBodyBufferSize / kMagicChunkSize); + drainer_->Start(); + EXPECT_FALSE(result_waiter_.WaitForResult()); +} + +TEST_F(HttpResponseBodyDrainerTest, DrainBodyTimeOut) { + mock_stream_->set_num_chunks(2); + mock_stream_->StallReadsForever(); + drainer_->Start(); + EXPECT_TRUE(result_waiter_.WaitForResult()); +} + +TEST_F(HttpResponseBodyDrainerTest, DrainBodyTooLarge) { + TestCompletionCallback callback; + int too_many_chunks = + HttpResponseBodyDrainer::kDrainBodyBufferSize / kMagicChunkSize; + too_many_chunks += 1; // Now it's too large. + + mock_stream_->set_num_chunks(too_many_chunks); + drainer_->Start(); + EXPECT_TRUE(result_waiter_.WaitForResult()); +} + +} // namespace + +} // namespace net diff --git a/net/http/http_util.cc b/net/http/http_util.cc index 83c17ab..4b52cc3 100644 --- a/net/http/http_util.cc +++ b/net/http/http_util.cc @@ -9,11 +9,15 @@ #include <algorithm> +#include "base/basictypes.h" #include "base/logging.h" #include "base/string_number_conversions.h" #include "base/string_piece.h" #include "base/string_util.h" +#include "net/base/net_errors.h" #include "net/base/net_util.h" +#include "net/http/http_response_body_drainer.h" +#include "net/http/http_stream.h" using std::string; @@ -695,4 +699,10 @@ bool HttpUtil::ValuesIterator::GetNext() { return false; } +void HttpUtil::DrainStreamBodyAndClose(HttpStream* stream) { + HttpResponseBodyDrainer* drainer = new HttpResponseBodyDrainer(stream); + drainer->Start(); + // |drainer| will delete itself. +} + } // namespace net diff --git a/net/http/http_util.h b/net/http/http_util.h index 33da33d..3179782 100644 --- a/net/http/http_util.h +++ b/net/http/http_util.h @@ -10,6 +10,7 @@ #include "base/string_tokenizer.h" #include "googleurl/src/gurl.h" +#include "net/base/completion_callback.h" #include "net/http/http_byte_range.h" // This is a macro to support extending this string literal at compile time. @@ -18,6 +19,8 @@ namespace net { +class HttpStream; + class HttpUtil { public: // Returns the absolute path of the URL, to be used for the http request. @@ -247,6 +250,10 @@ class HttpUtil { std::string::const_iterator value_begin_; std::string::const_iterator value_end_; }; + + // Attempts to read all of the response body of |stream|. Closes |stream| and + // deletes it when complete. + static void DrainStreamBodyAndClose(HttpStream* stream); }; } // namespace net diff --git a/net/net.gyp b/net/net.gyp index 10a4d8d..080b5b0 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -429,6 +429,8 @@ 'http/http_request_headers.cc', 'http/http_request_headers.h', 'http/http_request_info.h', + 'http/http_response_body_drainer.cc', + 'http/http_response_body_drainer.h', 'http/http_response_headers.cc', 'http/http_response_headers.h', 'http/http_response_info.cc', @@ -806,6 +808,7 @@ 'http/http_network_transaction_unittest.cc', 'http/http_proxy_client_socket_pool_unittest.cc', 'http/http_request_headers_unittest.cc', + 'http/http_response_body_drainer_unittest.cc', 'http/http_response_headers_unittest.cc', 'http/http_transaction_unittest.cc', 'http/http_transaction_unittest.h', diff --git a/net/spdy/spdy_network_transaction_unittest.cc b/net/spdy/spdy_network_transaction_unittest.cc index 6d5d644..927d2aa 100644 --- a/net/spdy/spdy_network_transaction_unittest.cc +++ b/net/spdy/spdy_network_transaction_unittest.cc @@ -2296,18 +2296,15 @@ TEST_P(SpdyNetworkTransactionTest, RedirectServerPush) { "301 Moved Permanently", "http://www.foo.com/index.php", "http://www.foo.com/index.php")); scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); - scoped_ptr<spdy::SpdyFrame> res( - ConstructSpdyRstStream(2, spdy::CANCEL)); MockWrite writes[] = { CreateMockWrite(*req, 1), - CreateMockWrite(*res, 6), }; MockRead reads[] = { CreateMockRead(*resp, 2), CreateMockRead(*rep, 3), CreateMockRead(*body, 4), MockRead(true, ERR_IO_PENDING, 5), // Force a pause - MockRead(true, 0, 0, 7) // EOF + MockRead(true, 0, 0, 6) // EOF }; // Setup writes/reads to www.foo.com |