summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwillchan@chromium.org <willchan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-09-19 23:05:07 +0000
committerwillchan@chromium.org <willchan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-09-19 23:05:07 +0000
commitca8e0d19712af797462689232c6b4b624fc5de6d (patch)
tree068d955703d91519d223885f044277b0f1c9cdc4
parentabb34747bac795574da01e38dd3fada5471fac39 (diff)
downloadchromium_src-ca8e0d19712af797462689232c6b4b624fc5de6d.zip
chromium_src-ca8e0d19712af797462689232c6b4b624fc5de6d.tar.gz
chromium_src-ca8e0d19712af797462689232c6b4b624fc5de6d.tar.bz2
Add HttpResponseBodyDrainer. Use it for unfinished HttpStreams.
Hopefully this will improve our reuse of sockets, and thus performance, since many redirects have response bodies, so we end up discarding those sockets rather than draining the bodies and reusing the socket. Fix a bunch of tests since we now try to read from the socket in HttpNetworkTransaction's destructor, which often executes after the StaticSocketDataProvider has already been destroyed, so we end up trying to invoke pure virtuals via stale pointers. BUG=54277 TEST= 1) Start up chrome with an empty cache (I just use --user-data-dir=/tmp/newprofile). 2) Open up about:net-internals, browse to its socket tab. 3) In a separate Chrome tab, open up http://google.com (NOT www.google.com). NOTE: This will get a 301 redirect with a response body for us to drain. Previously we wouldn't drain it, so we wouldn't reuse the socket. 4) In the about:net-internals's socket page, wait for it to reload (or force a reload). Look for the google.com row under tcp_socket_pool. If it doesn't exist, then that means it failed. If it does exist _and_ the idle column says '1', then it worked. Review URL: http://codereview.chromium.org/3293015 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@59908 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--net/base/net_error_list.h3
-rw-r--r--net/http/http_network_transaction.cc55
-rw-r--r--net/http/http_network_transaction_unittest.cc209
-rw-r--r--net/http/http_response_body_drainer.cc119
-rw-r--r--net/http/http_response_body_drainer.h66
-rw-r--r--net/http/http_response_body_drainer_unittest.cc208
-rw-r--r--net/http/http_util.cc10
-rw-r--r--net/http/http_util.h7
-rw-r--r--net/net.gyp3
-rw-r--r--net/spdy/spdy_network_transaction_unittest.cc5
10 files changed, 582 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..c857106
--- /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_ + total_read_, 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..41b4225
--- /dev/null
+++ b/net/http/http_response_body_drainer_unittest.cc
@@ -0,0 +1,208 @@
+// 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/message_loop.h"
+#include "base/task.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_;
+ 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_);
+
+ if (stall_reads_forever_)
+ return ERR_IO_PENDING;
+
+ if (num_chunks_ == 0)
+ return ERR_UNEXPECTED;
+
+ if (buf_len > kMagicChunkSize && num_chunks_ > 1) {
+ 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_;
+ 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