Index: net/http/http_stream_parser_unittest.cc |
diff --git a/net/http/http_stream_parser_unittest.cc b/net/http/http_stream_parser_unittest.cc |
index 01d4e120ab1b583bae90aef127db07654e16224b..a30cb0091cdb225e21ede3fc1df05a43a7e60008 100644 |
--- a/net/http/http_stream_parser_unittest.cc |
+++ b/net/http/http_stream_parser_unittest.cc |
@@ -40,6 +40,24 @@ const size_t kOutputSize = 1024; // Just large enough for this test. |
const size_t kMaxPayloadSize = |
kOutputSize - HttpStreamParser::kChunkHeaderFooterSize; |
+// Helper method to create a connected ClientSocketHandle using |data|. |
+// Modifies |data|. |
+scoped_ptr<ClientSocketHandle> CreateConnectedSocketHandle( |
+ DeterministicSocketData* data) { |
+ data->set_connect_data(MockConnect(SYNCHRONOUS, OK)); |
+ |
+ scoped_ptr<DeterministicMockTCPClientSocket> transport( |
+ new DeterministicMockTCPClientSocket(nullptr, data)); |
+ data->set_delegate(transport->AsWeakPtr()); |
+ |
+ TestCompletionCallback callback; |
+ EXPECT_EQ(OK, transport->Connect(callback.callback())); |
+ |
+ scoped_ptr<ClientSocketHandle> socket_handle(new ClientSocketHandle); |
+ socket_handle->SetSocket(transport.Pass()); |
+ return socket_handle.Pass(); |
+} |
+ |
// The empty payload is how the last chunk is encoded. |
TEST(HttpStreamParser, EncodeChunk_EmptyPayload) { |
char output[kOutputSize]; |
@@ -184,11 +202,174 @@ TEST(HttpStreamParser, ShouldMergeRequestHeadersAndBody_LargeBodyInMemory) { |
} |
// Test to ensure the HttpStreamParser state machine does not get confused |
+// when sending a request with a chunked body with only one chunk that becomes |
+// available asynchronously. |
+TEST(HttpStreamParser, AsyncSingleChunkAndAsyncSocket) { |
+ static const char kChunk[] = "Chunk"; |
+ |
+ MockWrite writes[] = { |
+ MockWrite(ASYNC, 0, |
+ "GET /one.html HTTP/1.1\r\n" |
+ "Transfer-Encoding: chunked\r\n\r\n"), |
+ MockWrite(ASYNC, 1, "5\r\nChunk\r\n"), |
+ MockWrite(ASYNC, 2, "0\r\n\r\n"), |
+ }; |
+ |
+ // The size of the response body, as reflected in the Content-Length of the |
+ // MockRead below. |
+ static const int kBodySize = 8; |
+ |
+ MockRead reads[] = { |
+ MockRead(ASYNC, 3, "HTTP/1.1 200 OK\r\n"), |
+ MockRead(ASYNC, 4, "Content-Length: 8\r\n\r\n"), |
+ MockRead(ASYNC, 5, "one.html"), |
+ MockRead(SYNCHRONOUS, 0, 6), // EOF |
+ }; |
+ |
+ ChunkedUploadDataStream upload_stream(0); |
+ ASSERT_EQ(OK, upload_stream.Init(TestCompletionCallback().callback())); |
+ |
+ DeterministicSocketData data(reads, arraysize(reads), writes, |
+ arraysize(writes)); |
+ scoped_ptr<ClientSocketHandle> socket_handle = |
+ CreateConnectedSocketHandle(&data); |
+ |
+ HttpRequestInfo request_info; |
+ request_info.method = "GET"; |
+ request_info.url = GURL("http://localhost"); |
+ request_info.upload_data_stream = &upload_stream; |
+ |
+ scoped_refptr<GrowableIOBuffer> read_buffer(new GrowableIOBuffer); |
+ HttpStreamParser parser(socket_handle.get(), &request_info, read_buffer.get(), |
+ BoundNetLog()); |
+ |
+ HttpRequestHeaders request_headers; |
+ request_headers.SetHeader("Transfer-Encoding", "chunked"); |
+ |
+ HttpResponseInfo response_info; |
+ TestCompletionCallback callback; |
+ // This will attempt to Write() the initial request and headers, which will |
+ // complete asynchronously. |
+ ASSERT_EQ(ERR_IO_PENDING, |
+ parser.SendRequest("GET /one.html HTTP/1.1\r\n", request_headers, |
+ &response_info, callback.callback())); |
+ |
+ // Complete the initial request write. |
+ data.RunFor(1); |
+ ASSERT_FALSE(callback.have_result()); |
+ |
+ // Now append the only chunk. |
+ upload_stream.AppendData(kChunk, arraysize(kChunk) - 1, true); |
+ // Write the chunk. |
+ data.RunFor(1); |
+ ASSERT_FALSE(callback.have_result()); |
+ |
+ // Write the trailer. |
+ data.RunFor(1); |
+ ASSERT_TRUE(callback.have_result()); |
+ ASSERT_EQ(OK, callback.WaitForResult()); |
+ |
+ // Attempt to read the response status and the response headers. |
+ ASSERT_EQ(ERR_IO_PENDING, parser.ReadResponseHeaders(callback.callback())); |
+ data.RunFor(2); |
+ ASSERT_TRUE(callback.have_result()); |
+ ASSERT_GT(callback.WaitForResult(), 0); |
+ |
+ // Finally, attempt to read the response body. |
+ scoped_refptr<IOBuffer> body_buffer(new IOBuffer(kBodySize)); |
+ ASSERT_EQ(ERR_IO_PENDING, |
+ parser.ReadResponseBody(body_buffer.get(), kBodySize, |
+ callback.callback())); |
+ data.RunFor(1); |
+ ASSERT_TRUE(callback.have_result()); |
+ ASSERT_EQ(kBodySize, callback.WaitForResult()); |
+} |
+ |
+// Test to ensure the HttpStreamParser state machine does not get confused |
+// when sending a request with a chunked body with only one chunk that is |
+// available synchronously. |
+TEST(HttpStreamParser, SyncSingleChunkAndAsyncSocket) { |
+ static const char kChunk[] = "Chunk"; |
+ |
+ MockWrite writes[] = { |
+ MockWrite(ASYNC, 0, |
+ "GET /one.html HTTP/1.1\r\n" |
+ "Transfer-Encoding: chunked\r\n\r\n"), |
+ MockWrite(ASYNC, 1, "5\r\nChunk\r\n"), |
+ MockWrite(ASYNC, 2, "0\r\n\r\n"), |
+ }; |
+ |
+ // The size of the response body, as reflected in the Content-Length of the |
+ // MockRead below. |
+ static const int kBodySize = 8; |
+ |
+ MockRead reads[] = { |
+ MockRead(ASYNC, 3, "HTTP/1.1 200 OK\r\n"), |
+ MockRead(ASYNC, 4, "Content-Length: 8\r\n\r\n"), |
+ MockRead(ASYNC, 5, "one.html"), |
+ MockRead(SYNCHRONOUS, 0, 6), // EOF |
+ }; |
+ |
+ ChunkedUploadDataStream upload_stream(0); |
+ ASSERT_EQ(OK, upload_stream.Init(TestCompletionCallback().callback())); |
+ // Append the only chunk. |
+ upload_stream.AppendData(kChunk, arraysize(kChunk) - 1, true); |
+ |
+ DeterministicSocketData data(reads, arraysize(reads), writes, |
+ arraysize(writes)); |
+ scoped_ptr<ClientSocketHandle> socket_handle = |
+ CreateConnectedSocketHandle(&data); |
+ |
+ HttpRequestInfo request_info; |
+ request_info.method = "GET"; |
+ request_info.url = GURL("http://localhost"); |
+ request_info.upload_data_stream = &upload_stream; |
+ |
+ scoped_refptr<GrowableIOBuffer> read_buffer(new GrowableIOBuffer); |
+ HttpStreamParser parser(socket_handle.get(), &request_info, read_buffer.get(), |
+ BoundNetLog()); |
+ |
+ HttpRequestHeaders request_headers; |
+ request_headers.SetHeader("Transfer-Encoding", "chunked"); |
+ |
+ HttpResponseInfo response_info; |
+ TestCompletionCallback callback; |
+ // This will attempt to Write() the initial request and headers, which will |
+ // complete asynchronously. |
+ ASSERT_EQ(ERR_IO_PENDING, |
+ parser.SendRequest("GET /one.html HTTP/1.1\r\n", request_headers, |
+ &response_info, callback.callback())); |
+ |
+ // Write the request and the only chunk. |
+ data.RunFor(2); |
+ |
+ // Write the trailer. |
+ data.RunFor(1); |
+ ASSERT_TRUE(callback.have_result()); |
+ ASSERT_EQ(OK, callback.WaitForResult()); |
+ |
+ // Attempt to read the response status and the response headers. |
+ ASSERT_EQ(ERR_IO_PENDING, parser.ReadResponseHeaders(callback.callback())); |
+ data.RunFor(2); |
+ ASSERT_TRUE(callback.have_result()); |
+ ASSERT_GT(callback.WaitForResult(), 0); |
+ |
+ // Finally, attempt to read the response body. |
+ scoped_refptr<IOBuffer> body_buffer(new IOBuffer(kBodySize)); |
+ ASSERT_EQ(ERR_IO_PENDING, |
+ parser.ReadResponseBody(body_buffer.get(), kBodySize, |
+ callback.callback())); |
+ data.RunFor(1); |
+ ASSERT_TRUE(callback.have_result()); |
+ ASSERT_EQ(kBodySize, callback.WaitForResult()); |
+} |
+ |
+// Test to ensure the HttpStreamParser state machine does not get confused |
// when sending a request with a chunked body, where chunks become available |
// asynchronously, over a socket where writes may also complete |
// asynchronously. |
// This is a regression test for http://crbug.com/132243 |
-TEST(HttpStreamParser, AsyncChunkAndAsyncSocket) { |
+TEST(HttpStreamParser, AsyncChunkAndAsyncSocketWithMultipleChunks) { |
// The chunks that will be written in the request, as reflected in the |
// MockWrites below. |
static const char kChunk1[] = "Chunk 1"; |
@@ -196,15 +377,13 @@ TEST(HttpStreamParser, AsyncChunkAndAsyncSocket) { |
static const char kChunk3[] = "Test 3"; |
MockWrite writes[] = { |
- MockWrite(ASYNC, 0, |
- "GET /one.html HTTP/1.1\r\n" |
- "Host: localhost\r\n" |
- "Transfer-Encoding: chunked\r\n" |
- "Connection: keep-alive\r\n\r\n"), |
- MockWrite(ASYNC, 1, "7\r\nChunk 1\r\n"), |
- MockWrite(ASYNC, 2, "8\r\nChunky 2\r\n"), |
- MockWrite(ASYNC, 3, "6\r\nTest 3\r\n"), |
- MockWrite(ASYNC, 4, "0\r\n\r\n"), |
+ MockWrite(ASYNC, 0, |
+ "GET /one.html HTTP/1.1\r\n" |
+ "Transfer-Encoding: chunked\r\n\r\n"), |
+ MockWrite(ASYNC, 1, "7\r\nChunk 1\r\n"), |
+ MockWrite(ASYNC, 2, "8\r\nChunky 2\r\n"), |
+ MockWrite(ASYNC, 3, "6\r\nTest 3\r\n"), |
+ MockWrite(ASYNC, 4, "0\r\n\r\n"), |
}; |
// The size of the response body, as reflected in the Content-Length of the |
@@ -222,26 +401,14 @@ TEST(HttpStreamParser, AsyncChunkAndAsyncSocket) { |
upload_stream.AppendData(kChunk1, arraysize(kChunk1) - 1, false); |
ASSERT_EQ(OK, upload_stream.Init(TestCompletionCallback().callback())); |
- DeterministicSocketData data(reads, arraysize(reads), |
- writes, arraysize(writes)); |
- data.set_connect_data(MockConnect(SYNCHRONOUS, OK)); |
- |
- scoped_ptr<DeterministicMockTCPClientSocket> transport( |
- new DeterministicMockTCPClientSocket(NULL, &data)); |
- data.set_delegate(transport->AsWeakPtr()); |
- |
- TestCompletionCallback callback; |
- int rv = transport->Connect(callback.callback()); |
- rv = callback.GetResult(rv); |
- ASSERT_EQ(OK, rv); |
- |
- scoped_ptr<ClientSocketHandle> socket_handle(new ClientSocketHandle); |
- socket_handle->SetSocket(transport.Pass()); |
+ DeterministicSocketData data(reads, arraysize(reads), writes, |
+ arraysize(writes)); |
+ scoped_ptr<ClientSocketHandle> socket_handle = |
+ CreateConnectedSocketHandle(&data); |
HttpRequestInfo request_info; |
request_info.method = "GET"; |
request_info.url = GURL("http://localhost"); |
- request_info.load_flags = LOAD_NORMAL; |
request_info.upload_data_stream = &upload_stream; |
scoped_refptr<GrowableIOBuffer> read_buffer(new GrowableIOBuffer); |
@@ -249,16 +416,15 @@ TEST(HttpStreamParser, AsyncChunkAndAsyncSocket) { |
socket_handle.get(), &request_info, read_buffer.get(), BoundNetLog()); |
HttpRequestHeaders request_headers; |
- request_headers.SetHeader("Host", "localhost"); |
request_headers.SetHeader("Transfer-Encoding", "chunked"); |
- request_headers.SetHeader("Connection", "keep-alive"); |
HttpResponseInfo response_info; |
+ TestCompletionCallback callback; |
// This will attempt to Write() the initial request and headers, which will |
// complete asynchronously. |
- rv = parser.SendRequest("GET /one.html HTTP/1.1\r\n", request_headers, |
- &response_info, callback.callback()); |
- ASSERT_EQ(ERR_IO_PENDING, rv); |
+ ASSERT_EQ(ERR_IO_PENDING, |
+ parser.SendRequest("GET /one.html HTTP/1.1\r\n", request_headers, |
+ &response_info, callback.callback())); |
// Complete the initial request write. Additionally, this should enqueue the |
// first chunk. |
@@ -294,32 +460,174 @@ TEST(HttpStreamParser, AsyncChunkAndAsyncSocket) { |
// Finalize writing the trailer. |
data.RunFor(1); |
ASSERT_TRUE(callback.have_result()); |
- |
- // Warning: This will hang if the callback doesn't already have a result, |
- // due to the deterministic socket provider. Do not remove the above |
- // ASSERT_TRUE, which will avoid this hang. |
- rv = callback.WaitForResult(); |
- ASSERT_EQ(OK, rv); |
+ ASSERT_EQ(OK, callback.WaitForResult()); |
// Attempt to read the response status and the response headers. |
- rv = parser.ReadResponseHeaders(callback.callback()); |
- ASSERT_EQ(ERR_IO_PENDING, rv); |
+ ASSERT_EQ(ERR_IO_PENDING, parser.ReadResponseHeaders(callback.callback())); |
data.RunFor(2); |
+ ASSERT_TRUE(callback.have_result()); |
+ ASSERT_GT(callback.WaitForResult(), 0); |
+ |
+ // Finally, attempt to read the response body. |
+ scoped_refptr<IOBuffer> body_buffer(new IOBuffer(kBodySize)); |
+ ASSERT_EQ(ERR_IO_PENDING, |
+ parser.ReadResponseBody(body_buffer.get(), kBodySize, |
+ callback.callback())); |
+ data.RunFor(1); |
+ ASSERT_TRUE(callback.have_result()); |
+ ASSERT_EQ(kBodySize, callback.WaitForResult()); |
+} |
+ |
+// Test to ensure the HttpStreamParser state machine does not get confused |
+// when there's only one "chunk" with 0 bytes, and is received from the |
+// UploadStream only after sending the request headers successfully. |
+TEST(HttpStreamParser, AsyncEmptyChunkedUpload) { |
+ MockWrite writes[] = { |
+ MockWrite(ASYNC, 0, |
+ "GET /one.html HTTP/1.1\r\n" |
+ "Transfer-Encoding: chunked\r\n\r\n"), |
+ MockWrite(ASYNC, 1, "0\r\n\r\n"), |
+ }; |
+ |
+ // The size of the response body, as reflected in the Content-Length of the |
+ // MockRead below. |
+ const int kBodySize = 8; |
+ |
+ MockRead reads[] = { |
+ MockRead(ASYNC, 2, "HTTP/1.1 200 OK\r\n"), |
+ MockRead(ASYNC, 3, "Content-Length: 8\r\n\r\n"), |
+ MockRead(ASYNC, 4, "one.html"), |
+ MockRead(SYNCHRONOUS, 0, 5), // EOF |
+ }; |
+ |
+ ChunkedUploadDataStream upload_stream(0); |
+ ASSERT_EQ(OK, upload_stream.Init(TestCompletionCallback().callback())); |
+ |
+ DeterministicSocketData data(reads, arraysize(reads), writes, |
+ arraysize(writes)); |
+ scoped_ptr<ClientSocketHandle> socket_handle = |
+ CreateConnectedSocketHandle(&data); |
+ |
+ HttpRequestInfo request_info; |
+ request_info.method = "GET"; |
+ request_info.url = GURL("http://localhost"); |
+ request_info.upload_data_stream = &upload_stream; |
+ |
+ scoped_refptr<GrowableIOBuffer> read_buffer(new GrowableIOBuffer); |
+ HttpStreamParser parser(socket_handle.get(), &request_info, read_buffer.get(), |
+ BoundNetLog()); |
+ |
+ HttpRequestHeaders request_headers; |
+ request_headers.SetHeader("Transfer-Encoding", "chunked"); |
+ |
+ HttpResponseInfo response_info; |
+ TestCompletionCallback callback; |
+ // This will attempt to Write() the initial request and headers, which will |
+ // complete asynchronously. |
+ ASSERT_EQ(ERR_IO_PENDING, |
+ parser.SendRequest("GET /one.html HTTP/1.1\r\n", request_headers, |
+ &response_info, callback.callback())); |
+ |
+ // Complete writing the request headers. |
+ data.RunFor(1); |
+ ASSERT_FALSE(callback.have_result()); |
+ |
+ // Now append the terminal 0-byte "chunk". |
+ upload_stream.AppendData(nullptr, 0, true); |
+ ASSERT_FALSE(callback.have_result()); |
+ |
+ // Finalize writing the trailer. |
+ data.RunFor(1); |
+ ASSERT_TRUE(callback.have_result()); |
+ ASSERT_EQ(OK, callback.WaitForResult()); |
+ // Attempt to read the response status and the response headers. |
+ ASSERT_EQ(ERR_IO_PENDING, parser.ReadResponseHeaders(callback.callback())); |
+ data.RunFor(2); |
ASSERT_TRUE(callback.have_result()); |
- rv = callback.WaitForResult(); |
- ASSERT_GT(rv, 0); |
+ ASSERT_GT(callback.WaitForResult(), 0); |
// Finally, attempt to read the response body. |
scoped_refptr<IOBuffer> body_buffer(new IOBuffer(kBodySize)); |
- rv = parser.ReadResponseBody( |
- body_buffer.get(), kBodySize, callback.callback()); |
- ASSERT_EQ(ERR_IO_PENDING, rv); |
+ ASSERT_EQ(ERR_IO_PENDING, |
+ parser.ReadResponseBody(body_buffer.get(), kBodySize, |
+ callback.callback())); |
data.RunFor(1); |
+ ASSERT_TRUE(callback.have_result()); |
+ ASSERT_EQ(kBodySize, callback.WaitForResult()); |
+} |
+ |
+// Test to ensure the HttpStreamParser state machine does not get confused |
+// when there's only one "chunk" with 0 bytes, which was already appended before |
+// the request was started. |
+TEST(HttpStreamParser, SyncEmptyChunkedUpload) { |
+ MockWrite writes[] = { |
+ MockWrite(ASYNC, 0, |
+ "GET /one.html HTTP/1.1\r\n" |
+ "Transfer-Encoding: chunked\r\n\r\n"), |
+ MockWrite(ASYNC, 1, "0\r\n\r\n"), |
+ }; |
+ |
+ // The size of the response body, as reflected in the Content-Length of the |
+ // MockRead below. |
+ const int kBodySize = 8; |
+ |
+ MockRead reads[] = { |
+ MockRead(ASYNC, 2, "HTTP/1.1 200 OK\r\n"), |
+ MockRead(ASYNC, 3, "Content-Length: 8\r\n\r\n"), |
+ MockRead(ASYNC, 4, "one.html"), |
+ MockRead(SYNCHRONOUS, 0, 5), // EOF |
+ }; |
+ |
+ ChunkedUploadDataStream upload_stream(0); |
+ ASSERT_EQ(OK, upload_stream.Init(TestCompletionCallback().callback())); |
+ // Append final empty chunk. |
+ upload_stream.AppendData(nullptr, 0, true); |
+ DeterministicSocketData data(reads, arraysize(reads), writes, |
+ arraysize(writes)); |
+ scoped_ptr<ClientSocketHandle> socket_handle = |
+ CreateConnectedSocketHandle(&data); |
+ |
+ HttpRequestInfo request_info; |
+ request_info.method = "GET"; |
+ request_info.url = GURL("http://localhost"); |
+ request_info.upload_data_stream = &upload_stream; |
+ |
+ scoped_refptr<GrowableIOBuffer> read_buffer(new GrowableIOBuffer); |
+ HttpStreamParser parser(socket_handle.get(), &request_info, read_buffer.get(), |
+ BoundNetLog()); |
+ |
+ HttpRequestHeaders request_headers; |
+ request_headers.SetHeader("Transfer-Encoding", "chunked"); |
+ |
+ HttpResponseInfo response_info; |
+ TestCompletionCallback callback; |
+ // This will attempt to Write() the initial request and headers, which will |
+ // complete asynchronously. |
+ ASSERT_EQ(ERR_IO_PENDING, |
+ parser.SendRequest("GET /one.html HTTP/1.1\r\n", request_headers, |
+ &response_info, callback.callback())); |
+ |
+ // Complete writing the request headers and body. |
+ data.RunFor(2); |
+ ASSERT_TRUE(callback.have_result()); |
+ ASSERT_EQ(OK, callback.WaitForResult()); |
+ |
+ // Attempt to read the response status and the response headers. |
+ ASSERT_EQ(ERR_IO_PENDING, parser.ReadResponseHeaders(callback.callback())); |
+ data.RunFor(2); |
+ ASSERT_TRUE(callback.have_result()); |
+ ASSERT_GT(callback.WaitForResult(), 0); |
+ |
+ // Finally, attempt to read the response body. |
+ scoped_refptr<IOBuffer> body_buffer(new IOBuffer(kBodySize)); |
+ ASSERT_EQ(ERR_IO_PENDING, |
+ parser.ReadResponseBody(body_buffer.get(), kBodySize, |
+ callback.callback())); |
+ data.RunFor(1); |
ASSERT_TRUE(callback.have_result()); |
- rv = callback.WaitForResult(); |
- ASSERT_EQ(kBodySize, rv); |
+ ASSERT_EQ(kBodySize, callback.WaitForResult()); |
} |
TEST(HttpStreamParser, TruncatedHeaders) { |
@@ -387,7 +695,6 @@ TEST(HttpStreamParser, TruncatedHeaders) { |
TestCompletionCallback callback; |
int rv = transport->Connect(callback.callback()); |
- rv = callback.GetResult(rv); |
ASSERT_EQ(OK, rv); |
scoped_ptr<ClientSocketHandle> socket_handle(new ClientSocketHandle); |
@@ -456,7 +763,6 @@ TEST(HttpStreamParser, Websocket101Response) { |
TestCompletionCallback callback; |
int rv = transport->Connect(callback.callback()); |
- rv = callback.GetResult(rv); |
ASSERT_EQ(OK, rv); |
scoped_ptr<ClientSocketHandle> socket_handle(new ClientSocketHandle); |