summaryrefslogtreecommitdiffstats
path: root/net/http
diff options
context:
space:
mode:
authorwillchan@chromium.org <willchan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-09-21 18:08:50 +0000
committerwillchan@chromium.org <willchan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-09-21 18:08:50 +0000
commit0b0bf036e2c2722b5d31162ad620b450763fa352 (patch)
treebbeeb3eaaa34df96d3f947d63280a93746837536 /net/http
parent78859c7d87db4433d068533b104c7c1addf7e7eb (diff)
downloadchromium_src-0b0bf036e2c2722b5d31162ad620b450763fa352.zip
chromium_src-0b0bf036e2c2722b5d31162ad620b450763fa352.tar.gz
chromium_src-0b0bf036e2c2722b5d31162ad620b450763fa352.tar.bz2
Reland r59910 - Add HttpResponseBodyDrainer. Use it for unfinished HttpStreams."
There's one simple fix. wtc had asked me to increment the buffer so we keep reading into new memory while draining the body. So I added |total_read_| to |read_buf_|. The problem is |read_buf_| is an IOBuffer*, not a char*, so I'm causing us to read into raw heap memory. Crashes ensue. My unit tests didn't catch it because they never actually read data. I've fixed that by doing a memset(). I've fixed the problem by not bothering to increment the read index, since that would require throwing away IOBuffers or some intrusive modifications to IOBuffer. BUG=54277 TEST=HttpResponseBodyDrainerTest.* Also see the manual testing instructions in r59910. Review URL: http://codereview.chromium.org/3449014 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@60075 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/http')
-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.cc216
-rw-r--r--net/http/http_util.cc10
-rw-r--r--net/http/http_util.h7
7 files changed, 583 insertions, 99 deletions
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