Index: net/spdy/spdy_network_transaction_unittest.cc |
diff --git a/net/spdy/spdy_network_transaction_unittest.cc b/net/spdy/spdy_network_transaction_unittest.cc |
index 805ab2bcc1feba4f67357ba9b5e3c8c0d610a2b2..f0e4c8c7c41171d5cce7b02c46be6dabc6c7be94 100644 |
--- a/net/spdy/spdy_network_transaction_unittest.cc |
+++ b/net/spdy/spdy_network_transaction_unittest.cc |
@@ -2157,1754 +2157,2014 @@ TEST_P(SpdyNetworkTransactionTest, ResponseWithTwoSynReplies) { |
helper.VerifyDataConsumed(); |
} |
-// Test that sent data frames and received WINDOW_UPDATE frames change |
-// the send_window_size_ correctly. |
+TEST_P(SpdyNetworkTransactionTest, ResetReplyWithTransferEncoding) { |
+ // Construct the request. |
+ scoped_ptr<SpdyFrame> req( |
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
+ scoped_ptr<SpdyFrame> rst( |
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR)); |
+ MockWrite writes[] = { |
+ CreateMockWrite(*req), |
+ CreateMockWrite(*rst), |
+ }; |
-// WINDOW_UPDATE is different than most other frames in that it can arrive |
-// while the client is still sending the request body. In order to enforce |
-// this scenario, we feed a couple of dummy frames and give a delay of 0 to |
-// socket data provider, so that initial read that is done as soon as the |
-// stream is created, succeeds and schedules another read. This way reads |
-// and writes are interleaved; after doing a full frame write, SpdyStream |
-// will break out of DoLoop and will read and process a WINDOW_UPDATE. |
-// Once our WINDOW_UPDATE is read, we cannot send SYN_REPLY right away |
-// since request has not been completely written, therefore we feed |
-// enough number of WINDOW_UPDATEs to finish the first read and cause a |
-// write, leading to a complete write of request body; after that we send |
-// a reply with a body, to cause a graceful shutdown. |
+ const char* const headers[] = { |
+ "transfer-encoding", "chunked" |
+ }; |
+ scoped_ptr<SpdyFrame> resp( |
+ spdy_util_.ConstructSpdyGetSynReply(headers, 1, 1)); |
+ scoped_ptr<SpdyFrame> body( |
+ spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ MockRead reads[] = { |
+ CreateMockRead(*resp), |
+ CreateMockRead(*body), |
+ MockRead(ASYNC, 0, 0) // EOF |
+ }; |
-// TODO(agayev): develop a socket data provider where both, reads and |
-// writes are ordered so that writing tests like these are easy and rewrite |
-// all these tests using it. Right now we are working around the |
-// limitations as described above and it's not deterministic, tests may |
-// fail under specific circumstances. |
-TEST_P(SpdyNetworkTransactionTest, WindowUpdateReceived) { |
- if (GetParam().protocol < kProtoSPDY3) |
- return; |
+ DelayedSocketData data(1, reads, arraysize(reads), |
+ writes, arraysize(writes)); |
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
+ BoundNetLog(), GetParam(), NULL); |
+ helper.RunToCompletion(&data); |
+ TransactionHelperResult out = helper.output(); |
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); |
- static int kFrameCount = 2; |
- scoped_ptr<std::string> content( |
- new std::string(kMaxSpdyFrameChunkSize, 'a')); |
- scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyPost( |
- kRequestUrl, 1, kMaxSpdyFrameChunkSize * kFrameCount, LOWEST, NULL, 0)); |
- scoped_ptr<SpdyFrame> body( |
- spdy_util_.ConstructSpdyBodyFrame( |
- 1, content->c_str(), content->size(), false)); |
- scoped_ptr<SpdyFrame> body_end( |
- spdy_util_.ConstructSpdyBodyFrame( |
- 1, content->c_str(), content->size(), true)); |
+ helper.session()->spdy_session_pool()->CloseAllSessions(); |
+ helper.VerifyDataConsumed(); |
+} |
+TEST_P(SpdyNetworkTransactionTest, ResetPushWithTransferEncoding) { |
+ // Construct the request. |
+ scoped_ptr<SpdyFrame> req( |
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
+ scoped_ptr<SpdyFrame> rst( |
+ spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_PROTOCOL_ERROR)); |
MockWrite writes[] = { |
- CreateMockWrite(*req, 0), |
- CreateMockWrite(*body, 1), |
- CreateMockWrite(*body_end, 2), |
+ CreateMockWrite(*req), |
+ CreateMockWrite(*rst), |
}; |
- static const int32 kDeltaWindowSize = 0xff; |
- static const int kDeltaCount = 4; |
- scoped_ptr<SpdyFrame> window_update( |
- spdy_util_.ConstructSpdyWindowUpdate(1, kDeltaWindowSize)); |
- scoped_ptr<SpdyFrame> window_update_dummy( |
- spdy_util_.ConstructSpdyWindowUpdate(2, kDeltaWindowSize)); |
- scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); |
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ const char* const headers[] = { |
+ "transfer-encoding", "chunked" |
+ }; |
+ scoped_ptr<SpdyFrame> push( |
+ spdy_util_.ConstructSpdyPush(headers, arraysize(headers) / 2, |
+ 2, 1, "http://www.google.com/1")); |
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
MockRead reads[] = { |
- CreateMockRead(*window_update_dummy, 3), |
- CreateMockRead(*window_update_dummy, 4), |
- CreateMockRead(*window_update_dummy, 5), |
- CreateMockRead(*window_update, 6), // Four updates, therefore window |
- CreateMockRead(*window_update, 7), // size should increase by |
- CreateMockRead(*window_update, 8), // kDeltaWindowSize * 4 |
- CreateMockRead(*window_update, 9), |
- CreateMockRead(*resp, 10), |
- CreateMockRead(*body_end, 11), |
- MockRead(ASYNC, 0, 0, 12) // EOF |
+ CreateMockRead(*resp), |
+ CreateMockRead(*push), |
+ CreateMockRead(*body), |
+ MockRead(ASYNC, 0, 0) // EOF |
}; |
- DeterministicSocketData data(reads, arraysize(reads), |
- writes, arraysize(writes)); |
+ DelayedSocketData data(1, reads, arraysize(reads), |
+ writes, arraysize(writes)); |
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
+ BoundNetLog(), GetParam(), NULL); |
+ helper.RunToCompletion(&data); |
+ TransactionHelperResult out = helper.output(); |
+ EXPECT_EQ(OK, out.rv); |
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); |
+ EXPECT_EQ("hello!", out.response_data); |
- ScopedVector<UploadElementReader> element_readers; |
- for (int i = 0; i < kFrameCount; ++i) { |
- element_readers.push_back( |
- new UploadBytesElementReader(content->c_str(), content->size())); |
- } |
- UploadDataStream upload_data_stream(&element_readers, 0); |
+ helper.session()->spdy_session_pool()->CloseAllSessions(); |
+ helper.VerifyDataConsumed(); |
+} |
- // Setup the request |
- HttpRequestInfo request; |
- request.method = "POST"; |
- request.url = GURL(kDefaultURL); |
- request.upload_data_stream = &upload_data_stream; |
+TEST_P(SpdyNetworkTransactionTest, CancelledTransaction) { |
+ // Construct the request. |
+ scoped_ptr<SpdyFrame> req( |
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
+ MockWrite writes[] = { |
+ CreateMockWrite(*req), |
+ }; |
- NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY, |
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ MockRead reads[] = { |
+ CreateMockRead(*resp), |
+ // This following read isn't used by the test, except during the |
+ // RunUntilIdle() call at the end since the SpdySession survives the |
+ // HttpNetworkTransaction and still tries to continue Read()'ing. Any |
+ // MockRead will do here. |
+ MockRead(ASYNC, 0, 0) // EOF |
+ }; |
+ |
+ StaticSocketDataProvider data(reads, arraysize(reads), |
+ writes, arraysize(writes)); |
+ |
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
BoundNetLog(), GetParam(), NULL); |
- helper.SetDeterministic(); |
- helper.AddDeterministicData(&data); |
helper.RunPreTestSetup(); |
- |
+ helper.AddData(&data); |
HttpNetworkTransaction* trans = helper.trans(); |
TestCompletionCallback callback; |
- int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); |
- |
+ int rv = trans->Start( |
+ &CreateGetRequest(), callback.callback(), BoundNetLog()); |
EXPECT_EQ(ERR_IO_PENDING, rv); |
+ helper.ResetTrans(); // Cancel the transaction. |
- data.RunFor(11); |
- |
- SpdyHttpStream* stream = static_cast<SpdyHttpStream*>(trans->stream_.get()); |
- ASSERT_TRUE(stream != NULL); |
- ASSERT_TRUE(stream->stream() != NULL); |
- EXPECT_EQ(static_cast<int>(kSpdyStreamInitialWindowSize) + |
- kDeltaWindowSize * kDeltaCount - |
- kMaxSpdyFrameChunkSize * kFrameCount, |
- stream->stream()->send_window_size()); |
- |
- data.RunFor(1); |
- |
- rv = callback.WaitForResult(); |
- EXPECT_EQ(OK, rv); |
- |
- helper.VerifyDataConsumed(); |
+ // Flush the MessageLoop while the SpdySessionDependencies (in particular, the |
+ // MockClientSocketFactory) are still alive. |
+ base::MessageLoop::current()->RunUntilIdle(); |
+ helper.VerifyDataNotConsumed(); |
} |
-// Test that received data frames and sent WINDOW_UPDATE frames change |
-// the recv_window_size_ correctly. |
-TEST_P(SpdyNetworkTransactionTest, WindowUpdateSent) { |
- if (GetParam().protocol < kProtoSPDY3) |
- return; |
- |
- // Set the data in the body frame large enough to trigger sending a |
- // WINDOW_UPDATE by the stream. |
- const std::string body_data(kSpdyStreamInitialWindowSize / 2 + 1, 'x'); |
- |
+// Verify that the client sends a Rst Frame upon cancelling the stream. |
+TEST_P(SpdyNetworkTransactionTest, CancelledTransactionSendRst) { |
scoped_ptr<SpdyFrame> req( |
spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- scoped_ptr<SpdyFrame> session_window_update( |
- spdy_util_.ConstructSpdyWindowUpdate(0, body_data.size())); |
- scoped_ptr<SpdyFrame> window_update( |
- spdy_util_.ConstructSpdyWindowUpdate(1, body_data.size())); |
- |
- std::vector<MockWrite> writes; |
- writes.push_back(CreateMockWrite(*req)); |
- if (GetParam().protocol >= kProtoSPDY31) |
- writes.push_back(CreateMockWrite(*session_window_update)); |
- writes.push_back(CreateMockWrite(*window_update)); |
+ scoped_ptr<SpdyFrame> rst( |
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_CANCEL)); |
+ MockWrite writes[] = { |
+ CreateMockWrite(*req, 0, SYNCHRONOUS), |
+ CreateMockWrite(*rst, 2, SYNCHRONOUS), |
+ }; |
- scoped_ptr<SpdyFrame> resp( |
- spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- scoped_ptr<SpdyFrame> body_no_fin( |
- spdy_util_.ConstructSpdyBodyFrame( |
- 1, body_data.data(), body_data.size(), false)); |
- scoped_ptr<SpdyFrame> body_fin( |
- spdy_util_.ConstructSpdyBodyFrame(1, NULL, 0, true)); |
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
MockRead reads[] = { |
- CreateMockRead(*resp), |
- CreateMockRead(*body_no_fin), |
- MockRead(ASYNC, ERR_IO_PENDING, 0), // Force a pause |
- CreateMockRead(*body_fin), |
- MockRead(ASYNC, ERR_IO_PENDING, 0), // Force a pause |
- MockRead(ASYNC, 0, 0) // EOF |
+ CreateMockRead(*resp, 1, ASYNC), |
+ MockRead(ASYNC, 0, 0, 3) // EOF |
}; |
- DelayedSocketData data(1, reads, arraysize(reads), |
- vector_as_array(&writes), writes.size()); |
+ DeterministicSocketData data(reads, arraysize(reads), |
+ writes, arraysize(writes)); |
NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
- BoundNetLog(), GetParam(), NULL); |
- helper.AddData(&data); |
+ BoundNetLog(), |
+ GetParam(), NULL); |
+ helper.SetDeterministic(); |
helper.RunPreTestSetup(); |
+ helper.AddDeterministicData(&data); |
HttpNetworkTransaction* trans = helper.trans(); |
TestCompletionCallback callback; |
- int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); |
+ int rv = trans->Start( |
+ &CreateGetRequest(), callback.callback(), BoundNetLog()); |
EXPECT_EQ(ERR_IO_PENDING, rv); |
- rv = callback.WaitForResult(); |
- EXPECT_EQ(OK, rv); |
- SpdyHttpStream* stream = |
- static_cast<SpdyHttpStream*>(trans->stream_.get()); |
- ASSERT_TRUE(stream != NULL); |
- ASSERT_TRUE(stream->stream() != NULL); |
+ data.SetStop(2); |
+ data.Run(); |
+ helper.ResetTrans(); |
+ data.SetStop(20); |
+ data.Run(); |
- EXPECT_EQ( |
- static_cast<int>(kSpdyStreamInitialWindowSize - body_data.size()), |
- stream->stream()->recv_window_size()); |
+ helper.VerifyDataConsumed(); |
+} |
- const HttpResponseInfo* response = trans->GetResponseInfo(); |
- ASSERT_TRUE(response != NULL); |
- ASSERT_TRUE(response->headers.get() != NULL); |
- EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); |
- EXPECT_TRUE(response->was_fetched_via_spdy); |
- |
- // Issue a read which will cause a WINDOW_UPDATE to be sent and window |
- // size increased to default. |
- scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(body_data.size())); |
- rv = trans->Read(buf.get(), body_data.size(), CompletionCallback()); |
- EXPECT_EQ(static_cast<int>(body_data.size()), rv); |
- std::string content(buf->data(), buf->data() + body_data.size()); |
- EXPECT_EQ(body_data, content); |
- |
- // Schedule the reading of empty data frame with FIN |
- data.CompleteRead(); |
- |
- // Force write of WINDOW_UPDATE which was scheduled during the above |
- // read. |
- base::MessageLoop::current()->RunUntilIdle(); |
- |
- // Read EOF. |
- data.CompleteRead(); |
- |
- helper.VerifyDataConsumed(); |
-} |
- |
-// Test that WINDOW_UPDATE frame causing overflow is handled correctly. |
-TEST_P(SpdyNetworkTransactionTest, WindowUpdateOverflow) { |
- if (GetParam().protocol < kProtoSPDY3) |
- return; |
- |
- // Number of full frames we hope to write (but will not, used to |
- // set content-length header correctly) |
- static int kFrameCount = 3; |
- |
- scoped_ptr<std::string> content( |
- new std::string(kMaxSpdyFrameChunkSize, 'a')); |
- scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyPost( |
- kRequestUrl, 1, kMaxSpdyFrameChunkSize * kFrameCount, LOWEST, NULL, 0)); |
- scoped_ptr<SpdyFrame> body( |
- spdy_util_.ConstructSpdyBodyFrame( |
- 1, content->c_str(), content->size(), false)); |
- scoped_ptr<SpdyFrame> rst( |
- spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_FLOW_CONTROL_ERROR)); |
+// Verify that the client can correctly deal with the user callback attempting |
+// to start another transaction on a session that is closing down. See |
+// http://crbug.com/47455 |
+TEST_P(SpdyNetworkTransactionTest, StartTransactionOnReadCallback) { |
+ scoped_ptr<SpdyFrame> req( |
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
+ MockWrite writes[] = { CreateMockWrite(*req) }; |
+ MockWrite writes2[] = { CreateMockWrite(*req) }; |
- // We're not going to write a data frame with FIN, we'll receive a bad |
- // WINDOW_UPDATE while sending a request and will send a RST_STREAM frame. |
- MockWrite writes[] = { |
- CreateMockWrite(*req, 0), |
- CreateMockWrite(*body, 2), |
- CreateMockWrite(*rst, 3), |
+ // The indicated length of this frame is longer than its actual length. When |
+ // the session receives an empty frame after this one, it shuts down the |
+ // session, and calls the read callback with the incomplete data. |
+ const uint8 kGetBodyFrame2[] = { |
+ 0x00, 0x00, 0x00, 0x01, |
+ 0x01, 0x00, 0x00, 0x07, |
+ 'h', 'e', 'l', 'l', 'o', '!', |
}; |
- static const int32 kDeltaWindowSize = 0x7fffffff; // cause an overflow |
- scoped_ptr<SpdyFrame> window_update( |
- spdy_util_.ConstructSpdyWindowUpdate(1, kDeltaWindowSize)); |
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
MockRead reads[] = { |
- CreateMockRead(*window_update, 1), |
- MockRead(ASYNC, 0, 4) // EOF |
+ CreateMockRead(*resp, 2), |
+ MockRead(ASYNC, ERR_IO_PENDING, 3), // Force a pause |
+ MockRead(ASYNC, reinterpret_cast<const char*>(kGetBodyFrame2), |
+ arraysize(kGetBodyFrame2), 4), |
+ MockRead(ASYNC, ERR_IO_PENDING, 5), // Force a pause |
+ MockRead(ASYNC, 0, 0, 6), // EOF |
+ }; |
+ MockRead reads2[] = { |
+ CreateMockRead(*resp, 2), |
+ MockRead(ASYNC, 0, 0, 3), // EOF |
}; |
- DeterministicSocketData data(reads, arraysize(reads), |
- writes, arraysize(writes)); |
- |
- ScopedVector<UploadElementReader> element_readers; |
- for (int i = 0; i < kFrameCount; ++i) { |
- element_readers.push_back( |
- new UploadBytesElementReader(content->c_str(), content->size())); |
- } |
- UploadDataStream upload_data_stream(&element_readers, 0); |
- |
- // Setup the request |
- HttpRequestInfo request; |
- request.method = "POST"; |
- request.url = GURL("http://www.google.com/"); |
- request.upload_data_stream = &upload_data_stream; |
+ OrderedSocketData data(reads, arraysize(reads), |
+ writes, arraysize(writes)); |
+ DelayedSocketData data2(1, reads2, arraysize(reads2), |
+ writes2, arraysize(writes2)); |
- NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY, |
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
BoundNetLog(), GetParam(), NULL); |
- helper.SetDeterministic(); |
helper.RunPreTestSetup(); |
- helper.AddDeterministicData(&data); |
+ helper.AddData(&data); |
+ helper.AddData(&data2); |
HttpNetworkTransaction* trans = helper.trans(); |
+ // Start the transaction with basic parameters. |
TestCompletionCallback callback; |
int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); |
- ASSERT_EQ(ERR_IO_PENDING, rv); |
+ EXPECT_EQ(ERR_IO_PENDING, rv); |
+ rv = callback.WaitForResult(); |
- data.RunFor(5); |
- ASSERT_TRUE(callback.have_result()); |
- EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, callback.WaitForResult()); |
+ const int kSize = 3000; |
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSize)); |
+ rv = trans->Read( |
+ buf.get(), |
+ kSize, |
+ base::Bind(&SpdyNetworkTransactionTest::StartTransactionCallback, |
+ helper.session())); |
+ // This forces an err_IO_pending, which sets the callback. |
+ data.CompleteRead(); |
+ // This finishes the read. |
+ data.CompleteRead(); |
helper.VerifyDataConsumed(); |
} |
-// Test that after hitting a send window size of 0, the write process |
-// stalls and upon receiving WINDOW_UPDATE frame write resumes. |
- |
-// This test constructs a POST request followed by enough data frames |
-// containing 'a' that would make the window size 0, followed by another |
-// data frame containing default content (which is "hello!") and this frame |
-// also contains a FIN flag. DelayedSocketData is used to enforce all |
-// writes go through before a read could happen. However, the last frame |
-// ("hello!") is not supposed to go through since by the time its turn |
-// arrives, window size is 0. At this point MessageLoop::Run() called via |
-// callback would block. Therefore we call MessageLoop::RunUntilIdle() |
-// which returns after performing all possible writes. We use DCHECKS to |
-// ensure that last data frame is still there and stream has stalled. |
-// After that, next read is artifically enforced, which causes a |
-// WINDOW_UPDATE to be read and I/O process resumes. |
-TEST_P(SpdyNetworkTransactionTest, FlowControlStallResume) { |
- if (GetParam().protocol < kProtoSPDY3) |
- return; |
- |
- // Number of frames we need to send to zero out the window size: data |
- // frames plus SYN_STREAM plus the last data frame; also we need another |
- // data frame that we will send once the WINDOW_UPDATE is received, |
- // therefore +3. |
- size_t num_writes = kSpdyStreamInitialWindowSize / kMaxSpdyFrameChunkSize + 3; |
- |
- // Calculate last frame's size; 0 size data frame is legal. |
- size_t last_frame_size = |
- kSpdyStreamInitialWindowSize % kMaxSpdyFrameChunkSize; |
- |
- // Construct content for a data frame of maximum size. |
- std::string content(kMaxSpdyFrameChunkSize, 'a'); |
- |
- scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyPost( |
- kRequestUrl, 1, kSpdyStreamInitialWindowSize + kUploadDataSize, |
- LOWEST, NULL, 0)); |
- |
- // Full frames. |
- scoped_ptr<SpdyFrame> body1( |
- spdy_util_.ConstructSpdyBodyFrame( |
- 1, content.c_str(), content.size(), false)); |
- |
- // Last frame to zero out the window size. |
- scoped_ptr<SpdyFrame> body2( |
- spdy_util_.ConstructSpdyBodyFrame( |
- 1, content.c_str(), last_frame_size, false)); |
- |
- // Data frame to be sent once WINDOW_UPDATE frame is received. |
- scoped_ptr<SpdyFrame> body3(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- |
- // Fill in mock writes. |
- scoped_ptr<MockWrite[]> writes(new MockWrite[num_writes]); |
- size_t i = 0; |
- writes[i] = CreateMockWrite(*req); |
- for (i = 1; i < num_writes - 2; i++) |
- writes[i] = CreateMockWrite(*body1); |
- writes[i++] = CreateMockWrite(*body2); |
- writes[i] = CreateMockWrite(*body3); |
+// Verify that the client can correctly deal with the user callback deleting the |
+// transaction. Failures will usually be valgrind errors. See |
+// http://crbug.com/46925 |
+TEST_P(SpdyNetworkTransactionTest, DeleteSessionOnReadCallback) { |
+ scoped_ptr<SpdyFrame> req( |
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
+ MockWrite writes[] = { CreateMockWrite(*req) }; |
- // Construct read frame, give enough space to upload the rest of the |
- // data. |
- scoped_ptr<SpdyFrame> session_window_update( |
- spdy_util_.ConstructSpdyWindowUpdate(0, kUploadDataSize)); |
- scoped_ptr<SpdyFrame> window_update( |
- spdy_util_.ConstructSpdyWindowUpdate(1, kUploadDataSize)); |
- scoped_ptr<SpdyFrame> reply(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); |
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
MockRead reads[] = { |
- CreateMockRead(*session_window_update), |
- CreateMockRead(*session_window_update), |
- CreateMockRead(*window_update), |
- CreateMockRead(*window_update), |
- CreateMockRead(*reply), |
- CreateMockRead(*body2), |
- CreateMockRead(*body3), |
- MockRead(ASYNC, 0, 0) // EOF |
+ CreateMockRead(*resp.get(), 2), |
+ MockRead(ASYNC, ERR_IO_PENDING, 3), // Force a pause |
+ CreateMockRead(*body.get(), 4), |
+ MockRead(ASYNC, 0, 0, 5), // EOF |
}; |
- // Skip the session window updates unless we're using SPDY/3.1 and |
- // above. |
- size_t read_offset = (GetParam().protocol >= kProtoSPDY31) ? 0 : 2; |
- size_t num_reads = arraysize(reads) - read_offset; |
- |
- // Force all writes to happen before any read, last write will not |
- // actually queue a frame, due to window size being 0. |
- DelayedSocketData data(num_writes, reads + read_offset, num_reads, |
- writes.get(), num_writes); |
- |
- ScopedVector<UploadElementReader> element_readers; |
- std::string upload_data_string(kSpdyStreamInitialWindowSize, 'a'); |
- upload_data_string.append(kUploadData, kUploadDataSize); |
- element_readers.push_back(new UploadBytesElementReader( |
- upload_data_string.c_str(), upload_data_string.size())); |
- UploadDataStream upload_data_stream(&element_readers, 0); |
+ OrderedSocketData data(reads, arraysize(reads), |
+ writes, arraysize(writes)); |
- HttpRequestInfo request; |
- request.method = "POST"; |
- request.url = GURL("http://www.google.com/"); |
- request.upload_data_stream = &upload_data_stream; |
- NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY, |
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
BoundNetLog(), GetParam(), NULL); |
- helper.AddData(&data); |
helper.RunPreTestSetup(); |
- |
+ helper.AddData(&data); |
HttpNetworkTransaction* trans = helper.trans(); |
+ // Start the transaction with basic parameters. |
TestCompletionCallback callback; |
int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); |
EXPECT_EQ(ERR_IO_PENDING, rv); |
- |
- base::MessageLoop::current()->RunUntilIdle(); // Write as much as we can. |
- |
- SpdyHttpStream* stream = static_cast<SpdyHttpStream*>(trans->stream_.get()); |
- ASSERT_TRUE(stream != NULL); |
- ASSERT_TRUE(stream->stream() != NULL); |
- EXPECT_EQ(0, stream->stream()->send_window_size()); |
- // All the body data should have been read. |
- // TODO(satorux): This is because of the weirdness in reading the request |
- // body in OnSendBodyComplete(). See crbug.com/113107. |
- EXPECT_TRUE(upload_data_stream.IsEOF()); |
- // But the body is not yet fully sent (kUploadData is not yet sent) |
- // since we're send-stalled. |
- EXPECT_TRUE(stream->stream()->send_stalled_by_flow_control()); |
- |
- data.ForceNextRead(); // Read in WINDOW_UPDATE frame. |
rv = callback.WaitForResult(); |
- helper.VerifyDataConsumed(); |
-} |
-// Test we correctly handle the case where the SETTINGS frame results in |
-// unstalling the send window. |
-TEST_P(SpdyNetworkTransactionTest, FlowControlStallResumeAfterSettings) { |
- if (GetParam().protocol < kProtoSPDY3) |
- return; |
- |
- // Number of frames we need to send to zero out the window size: data |
- // frames plus SYN_STREAM plus the last data frame; also we need another |
- // data frame that we will send once the SETTING is received, therefore +3. |
- size_t num_writes = kSpdyStreamInitialWindowSize / kMaxSpdyFrameChunkSize + 3; |
+ // Setup a user callback which will delete the session, and clear out the |
+ // memory holding the stream object. Note that the callback deletes trans. |
+ const int kSize = 3000; |
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSize)); |
+ rv = trans->Read( |
+ buf.get(), |
+ kSize, |
+ base::Bind(&SpdyNetworkTransactionTest::DeleteSessionCallback, |
+ base::Unretained(&helper))); |
+ ASSERT_EQ(ERR_IO_PENDING, rv); |
+ data.CompleteRead(); |
- // Calculate last frame's size; 0 size data frame is legal. |
- size_t last_frame_size = |
- kSpdyStreamInitialWindowSize % kMaxSpdyFrameChunkSize; |
+ // Finish running rest of tasks. |
+ base::MessageLoop::current()->RunUntilIdle(); |
+ helper.VerifyDataConsumed(); |
+} |
- // Construct content for a data frame of maximum size. |
- std::string content(kMaxSpdyFrameChunkSize, 'a'); |
+// Send a spdy request to www.google.com that gets redirected to www.foo.com. |
+TEST_P(SpdyNetworkTransactionTest, RedirectGetRequest) { |
+ const SpdyHeaderInfo kSynStartHeader = spdy_util_.MakeSpdyHeader(SYN_STREAM); |
+ scoped_ptr<SpdyHeaderBlock> headers( |
+ spdy_util_.ConstructGetHeaderBlock("http://www.google.com/")); |
+ (*headers)["user-agent"] = ""; |
+ (*headers)["accept-encoding"] = "gzip,deflate"; |
+ scoped_ptr<SpdyHeaderBlock> headers2( |
+ spdy_util_.ConstructGetHeaderBlock("http://www.foo.com/index.php")); |
+ (*headers2)["user-agent"] = ""; |
+ (*headers2)["accept-encoding"] = "gzip,deflate"; |
- scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyPost( |
- kRequestUrl, 1, kSpdyStreamInitialWindowSize + kUploadDataSize, |
- LOWEST, NULL, 0)); |
+ // Setup writes/reads to www.google.com |
+ scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyFrame( |
+ kSynStartHeader, headers.Pass())); |
+ scoped_ptr<SpdyFrame> req2(spdy_util_.ConstructSpdyFrame( |
+ kSynStartHeader, headers2.Pass())); |
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReplyRedirect(1)); |
+ MockWrite writes[] = { |
+ CreateMockWrite(*req, 1), |
+ }; |
+ MockRead reads[] = { |
+ CreateMockRead(*resp, 2), |
+ MockRead(ASYNC, 0, 0, 3) // EOF |
+ }; |
- // Full frames. |
- scoped_ptr<SpdyFrame> body1( |
- spdy_util_.ConstructSpdyBodyFrame( |
- 1, content.c_str(), content.size(), false)); |
+ // Setup writes/reads to www.foo.com |
+ scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ MockWrite writes2[] = { |
+ CreateMockWrite(*req2, 1), |
+ }; |
+ MockRead reads2[] = { |
+ CreateMockRead(*resp2, 2), |
+ CreateMockRead(*body2, 3), |
+ MockRead(ASYNC, 0, 0, 4) // EOF |
+ }; |
+ OrderedSocketData data(reads, arraysize(reads), |
+ writes, arraysize(writes)); |
+ OrderedSocketData data2(reads2, arraysize(reads2), |
+ writes2, arraysize(writes2)); |
- // Last frame to zero out the window size. |
- scoped_ptr<SpdyFrame> body2( |
- spdy_util_.ConstructSpdyBodyFrame( |
- 1, content.c_str(), last_frame_size, false)); |
+ // TODO(erikchen): Make test support SPDYSSL, SPDYNPN |
+ HttpStreamFactory::set_force_spdy_over_ssl(false); |
+ HttpStreamFactory::set_force_spdy_always(true); |
+ TestDelegate d; |
+ { |
+ SpdyURLRequestContext spdy_url_request_context(GetParam().protocol); |
+ net::URLRequest r( |
+ GURL("http://www.google.com/"), &d, &spdy_url_request_context); |
+ spdy_url_request_context.socket_factory(). |
+ AddSocketDataProvider(&data); |
+ spdy_url_request_context.socket_factory(). |
+ AddSocketDataProvider(&data2); |
- // Data frame to be sent once SETTINGS frame is received. |
- scoped_ptr<SpdyFrame> body3(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ d.set_quit_on_redirect(true); |
+ r.Start(); |
+ base::MessageLoop::current()->Run(); |
- // Fill in mock reads/writes. |
- std::vector<MockRead> reads; |
- std::vector<MockWrite> writes; |
- size_t i = 0; |
- writes.push_back(CreateMockWrite(*req, i++)); |
- while (i < num_writes - 2) |
- writes.push_back(CreateMockWrite(*body1, i++)); |
- writes.push_back(CreateMockWrite(*body2, i++)); |
+ EXPECT_EQ(1, d.received_redirect_count()); |
- // Construct read frame for SETTINGS that gives enough space to upload the |
- // rest of the data. |
- SettingsMap settings; |
- settings[SETTINGS_INITIAL_WINDOW_SIZE] = |
- SettingsFlagsAndValue( |
- SETTINGS_FLAG_NONE, kSpdyStreamInitialWindowSize * 2); |
- scoped_ptr<SpdyFrame> settings_frame_large( |
- spdy_util_.ConstructSpdySettings(settings)); |
+ r.FollowDeferredRedirect(); |
+ base::MessageLoop::current()->Run(); |
+ EXPECT_EQ(1, d.response_started_count()); |
+ EXPECT_FALSE(d.received_data_before_response()); |
+ EXPECT_EQ(net::URLRequestStatus::SUCCESS, r.status().status()); |
+ std::string contents("hello!"); |
+ EXPECT_EQ(contents, d.data_received()); |
+ } |
+ EXPECT_TRUE(data.at_read_eof()); |
+ EXPECT_TRUE(data.at_write_eof()); |
+ EXPECT_TRUE(data2.at_read_eof()); |
+ EXPECT_TRUE(data2.at_write_eof()); |
+} |
- reads.push_back(CreateMockRead(*settings_frame_large, i++)); |
+// Send a spdy request to www.google.com. Get a pushed stream that redirects to |
+// www.foo.com. |
+TEST_P(SpdyNetworkTransactionTest, RedirectServerPush) { |
+ const SpdyHeaderInfo kSynStartHeader = spdy_util_.MakeSpdyHeader(SYN_STREAM); |
- scoped_ptr<SpdyFrame> session_window_update( |
- spdy_util_.ConstructSpdyWindowUpdate(0, kUploadDataSize)); |
- if (GetParam().protocol >= kProtoSPDY31) |
- reads.push_back(CreateMockRead(*session_window_update, i++)); |
+ scoped_ptr<SpdyHeaderBlock> headers( |
+ spdy_util_.ConstructGetHeaderBlock("http://www.google.com/")); |
+ (*headers)["user-agent"] = ""; |
+ (*headers)["accept-encoding"] = "gzip,deflate"; |
- writes.push_back(CreateMockWrite(*body3, i++)); |
+ // Setup writes/reads to www.google.com |
+ scoped_ptr<SpdyFrame> req( |
+ spdy_util_.ConstructSpdyFrame(kSynStartHeader, headers.Pass())); |
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyFrame> rep( |
+ spdy_util_.ConstructSpdyPush(NULL, |
+ 0, |
+ 2, |
+ 1, |
+ "http://www.google.com/foo.dat", |
+ "301 Moved Permanently", |
+ "http://www.foo.com/index.php")); |
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ scoped_ptr<SpdyFrame> rst( |
+ spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_CANCEL)); |
+ MockWrite writes[] = { |
+ CreateMockWrite(*req, 1), |
+ CreateMockWrite(*rst, 6), |
+ }; |
+ MockRead reads[] = { |
+ CreateMockRead(*resp, 2), |
+ CreateMockRead(*rep, 3), |
+ CreateMockRead(*body, 4), |
+ MockRead(ASYNC, ERR_IO_PENDING, 5), // Force a pause |
+ MockRead(ASYNC, 0, 0, 7) // EOF |
+ }; |
- scoped_ptr<SpdyFrame> reply(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); |
- reads.push_back(CreateMockRead(*reply, i++)); |
- reads.push_back(CreateMockRead(*body2, i++)); |
- reads.push_back(CreateMockRead(*body3, i++)); |
- reads.push_back(MockRead(ASYNC, 0, i++)); // EOF |
+ // Setup writes/reads to www.foo.com |
+ scoped_ptr<SpdyHeaderBlock> headers2( |
+ spdy_util_.ConstructGetHeaderBlock("http://www.foo.com/index.php")); |
+ (*headers2)["user-agent"] = ""; |
+ (*headers2)["accept-encoding"] = "gzip,deflate"; |
+ scoped_ptr<SpdyFrame> req2( |
+ spdy_util_.ConstructSpdyFrame(kSynStartHeader, headers2.Pass())); |
+ scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ MockWrite writes2[] = { |
+ CreateMockWrite(*req2, 1), |
+ }; |
+ MockRead reads2[] = { |
+ CreateMockRead(*resp2, 2), |
+ CreateMockRead(*body2, 3), |
+ MockRead(ASYNC, 0, 0, 5) // EOF |
+ }; |
+ OrderedSocketData data(reads, arraysize(reads), |
+ writes, arraysize(writes)); |
+ OrderedSocketData data2(reads2, arraysize(reads2), |
+ writes2, arraysize(writes2)); |
- // Force all writes to happen before any read, last write will not |
- // actually queue a frame, due to window size being 0. |
- DeterministicSocketData data(vector_as_array(&reads), reads.size(), |
- vector_as_array(&writes), writes.size()); |
+ // TODO(erikchen): Make test support SPDYSSL, SPDYNPN |
+ HttpStreamFactory::set_force_spdy_over_ssl(false); |
+ HttpStreamFactory::set_force_spdy_always(true); |
+ TestDelegate d; |
+ TestDelegate d2; |
+ SpdyURLRequestContext spdy_url_request_context(GetParam().protocol); |
+ { |
+ net::URLRequest r( |
+ GURL("http://www.google.com/"), &d, &spdy_url_request_context); |
+ spdy_url_request_context.socket_factory(). |
+ AddSocketDataProvider(&data); |
- ScopedVector<UploadElementReader> element_readers; |
- std::string upload_data_string(kSpdyStreamInitialWindowSize, 'a'); |
- upload_data_string.append(kUploadData, kUploadDataSize); |
- element_readers.push_back(new UploadBytesElementReader( |
- upload_data_string.c_str(), upload_data_string.size())); |
- UploadDataStream upload_data_stream(&element_readers, 0); |
+ r.Start(); |
+ base::MessageLoop::current()->Run(); |
- HttpRequestInfo request; |
- request.method = "POST"; |
- request.url = GURL("http://www.google.com/"); |
- request.upload_data_stream = &upload_data_stream; |
- NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY, |
- BoundNetLog(), GetParam(), NULL); |
- helper.SetDeterministic(); |
- helper.RunPreTestSetup(); |
- helper.AddDeterministicData(&data); |
+ EXPECT_EQ(0, d.received_redirect_count()); |
+ std::string contents("hello!"); |
+ EXPECT_EQ(contents, d.data_received()); |
- HttpNetworkTransaction* trans = helper.trans(); |
+ net::URLRequest r2( |
+ GURL("http://www.google.com/foo.dat"), &d2, &spdy_url_request_context); |
+ spdy_url_request_context.socket_factory(). |
+ AddSocketDataProvider(&data2); |
- TestCompletionCallback callback; |
- int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); |
- EXPECT_EQ(ERR_IO_PENDING, rv); |
+ d2.set_quit_on_redirect(true); |
+ r2.Start(); |
+ base::MessageLoop::current()->Run(); |
+ EXPECT_EQ(1, d2.received_redirect_count()); |
- data.RunFor(num_writes - 1); // Write as much as we can. |
+ r2.FollowDeferredRedirect(); |
+ base::MessageLoop::current()->Run(); |
+ EXPECT_EQ(1, d2.response_started_count()); |
+ EXPECT_FALSE(d2.received_data_before_response()); |
+ EXPECT_EQ(net::URLRequestStatus::SUCCESS, r2.status().status()); |
+ std::string contents2("hello!"); |
+ EXPECT_EQ(contents2, d2.data_received()); |
+ } |
+ data.CompleteRead(); |
+ data2.CompleteRead(); |
+ EXPECT_TRUE(data.at_read_eof()); |
+ EXPECT_TRUE(data.at_write_eof()); |
+ EXPECT_TRUE(data2.at_read_eof()); |
+ EXPECT_TRUE(data2.at_write_eof()); |
+} |
- SpdyHttpStream* stream = static_cast<SpdyHttpStream*>(trans->stream_.get()); |
- ASSERT_TRUE(stream != NULL); |
- ASSERT_TRUE(stream->stream() != NULL); |
- EXPECT_EQ(0, stream->stream()->send_window_size()); |
- |
- // All the body data should have been read. |
- // TODO(satorux): This is because of the weirdness in reading the request |
- // body in OnSendBodyComplete(). See crbug.com/113107. |
- EXPECT_TRUE(upload_data_stream.IsEOF()); |
- // But the body is not yet fully sent (kUploadData is not yet sent) |
- // since we're send-stalled. |
- EXPECT_TRUE(stream->stream()->send_stalled_by_flow_control()); |
- |
- data.RunFor(6); // Read in SETTINGS frame to unstall. |
- rv = callback.WaitForResult(); |
- helper.VerifyDataConsumed(); |
- // If stream is NULL, that means it was unstalled and closed. |
- EXPECT_TRUE(stream->stream() == NULL); |
-} |
- |
-// Test we correctly handle the case where the SETTINGS frame results in a |
-// negative send window size. |
-TEST_P(SpdyNetworkTransactionTest, FlowControlNegativeSendWindowSize) { |
- if (GetParam().protocol < kProtoSPDY3) |
- return; |
+TEST_P(SpdyNetworkTransactionTest, ServerPushSingleDataFrame) { |
+ scoped_ptr<SpdyFrame> stream1_syn( |
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
+ scoped_ptr<SpdyFrame> stream1_body( |
+ spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ MockWrite writes[] = { |
+ CreateMockWrite(*stream1_syn, 1), |
+ }; |
- // Number of frames we need to send to zero out the window size: data |
- // frames plus SYN_STREAM plus the last data frame; also we need another |
- // data frame that we will send once the SETTING is received, therefore +3. |
- size_t num_writes = kSpdyStreamInitialWindowSize / kMaxSpdyFrameChunkSize + 3; |
+ scoped_ptr<SpdyFrame> |
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyFrame> |
+ stream2_syn(spdy_util_.ConstructSpdyPush(NULL, |
+ 0, |
+ 2, |
+ 1, |
+ "http://www.google.com/foo.dat")); |
+ const char kPushedData[] = "pushed"; |
+ scoped_ptr<SpdyFrame> stream2_body( |
+ spdy_util_.ConstructSpdyBodyFrame( |
+ 2, kPushedData, strlen(kPushedData), true)); |
+ MockRead reads[] = { |
+ CreateMockRead(*stream1_reply, 2), |
+ CreateMockRead(*stream2_syn, 3), |
+ CreateMockRead(*stream1_body, 4, SYNCHRONOUS), |
+ CreateMockRead(*stream2_body, 5), |
+ MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause |
+ }; |
- // Calculate last frame's size; 0 size data frame is legal. |
- size_t last_frame_size = |
- kSpdyStreamInitialWindowSize % kMaxSpdyFrameChunkSize; |
+ HttpResponseInfo response; |
+ HttpResponseInfo response2; |
+ std::string expected_push_result("pushed"); |
+ OrderedSocketData data(reads, arraysize(reads), |
+ writes, arraysize(writes)); |
+ RunServerPushTest(&data, |
+ &response, |
+ &response2, |
+ expected_push_result); |
- // Construct content for a data frame of maximum size. |
- std::string content(kMaxSpdyFrameChunkSize, 'a'); |
+ // Verify the SYN_REPLY. |
+ EXPECT_TRUE(response.headers.get() != NULL); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
- scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyPost( |
- kRequestUrl, 1, kSpdyStreamInitialWindowSize + kUploadDataSize, |
- LOWEST, NULL, 0)); |
+ // Verify the pushed stream. |
+ EXPECT_TRUE(response2.headers.get() != NULL); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); |
+} |
- // Full frames. |
- scoped_ptr<SpdyFrame> body1( |
- spdy_util_.ConstructSpdyBodyFrame( |
- 1, content.c_str(), content.size(), false)); |
+TEST_P(SpdyNetworkTransactionTest, ServerPushBeforeSynReply) { |
+ scoped_ptr<SpdyFrame> stream1_syn( |
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
+ scoped_ptr<SpdyFrame> stream1_body( |
+ spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ MockWrite writes[] = { |
+ CreateMockWrite(*stream1_syn, 1), |
+ }; |
- // Last frame to zero out the window size. |
- scoped_ptr<SpdyFrame> body2( |
+ scoped_ptr<SpdyFrame> |
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyFrame> |
+ stream2_syn(spdy_util_.ConstructSpdyPush(NULL, |
+ 0, |
+ 2, |
+ 1, |
+ "http://www.google.com/foo.dat")); |
+ const char kPushedData[] = "pushed"; |
+ scoped_ptr<SpdyFrame> stream2_body( |
spdy_util_.ConstructSpdyBodyFrame( |
- 1, content.c_str(), last_frame_size, false)); |
+ 2, kPushedData, strlen(kPushedData), true)); |
+ MockRead reads[] = { |
+ CreateMockRead(*stream2_syn, 2), |
+ CreateMockRead(*stream1_reply, 3), |
+ CreateMockRead(*stream1_body, 4, SYNCHRONOUS), |
+ CreateMockRead(*stream2_body, 5), |
+ MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause |
+ }; |
- // Data frame to be sent once SETTINGS frame is received. |
- scoped_ptr<SpdyFrame> body3(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ HttpResponseInfo response; |
+ HttpResponseInfo response2; |
+ std::string expected_push_result("pushed"); |
+ OrderedSocketData data(reads, arraysize(reads), |
+ writes, arraysize(writes)); |
+ RunServerPushTest(&data, |
+ &response, |
+ &response2, |
+ expected_push_result); |
- // Fill in mock reads/writes. |
- std::vector<MockRead> reads; |
- std::vector<MockWrite> writes; |
- size_t i = 0; |
- writes.push_back(CreateMockWrite(*req, i++)); |
- while (i < num_writes - 2) |
- writes.push_back(CreateMockWrite(*body1, i++)); |
- writes.push_back(CreateMockWrite(*body2, i++)); |
+ // Verify the SYN_REPLY. |
+ EXPECT_TRUE(response.headers.get() != NULL); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
- // Construct read frame for SETTINGS that makes the send_window_size |
- // negative. |
- SettingsMap new_settings; |
- new_settings[SETTINGS_INITIAL_WINDOW_SIZE] = |
- SettingsFlagsAndValue( |
- SETTINGS_FLAG_NONE, kSpdyStreamInitialWindowSize / 2); |
- scoped_ptr<SpdyFrame> settings_frame_small( |
- spdy_util_.ConstructSpdySettings(new_settings)); |
- // Construct read frames for WINDOW_UPDATE that makes the send_window_size |
- // positive. |
- scoped_ptr<SpdyFrame> session_window_update_init_size( |
- spdy_util_.ConstructSpdyWindowUpdate(0, kSpdyStreamInitialWindowSize)); |
- scoped_ptr<SpdyFrame> window_update_init_size( |
- spdy_util_.ConstructSpdyWindowUpdate(1, kSpdyStreamInitialWindowSize)); |
+ // Verify the pushed stream. |
+ EXPECT_TRUE(response2.headers.get() != NULL); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); |
+} |
- reads.push_back(CreateMockRead(*settings_frame_small, i++)); |
+TEST_P(SpdyNetworkTransactionTest, ServerPushSingleDataFrame2) { |
+ scoped_ptr<SpdyFrame> stream1_syn( |
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
+ MockWrite writes[] = { CreateMockWrite(*stream1_syn, 1), }; |
- if (GetParam().protocol >= kProtoSPDY3) |
- reads.push_back(CreateMockRead(*session_window_update_init_size, i++)); |
+ scoped_ptr<SpdyFrame> |
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyFrame> |
+ stream2_syn(spdy_util_.ConstructSpdyPush(NULL, |
+ 0, |
+ 2, |
+ 1, |
+ "http://www.google.com/foo.dat")); |
+ const char kPushedData[] = "pushed"; |
+ scoped_ptr<SpdyFrame> stream2_body( |
+ spdy_util_.ConstructSpdyBodyFrame( |
+ 2, kPushedData, strlen(kPushedData), true)); |
+ scoped_ptr<SpdyFrame> |
+ stream1_body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ MockRead reads[] = { |
+ CreateMockRead(*stream1_reply, 2), |
+ CreateMockRead(*stream2_syn, 3), |
+ CreateMockRead(*stream2_body, 4), |
+ CreateMockRead(*stream1_body, 5, SYNCHRONOUS), |
+ MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause |
+ }; |
- reads.push_back(CreateMockRead(*window_update_init_size, i++)); |
+ HttpResponseInfo response; |
+ HttpResponseInfo response2; |
+ std::string expected_push_result("pushed"); |
+ OrderedSocketData data(reads, arraysize(reads), |
+ writes, arraysize(writes)); |
+ RunServerPushTest(&data, |
+ &response, |
+ &response2, |
+ expected_push_result); |
- writes.push_back(CreateMockWrite(*body3, i++)); |
+ // Verify the SYN_REPLY. |
+ EXPECT_TRUE(response.headers.get() != NULL); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
- scoped_ptr<SpdyFrame> reply(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); |
- reads.push_back(CreateMockRead(*reply, i++)); |
- reads.push_back(CreateMockRead(*body2, i++)); |
- reads.push_back(CreateMockRead(*body3, i++)); |
- reads.push_back(MockRead(ASYNC, 0, i++)); // EOF |
+ // Verify the pushed stream. |
+ EXPECT_TRUE(response2.headers.get() != NULL); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); |
+} |
- // Force all writes to happen before any read, last write will not |
- // actually queue a frame, due to window size being 0. |
- DeterministicSocketData data(vector_as_array(&reads), reads.size(), |
- vector_as_array(&writes), writes.size()); |
+TEST_P(SpdyNetworkTransactionTest, ServerPushServerAborted) { |
+ scoped_ptr<SpdyFrame> stream1_syn( |
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
+ scoped_ptr<SpdyFrame> stream1_body( |
+ spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ MockWrite writes[] = { |
+ CreateMockWrite(*stream1_syn, 1), |
+ }; |
- ScopedVector<UploadElementReader> element_readers; |
- std::string upload_data_string(kSpdyStreamInitialWindowSize, 'a'); |
- upload_data_string.append(kUploadData, kUploadDataSize); |
- element_readers.push_back(new UploadBytesElementReader( |
- upload_data_string.c_str(), upload_data_string.size())); |
- UploadDataStream upload_data_stream(&element_readers, 0); |
+ scoped_ptr<SpdyFrame> |
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyFrame> |
+ stream2_syn(spdy_util_.ConstructSpdyPush(NULL, |
+ 0, |
+ 2, |
+ 1, |
+ "http://www.google.com/foo.dat")); |
+ scoped_ptr<SpdyFrame> stream2_rst( |
+ spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_PROTOCOL_ERROR)); |
+ MockRead reads[] = { |
+ CreateMockRead(*stream1_reply, 2), |
+ CreateMockRead(*stream2_syn, 3), |
+ CreateMockRead(*stream2_rst, 4), |
+ CreateMockRead(*stream1_body, 5, SYNCHRONOUS), |
+ MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause |
+ }; |
- HttpRequestInfo request; |
- request.method = "POST"; |
- request.url = GURL("http://www.google.com/"); |
- request.upload_data_stream = &upload_data_stream; |
- NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY, |
+ OrderedSocketData data(reads, arraysize(reads), |
+ writes, arraysize(writes)); |
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
BoundNetLog(), GetParam(), NULL); |
- helper.SetDeterministic(); |
+ |
helper.RunPreTestSetup(); |
- helper.AddDeterministicData(&data); |
+ helper.AddData(&data); |
HttpNetworkTransaction* trans = helper.trans(); |
+ // Start the transaction with basic parameters. |
TestCompletionCallback callback; |
- int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); |
+ int rv = trans->Start( |
+ &CreateGetRequest(), callback.callback(), BoundNetLog()); |
EXPECT_EQ(ERR_IO_PENDING, rv); |
+ rv = callback.WaitForResult(); |
+ EXPECT_EQ(OK, rv); |
- data.RunFor(num_writes - 1); // Write as much as we can. |
- |
- SpdyHttpStream* stream = static_cast<SpdyHttpStream*>(trans->stream_.get()); |
- ASSERT_TRUE(stream != NULL); |
- ASSERT_TRUE(stream->stream() != NULL); |
- EXPECT_EQ(0, stream->stream()->send_window_size()); |
- |
- // All the body data should have been read. |
- // TODO(satorux): This is because of the weirdness in reading the request |
- // body in OnSendBodyComplete(). See crbug.com/113107. |
- EXPECT_TRUE(upload_data_stream.IsEOF()); |
- // But the body is not yet fully sent (kUploadData is not yet sent) |
- // since we're send-stalled. |
- EXPECT_TRUE(stream->stream()->send_stalled_by_flow_control()); |
+ // Verify that we consumed all test data. |
+ EXPECT_TRUE(data.at_read_eof()) << "Read count: " |
+ << data.read_count() |
+ << " Read index: " |
+ << data.read_index(); |
+ EXPECT_TRUE(data.at_write_eof()) << "Write count: " |
+ << data.write_count() |
+ << " Write index: " |
+ << data.write_index(); |
- // Read in WINDOW_UPDATE or SETTINGS frame. |
- data.RunFor((GetParam().protocol >= kProtoSPDY31) ? 8 : 7); |
- rv = callback.WaitForResult(); |
- helper.VerifyDataConsumed(); |
+ // Verify the SYN_REPLY. |
+ HttpResponseInfo response = *trans->GetResponseInfo(); |
+ EXPECT_TRUE(response.headers.get() != NULL); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
} |
-TEST_P(SpdyNetworkTransactionTest, ResetReplyWithTransferEncoding) { |
- // Construct the request. |
- scoped_ptr<SpdyFrame> req( |
+// Verify that we don't leak streams and that we properly send a reset |
+// if the server pushes the same stream twice. |
+TEST_P(SpdyNetworkTransactionTest, ServerPushDuplicate) { |
+ scoped_ptr<SpdyFrame> stream1_syn( |
spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- scoped_ptr<SpdyFrame> rst( |
- spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR)); |
+ scoped_ptr<SpdyFrame> stream1_body( |
+ spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ scoped_ptr<SpdyFrame> stream3_rst( |
+ spdy_util_.ConstructSpdyRstStream(4, RST_STREAM_PROTOCOL_ERROR)); |
MockWrite writes[] = { |
- CreateMockWrite(*req), |
- CreateMockWrite(*rst), |
+ CreateMockWrite(*stream1_syn, 1), |
+ CreateMockWrite(*stream3_rst, 5), |
}; |
- const char* const headers[] = { |
- "transfer-encoding", "chunked" |
- }; |
- scoped_ptr<SpdyFrame> resp( |
- spdy_util_.ConstructSpdyGetSynReply(headers, 1, 1)); |
- scoped_ptr<SpdyFrame> body( |
- spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ scoped_ptr<SpdyFrame> |
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyFrame> |
+ stream2_syn(spdy_util_.ConstructSpdyPush(NULL, |
+ 0, |
+ 2, |
+ 1, |
+ "http://www.google.com/foo.dat")); |
+ const char kPushedData[] = "pushed"; |
+ scoped_ptr<SpdyFrame> stream2_body( |
+ spdy_util_.ConstructSpdyBodyFrame( |
+ 2, kPushedData, strlen(kPushedData), true)); |
+ scoped_ptr<SpdyFrame> |
+ stream3_syn(spdy_util_.ConstructSpdyPush(NULL, |
+ 0, |
+ 4, |
+ 1, |
+ "http://www.google.com/foo.dat")); |
MockRead reads[] = { |
- CreateMockRead(*resp), |
- CreateMockRead(*body), |
- MockRead(ASYNC, 0, 0) // EOF |
+ CreateMockRead(*stream1_reply, 2), |
+ CreateMockRead(*stream2_syn, 3), |
+ CreateMockRead(*stream3_syn, 4), |
+ CreateMockRead(*stream1_body, 6, SYNCHRONOUS), |
+ CreateMockRead(*stream2_body, 7), |
+ MockRead(ASYNC, ERR_IO_PENDING, 8), // Force a pause |
}; |
- DelayedSocketData data(1, reads, arraysize(reads), |
+ HttpResponseInfo response; |
+ HttpResponseInfo response2; |
+ std::string expected_push_result("pushed"); |
+ OrderedSocketData data(reads, arraysize(reads), |
writes, arraysize(writes)); |
- NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
- BoundNetLog(), GetParam(), NULL); |
- helper.RunToCompletion(&data); |
- TransactionHelperResult out = helper.output(); |
- EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); |
+ RunServerPushTest(&data, |
+ &response, |
+ &response2, |
+ expected_push_result); |
- helper.session()->spdy_session_pool()->CloseAllSessions(); |
- helper.VerifyDataConsumed(); |
+ // Verify the SYN_REPLY. |
+ EXPECT_TRUE(response.headers.get() != NULL); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
+ |
+ // Verify the pushed stream. |
+ EXPECT_TRUE(response2.headers.get() != NULL); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); |
} |
-TEST_P(SpdyNetworkTransactionTest, ResetPushWithTransferEncoding) { |
- // Construct the request. |
- scoped_ptr<SpdyFrame> req( |
+TEST_P(SpdyNetworkTransactionTest, ServerPushMultipleDataFrame) { |
+ scoped_ptr<SpdyFrame> stream1_syn( |
spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- scoped_ptr<SpdyFrame> rst( |
- spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_PROTOCOL_ERROR)); |
+ scoped_ptr<SpdyFrame> stream1_body( |
+ spdy_util_.ConstructSpdyBodyFrame(1, true)); |
MockWrite writes[] = { |
- CreateMockWrite(*req), |
- CreateMockWrite(*rst), |
+ CreateMockWrite(*stream1_syn, 1), |
}; |
- scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- const char* const headers[] = { |
- "transfer-encoding", "chunked" |
- }; |
- scoped_ptr<SpdyFrame> push( |
- spdy_util_.ConstructSpdyPush(headers, arraysize(headers) / 2, |
- 2, 1, "http://www.google.com/1")); |
- scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ scoped_ptr<SpdyFrame> |
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyFrame> |
+ stream2_syn(spdy_util_.ConstructSpdyPush(NULL, |
+ 0, |
+ 2, |
+ 1, |
+ "http://www.google.com/foo.dat")); |
+ static const char kPushedData[] = "pushed my darling hello my baby"; |
+ scoped_ptr<SpdyFrame> stream2_body_base( |
+ spdy_util_.ConstructSpdyBodyFrame( |
+ 2, kPushedData, strlen(kPushedData), true)); |
+ const size_t kChunkSize = strlen(kPushedData) / 4; |
+ scoped_ptr<SpdyFrame> stream2_body1( |
+ new SpdyFrame(stream2_body_base->data(), kChunkSize, false)); |
+ scoped_ptr<SpdyFrame> stream2_body2( |
+ new SpdyFrame(stream2_body_base->data() + kChunkSize, kChunkSize, false)); |
+ scoped_ptr<SpdyFrame> stream2_body3( |
+ new SpdyFrame(stream2_body_base->data() + 2 * kChunkSize, |
+ kChunkSize, false)); |
+ scoped_ptr<SpdyFrame> stream2_body4( |
+ new SpdyFrame(stream2_body_base->data() + 3 * kChunkSize, |
+ stream2_body_base->size() - 3 * kChunkSize, false)); |
MockRead reads[] = { |
- CreateMockRead(*resp), |
- CreateMockRead(*push), |
- CreateMockRead(*body), |
- MockRead(ASYNC, 0, 0) // EOF |
+ CreateMockRead(*stream1_reply, 2), |
+ CreateMockRead(*stream2_syn, 3), |
+ CreateMockRead(*stream2_body1, 4), |
+ CreateMockRead(*stream2_body2, 5), |
+ CreateMockRead(*stream2_body3, 6), |
+ CreateMockRead(*stream2_body4, 7), |
+ CreateMockRead(*stream1_body, 8, SYNCHRONOUS), |
+ MockRead(ASYNC, ERR_IO_PENDING, 9), // Force a pause |
}; |
- DelayedSocketData data(1, reads, arraysize(reads), |
+ HttpResponseInfo response; |
+ HttpResponseInfo response2; |
+ std::string expected_push_result("pushed my darling hello my baby"); |
+ OrderedSocketData data(reads, arraysize(reads), |
writes, arraysize(writes)); |
- NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
- BoundNetLog(), GetParam(), NULL); |
- helper.RunToCompletion(&data); |
- TransactionHelperResult out = helper.output(); |
- EXPECT_EQ(OK, out.rv); |
- EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); |
- EXPECT_EQ("hello!", out.response_data); |
+ RunServerPushTest(&data, &response, &response2, kPushedData); |
- helper.session()->spdy_session_pool()->CloseAllSessions(); |
- helper.VerifyDataConsumed(); |
+ // Verify the SYN_REPLY. |
+ EXPECT_TRUE(response.headers.get() != NULL); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
+ |
+ // Verify the pushed stream. |
+ EXPECT_TRUE(response2.headers.get() != NULL); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); |
} |
-TEST_P(SpdyNetworkTransactionTest, CancelledTransaction) { |
- // Construct the request. |
- scoped_ptr<SpdyFrame> req( |
+TEST_P(SpdyNetworkTransactionTest, ServerPushMultipleDataFrameInterrupted) { |
+ scoped_ptr<SpdyFrame> stream1_syn( |
spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
+ scoped_ptr<SpdyFrame> stream1_body( |
+ spdy_util_.ConstructSpdyBodyFrame(1, true)); |
MockWrite writes[] = { |
- CreateMockWrite(*req), |
+ CreateMockWrite(*stream1_syn, 1), |
}; |
- scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyFrame> |
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyFrame> |
+ stream2_syn(spdy_util_.ConstructSpdyPush(NULL, |
+ 0, |
+ 2, |
+ 1, |
+ "http://www.google.com/foo.dat")); |
+ static const char kPushedData[] = "pushed my darling hello my baby"; |
+ scoped_ptr<SpdyFrame> stream2_body_base( |
+ spdy_util_.ConstructSpdyBodyFrame( |
+ 2, kPushedData, strlen(kPushedData), true)); |
+ const size_t kChunkSize = strlen(kPushedData) / 4; |
+ scoped_ptr<SpdyFrame> stream2_body1( |
+ new SpdyFrame(stream2_body_base->data(), kChunkSize, false)); |
+ scoped_ptr<SpdyFrame> stream2_body2( |
+ new SpdyFrame(stream2_body_base->data() + kChunkSize, kChunkSize, false)); |
+ scoped_ptr<SpdyFrame> stream2_body3( |
+ new SpdyFrame(stream2_body_base->data() + 2 * kChunkSize, |
+ kChunkSize, false)); |
+ scoped_ptr<SpdyFrame> stream2_body4( |
+ new SpdyFrame(stream2_body_base->data() + 3 * kChunkSize, |
+ stream2_body_base->size() - 3 * kChunkSize, false)); |
MockRead reads[] = { |
- CreateMockRead(*resp), |
- // This following read isn't used by the test, except during the |
- // RunUntilIdle() call at the end since the SpdySession survives the |
- // HttpNetworkTransaction and still tries to continue Read()'ing. Any |
- // MockRead will do here. |
- MockRead(ASYNC, 0, 0) // EOF |
+ CreateMockRead(*stream1_reply, 2), |
+ CreateMockRead(*stream2_syn, 3), |
+ CreateMockRead(*stream2_body1, 4), |
+ CreateMockRead(*stream2_body2, 5), |
+ MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause |
+ CreateMockRead(*stream2_body3, 7), |
+ CreateMockRead(*stream2_body4, 8), |
+ CreateMockRead(*stream1_body.get(), 9, SYNCHRONOUS), |
+ MockRead(ASYNC, ERR_IO_PENDING, 10) // Force a pause. |
}; |
- StaticSocketDataProvider data(reads, arraysize(reads), |
- writes, arraysize(writes)); |
+ HttpResponseInfo response; |
+ HttpResponseInfo response2; |
+ OrderedSocketData data(reads, arraysize(reads), |
+ writes, arraysize(writes)); |
+ RunServerPushTest(&data, &response, &response2, kPushedData); |
- NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
- BoundNetLog(), GetParam(), NULL); |
- helper.RunPreTestSetup(); |
- helper.AddData(&data); |
- HttpNetworkTransaction* trans = helper.trans(); |
+ // Verify the SYN_REPLY. |
+ EXPECT_TRUE(response.headers.get() != NULL); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
- TestCompletionCallback callback; |
- int rv = trans->Start( |
- &CreateGetRequest(), callback.callback(), BoundNetLog()); |
- EXPECT_EQ(ERR_IO_PENDING, rv); |
- helper.ResetTrans(); // Cancel the transaction. |
+ // Verify the pushed stream. |
+ EXPECT_TRUE(response2.headers.get() != NULL); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); |
+} |
- // Flush the MessageLoop while the SpdySessionDependencies (in particular, the |
- // MockClientSocketFactory) are still alive. |
- base::MessageLoop::current()->RunUntilIdle(); |
- helper.VerifyDataNotConsumed(); |
-} |
- |
-// Verify that the client sends a Rst Frame upon cancelling the stream. |
-TEST_P(SpdyNetworkTransactionTest, CancelledTransactionSendRst) { |
- scoped_ptr<SpdyFrame> req( |
+TEST_P(SpdyNetworkTransactionTest, ServerPushInvalidAssociatedStreamID0) { |
+ scoped_ptr<SpdyFrame> stream1_syn( |
spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- scoped_ptr<SpdyFrame> rst( |
- spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_CANCEL)); |
+ scoped_ptr<SpdyFrame> stream1_body( |
+ spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ scoped_ptr<SpdyFrame> stream2_rst( |
+ spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_REFUSED_STREAM)); |
MockWrite writes[] = { |
- CreateMockWrite(*req, 0, SYNCHRONOUS), |
- CreateMockWrite(*rst, 2, SYNCHRONOUS), |
+ CreateMockWrite(*stream1_syn, 1), |
+ CreateMockWrite(*stream2_rst, 4), |
}; |
- scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyFrame> |
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyFrame> |
+ stream2_syn(spdy_util_.ConstructSpdyPush(NULL, |
+ 0, |
+ 2, |
+ 0, |
+ "http://www.google.com/foo.dat")); |
MockRead reads[] = { |
- CreateMockRead(*resp, 1, ASYNC), |
- MockRead(ASYNC, 0, 0, 3) // EOF |
+ CreateMockRead(*stream1_reply, 2), |
+ CreateMockRead(*stream2_syn, 3), |
+ CreateMockRead(*stream1_body, 4), |
+ MockRead(ASYNC, ERR_IO_PENDING, 5) // Force a pause |
}; |
- DeterministicSocketData data(reads, arraysize(reads), |
- writes, arraysize(writes)); |
- |
+ OrderedSocketData data(reads, arraysize(reads), |
+ writes, arraysize(writes)); |
NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
- BoundNetLog(), |
- GetParam(), NULL); |
- helper.SetDeterministic(); |
+ BoundNetLog(), GetParam(), NULL); |
+ |
helper.RunPreTestSetup(); |
- helper.AddDeterministicData(&data); |
+ helper.AddData(&data); |
+ |
HttpNetworkTransaction* trans = helper.trans(); |
+ // Start the transaction with basic parameters. |
TestCompletionCallback callback; |
- |
int rv = trans->Start( |
&CreateGetRequest(), callback.callback(), BoundNetLog()); |
EXPECT_EQ(ERR_IO_PENDING, rv); |
+ rv = callback.WaitForResult(); |
+ EXPECT_EQ(OK, rv); |
- data.SetStop(2); |
- data.Run(); |
- helper.ResetTrans(); |
- data.SetStop(20); |
- data.Run(); |
+ // Verify that we consumed all test data. |
+ EXPECT_TRUE(data.at_read_eof()) << "Read count: " |
+ << data.read_count() |
+ << " Read index: " |
+ << data.read_index(); |
+ EXPECT_TRUE(data.at_write_eof()) << "Write count: " |
+ << data.write_count() |
+ << " Write index: " |
+ << data.write_index(); |
- helper.VerifyDataConsumed(); |
+ // Verify the SYN_REPLY. |
+ HttpResponseInfo response = *trans->GetResponseInfo(); |
+ EXPECT_TRUE(response.headers.get() != NULL); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
} |
-// Verify that the client can correctly deal with the user callback attempting |
-// to start another transaction on a session that is closing down. See |
-// http://crbug.com/47455 |
-TEST_P(SpdyNetworkTransactionTest, StartTransactionOnReadCallback) { |
- scoped_ptr<SpdyFrame> req( |
+TEST_P(SpdyNetworkTransactionTest, ServerPushInvalidAssociatedStreamID9) { |
+ scoped_ptr<SpdyFrame> stream1_syn( |
spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- MockWrite writes[] = { CreateMockWrite(*req) }; |
- MockWrite writes2[] = { CreateMockWrite(*req) }; |
- |
- // The indicated length of this frame is longer than its actual length. When |
- // the session receives an empty frame after this one, it shuts down the |
- // session, and calls the read callback with the incomplete data. |
- const uint8 kGetBodyFrame2[] = { |
- 0x00, 0x00, 0x00, 0x01, |
- 0x01, 0x00, 0x00, 0x07, |
- 'h', 'e', 'l', 'l', 'o', '!', |
+ scoped_ptr<SpdyFrame> stream1_body( |
+ spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ scoped_ptr<SpdyFrame> stream2_rst( |
+ spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_INVALID_STREAM)); |
+ MockWrite writes[] = { |
+ CreateMockWrite(*stream1_syn, 1), |
+ CreateMockWrite(*stream2_rst, 4), |
}; |
- scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyFrame> |
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyFrame> |
+ stream2_syn(spdy_util_.ConstructSpdyPush(NULL, |
+ 0, |
+ 2, |
+ 9, |
+ "http://www.google.com/foo.dat")); |
MockRead reads[] = { |
- CreateMockRead(*resp, 2), |
- MockRead(ASYNC, ERR_IO_PENDING, 3), // Force a pause |
- MockRead(ASYNC, reinterpret_cast<const char*>(kGetBodyFrame2), |
- arraysize(kGetBodyFrame2), 4), |
+ CreateMockRead(*stream1_reply, 2), |
+ CreateMockRead(*stream2_syn, 3), |
+ CreateMockRead(*stream1_body, 4), |
MockRead(ASYNC, ERR_IO_PENDING, 5), // Force a pause |
- MockRead(ASYNC, 0, 0, 6), // EOF |
- }; |
- MockRead reads2[] = { |
- CreateMockRead(*resp, 2), |
- MockRead(ASYNC, 0, 0, 3), // EOF |
}; |
OrderedSocketData data(reads, arraysize(reads), |
writes, arraysize(writes)); |
- DelayedSocketData data2(1, reads2, arraysize(reads2), |
- writes2, arraysize(writes2)); |
- |
NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
BoundNetLog(), GetParam(), NULL); |
+ |
helper.RunPreTestSetup(); |
helper.AddData(&data); |
- helper.AddData(&data2); |
+ |
HttpNetworkTransaction* trans = helper.trans(); |
// Start the transaction with basic parameters. |
TestCompletionCallback callback; |
- int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); |
+ int rv = trans->Start( |
+ &CreateGetRequest(), callback.callback(), BoundNetLog()); |
EXPECT_EQ(ERR_IO_PENDING, rv); |
rv = callback.WaitForResult(); |
+ EXPECT_EQ(OK, rv); |
- const int kSize = 3000; |
- scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSize)); |
- rv = trans->Read( |
- buf.get(), |
- kSize, |
- base::Bind(&SpdyNetworkTransactionTest::StartTransactionCallback, |
- helper.session())); |
- // This forces an err_IO_pending, which sets the callback. |
- data.CompleteRead(); |
- // This finishes the read. |
- data.CompleteRead(); |
- helper.VerifyDataConsumed(); |
+ // Verify that we consumed all test data. |
+ EXPECT_TRUE(data.at_read_eof()) << "Read count: " |
+ << data.read_count() |
+ << " Read index: " |
+ << data.read_index(); |
+ EXPECT_TRUE(data.at_write_eof()) << "Write count: " |
+ << data.write_count() |
+ << " Write index: " |
+ << data.write_index(); |
+ |
+ // Verify the SYN_REPLY. |
+ HttpResponseInfo response = *trans->GetResponseInfo(); |
+ EXPECT_TRUE(response.headers.get() != NULL); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
} |
-// Verify that the client can correctly deal with the user callback deleting the |
-// transaction. Failures will usually be valgrind errors. See |
-// http://crbug.com/46925 |
-TEST_P(SpdyNetworkTransactionTest, DeleteSessionOnReadCallback) { |
- scoped_ptr<SpdyFrame> req( |
+TEST_P(SpdyNetworkTransactionTest, ServerPushNoURL) { |
+ scoped_ptr<SpdyFrame> stream1_syn( |
spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- MockWrite writes[] = { CreateMockWrite(*req) }; |
+ scoped_ptr<SpdyFrame> stream1_body( |
+ spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ scoped_ptr<SpdyFrame> stream2_rst( |
+ spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_PROTOCOL_ERROR)); |
+ MockWrite writes[] = { |
+ CreateMockWrite(*stream1_syn, 1), |
+ CreateMockWrite(*stream2_rst, 4), |
+ }; |
- scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ scoped_ptr<SpdyFrame> |
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyHeaderBlock> incomplete_headers(new SpdyHeaderBlock()); |
+ (*incomplete_headers)["hello"] = "bye"; |
+ (*incomplete_headers)[spdy_util_.GetStatusKey()] = "200 OK"; |
+ (*incomplete_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1"; |
+ scoped_ptr<SpdyFrame> stream2_syn( |
+ spdy_util_.ConstructSpdyControlFrame(incomplete_headers.Pass(), |
+ false, |
+ 2, // Stream ID |
+ LOWEST, |
+ SYN_STREAM, |
+ CONTROL_FLAG_NONE, |
+ // Associated stream ID |
+ 1)); |
MockRead reads[] = { |
- CreateMockRead(*resp.get(), 2), |
- MockRead(ASYNC, ERR_IO_PENDING, 3), // Force a pause |
- CreateMockRead(*body.get(), 4), |
- MockRead(ASYNC, 0, 0, 5), // EOF |
+ CreateMockRead(*stream1_reply, 2), |
+ CreateMockRead(*stream2_syn, 3), |
+ CreateMockRead(*stream1_body, 4), |
+ MockRead(ASYNC, ERR_IO_PENDING, 5) // Force a pause |
}; |
OrderedSocketData data(reads, arraysize(reads), |
writes, arraysize(writes)); |
- |
NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
BoundNetLog(), GetParam(), NULL); |
+ |
helper.RunPreTestSetup(); |
helper.AddData(&data); |
+ |
HttpNetworkTransaction* trans = helper.trans(); |
// Start the transaction with basic parameters. |
TestCompletionCallback callback; |
- int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); |
+ int rv = trans->Start( |
+ &CreateGetRequest(), callback.callback(), BoundNetLog()); |
EXPECT_EQ(ERR_IO_PENDING, rv); |
rv = callback.WaitForResult(); |
+ EXPECT_EQ(OK, rv); |
+ // Verify that we consumed all test data. |
+ EXPECT_TRUE(data.at_read_eof()) << "Read count: " |
+ << data.read_count() |
+ << " Read index: " |
+ << data.read_index(); |
+ EXPECT_TRUE(data.at_write_eof()) << "Write count: " |
+ << data.write_count() |
+ << " Write index: " |
+ << data.write_index(); |
- // Setup a user callback which will delete the session, and clear out the |
- // memory holding the stream object. Note that the callback deletes trans. |
- const int kSize = 3000; |
- scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSize)); |
- rv = trans->Read( |
- buf.get(), |
- kSize, |
- base::Bind(&SpdyNetworkTransactionTest::DeleteSessionCallback, |
- base::Unretained(&helper))); |
- ASSERT_EQ(ERR_IO_PENDING, rv); |
- data.CompleteRead(); |
- |
- // Finish running rest of tasks. |
- base::MessageLoop::current()->RunUntilIdle(); |
- helper.VerifyDataConsumed(); |
+ // Verify the SYN_REPLY. |
+ HttpResponseInfo response = *trans->GetResponseInfo(); |
+ EXPECT_TRUE(response.headers.get() != NULL); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
} |
-// Send a spdy request to www.google.com that gets redirected to www.foo.com. |
-TEST_P(SpdyNetworkTransactionTest, RedirectGetRequest) { |
- const SpdyHeaderInfo kSynStartHeader = spdy_util_.MakeSpdyHeader(SYN_STREAM); |
- scoped_ptr<SpdyHeaderBlock> headers( |
- spdy_util_.ConstructGetHeaderBlock("http://www.google.com/")); |
- (*headers)["user-agent"] = ""; |
- (*headers)["accept-encoding"] = "gzip,deflate"; |
- scoped_ptr<SpdyHeaderBlock> headers2( |
- spdy_util_.ConstructGetHeaderBlock("http://www.foo.com/index.php")); |
- (*headers2)["user-agent"] = ""; |
- (*headers2)["accept-encoding"] = "gzip,deflate"; |
- |
- // Setup writes/reads to www.google.com |
- scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyFrame( |
- kSynStartHeader, headers.Pass())); |
- scoped_ptr<SpdyFrame> req2(spdy_util_.ConstructSpdyFrame( |
- kSynStartHeader, headers2.Pass())); |
- scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReplyRedirect(1)); |
- MockWrite writes[] = { |
- CreateMockWrite(*req, 1), |
- }; |
- MockRead reads[] = { |
- CreateMockRead(*resp, 2), |
- MockRead(ASYNC, 0, 0, 3) // EOF |
- }; |
- |
- // Setup writes/reads to www.foo.com |
- scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- MockWrite writes2[] = { |
- CreateMockWrite(*req2, 1), |
- }; |
- MockRead reads2[] = { |
- CreateMockRead(*resp2, 2), |
- CreateMockRead(*body2, 3), |
- MockRead(ASYNC, 0, 0, 4) // EOF |
+// Verify that various SynReply headers parse correctly through the |
+// HTTP layer. |
+TEST_P(SpdyNetworkTransactionTest, SynReplyHeaders) { |
+ struct SynReplyHeadersTests { |
+ int num_headers; |
+ const char* extra_headers[5]; |
+ SpdyHeaderBlock expected_headers; |
+ } test_cases[] = { |
+ // This uses a multi-valued cookie header. |
+ { 2, |
+ { "cookie", "val1", |
+ "cookie", "val2", // will get appended separated by NULL |
+ NULL |
+ }, |
+ }, |
+ // This is the minimalist set of headers. |
+ { 0, |
+ { NULL }, |
+ }, |
+ // Headers with a comma separated list. |
+ { 1, |
+ { "cookie", "val1,val2", |
+ NULL |
+ }, |
+ } |
}; |
- OrderedSocketData data(reads, arraysize(reads), |
- writes, arraysize(writes)); |
- OrderedSocketData data2(reads2, arraysize(reads2), |
- writes2, arraysize(writes2)); |
- // TODO(erikchen): Make test support SPDYSSL, SPDYNPN |
- HttpStreamFactory::set_force_spdy_over_ssl(false); |
- HttpStreamFactory::set_force_spdy_always(true); |
- TestDelegate d; |
- { |
- SpdyURLRequestContext spdy_url_request_context(GetParam().protocol); |
- net::URLRequest r( |
- GURL("http://www.google.com/"), &d, &spdy_url_request_context); |
- spdy_url_request_context.socket_factory(). |
- AddSocketDataProvider(&data); |
- spdy_url_request_context.socket_factory(). |
- AddSocketDataProvider(&data2); |
+ test_cases[0].expected_headers["cookie"] = "val1"; |
+ test_cases[0].expected_headers["cookie"] += '\0'; |
+ test_cases[0].expected_headers["cookie"] += "val2"; |
+ test_cases[0].expected_headers["hello"] = "bye"; |
+ test_cases[0].expected_headers["status"] = "200"; |
+ test_cases[0].expected_headers["version"] = "HTTP/1.1"; |
- d.set_quit_on_redirect(true); |
- r.Start(); |
- base::MessageLoop::current()->Run(); |
+ test_cases[1].expected_headers["hello"] = "bye"; |
+ test_cases[1].expected_headers["status"] = "200"; |
+ test_cases[1].expected_headers["version"] = "HTTP/1.1"; |
- EXPECT_EQ(1, d.received_redirect_count()); |
+ test_cases[2].expected_headers["cookie"] = "val1,val2"; |
+ test_cases[2].expected_headers["hello"] = "bye"; |
+ test_cases[2].expected_headers["status"] = "200"; |
+ test_cases[2].expected_headers["version"] = "HTTP/1.1"; |
- r.FollowDeferredRedirect(); |
- base::MessageLoop::current()->Run(); |
- EXPECT_EQ(1, d.response_started_count()); |
- EXPECT_FALSE(d.received_data_before_response()); |
- EXPECT_EQ(net::URLRequestStatus::SUCCESS, r.status().status()); |
- std::string contents("hello!"); |
- EXPECT_EQ(contents, d.data_received()); |
- } |
- EXPECT_TRUE(data.at_read_eof()); |
- EXPECT_TRUE(data.at_write_eof()); |
- EXPECT_TRUE(data2.at_read_eof()); |
- EXPECT_TRUE(data2.at_write_eof()); |
-} |
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { |
+ scoped_ptr<SpdyFrame> req( |
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
+ MockWrite writes[] = { CreateMockWrite(*req) }; |
-// Send a spdy request to www.google.com. Get a pushed stream that redirects to |
-// www.foo.com. |
-TEST_P(SpdyNetworkTransactionTest, RedirectServerPush) { |
- const SpdyHeaderInfo kSynStartHeader = spdy_util_.MakeSpdyHeader(SYN_STREAM); |
+ scoped_ptr<SpdyFrame> resp( |
+ spdy_util_.ConstructSpdyGetSynReply(test_cases[i].extra_headers, |
+ test_cases[i].num_headers, |
+ 1)); |
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ MockRead reads[] = { |
+ CreateMockRead(*resp), |
+ CreateMockRead(*body), |
+ MockRead(ASYNC, 0, 0) // EOF |
+ }; |
- scoped_ptr<SpdyHeaderBlock> headers( |
- spdy_util_.ConstructGetHeaderBlock("http://www.google.com/")); |
- (*headers)["user-agent"] = ""; |
- (*headers)["accept-encoding"] = "gzip,deflate"; |
+ DelayedSocketData data(1, reads, arraysize(reads), |
+ writes, arraysize(writes)); |
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
+ BoundNetLog(), GetParam(), NULL); |
+ helper.RunToCompletion(&data); |
+ TransactionHelperResult out = helper.output(); |
- // Setup writes/reads to www.google.com |
- scoped_ptr<SpdyFrame> req( |
- spdy_util_.ConstructSpdyFrame(kSynStartHeader, headers.Pass())); |
- scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- scoped_ptr<SpdyFrame> rep( |
- spdy_util_.ConstructSpdyPush(NULL, |
- 0, |
- 2, |
- 1, |
- "http://www.google.com/foo.dat", |
- "301 Moved Permanently", |
- "http://www.foo.com/index.php")); |
- scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- scoped_ptr<SpdyFrame> rst( |
- spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_CANCEL)); |
- MockWrite writes[] = { |
- CreateMockWrite(*req, 1), |
- CreateMockWrite(*rst, 6), |
+ EXPECT_EQ(OK, out.rv); |
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); |
+ EXPECT_EQ("hello!", out.response_data); |
+ |
+ scoped_refptr<HttpResponseHeaders> headers = out.response_info.headers; |
+ EXPECT_TRUE(headers.get() != NULL); |
+ void* iter = NULL; |
+ std::string name, value; |
+ SpdyHeaderBlock header_block; |
+ while (headers->EnumerateHeaderLines(&iter, &name, &value)) { |
+ if (header_block[name].empty()) { |
+ header_block[name] = value; |
+ } else { |
+ header_block[name] += '\0'; |
+ header_block[name] += value; |
+ } |
+ } |
+ EXPECT_EQ(test_cases[i].expected_headers, header_block); |
+ } |
+} |
+ |
+// Verify that various SynReply headers parse vary fields correctly |
+// through the HTTP layer, and the response matches the request. |
+TEST_P(SpdyNetworkTransactionTest, SynReplyHeadersVary) { |
+ static const SpdyHeaderInfo syn_reply_info = { |
+ SYN_REPLY, // Syn Reply |
+ 1, // Stream ID |
+ 0, // Associated Stream ID |
+ ConvertRequestPriorityToSpdyPriority( |
+ LOWEST, spdy_util_.spdy_version()), |
+ kSpdyCredentialSlotUnused, |
+ CONTROL_FLAG_NONE, // Control Flags |
+ false, // Compressed |
+ RST_STREAM_INVALID, // Status |
+ NULL, // Data |
+ 0, // Data Length |
+ DATA_FLAG_NONE // Data Flags |
}; |
- MockRead reads[] = { |
- CreateMockRead(*resp, 2), |
- CreateMockRead(*rep, 3), |
- CreateMockRead(*body, 4), |
- MockRead(ASYNC, ERR_IO_PENDING, 5), // Force a pause |
- MockRead(ASYNC, 0, 0, 7) // EOF |
+ // Modify the following data to change/add test cases: |
+ struct SynReplyTests { |
+ const SpdyHeaderInfo* syn_reply; |
+ bool vary_matches; |
+ int num_headers[2]; |
+ const char* extra_headers[2][16]; |
+ } test_cases[] = { |
+ // Test the case of a multi-valued cookie. When the value is delimited |
+ // with NUL characters, it needs to be unfolded into multiple headers. |
+ { |
+ &syn_reply_info, |
+ true, |
+ { 1, 4 }, |
+ { { "cookie", "val1,val2", |
+ NULL |
+ }, |
+ { "vary", "cookie", |
+ spdy_util_.GetStatusKey(), "200", |
+ spdy_util_.GetPathKey(), "/index.php", |
+ spdy_util_.GetVersionKey(), "HTTP/1.1", |
+ NULL |
+ } |
+ } |
+ }, { // Multiple vary fields. |
+ &syn_reply_info, |
+ true, |
+ { 2, 5 }, |
+ { { "friend", "barney", |
+ "enemy", "snaggletooth", |
+ NULL |
+ }, |
+ { "vary", "friend", |
+ "vary", "enemy", |
+ spdy_util_.GetStatusKey(), "200", |
+ spdy_util_.GetPathKey(), "/index.php", |
+ spdy_util_.GetVersionKey(), "HTTP/1.1", |
+ NULL |
+ } |
+ } |
+ }, { // Test a '*' vary field. |
+ &syn_reply_info, |
+ false, |
+ { 1, 4 }, |
+ { { "cookie", "val1,val2", |
+ NULL |
+ }, |
+ { "vary", "*", |
+ spdy_util_.GetStatusKey(), "200", |
+ spdy_util_.GetPathKey(), "/index.php", |
+ spdy_util_.GetVersionKey(), "HTTP/1.1", |
+ NULL |
+ } |
+ } |
+ }, { // Multiple comma-separated vary fields. |
+ &syn_reply_info, |
+ true, |
+ { 2, 4 }, |
+ { { "friend", "barney", |
+ "enemy", "snaggletooth", |
+ NULL |
+ }, |
+ { "vary", "friend,enemy", |
+ spdy_util_.GetStatusKey(), "200", |
+ spdy_util_.GetPathKey(), "/index.php", |
+ spdy_util_.GetVersionKey(), "HTTP/1.1", |
+ NULL |
+ } |
+ } |
+ } |
}; |
- // Setup writes/reads to www.foo.com |
- scoped_ptr<SpdyHeaderBlock> headers2( |
- spdy_util_.ConstructGetHeaderBlock("http://www.foo.com/index.php")); |
- (*headers2)["user-agent"] = ""; |
- (*headers2)["accept-encoding"] = "gzip,deflate"; |
- scoped_ptr<SpdyFrame> req2( |
- spdy_util_.ConstructSpdyFrame(kSynStartHeader, headers2.Pass())); |
- scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- MockWrite writes2[] = { |
- CreateMockWrite(*req2, 1), |
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { |
+ // Construct the request. |
+ scoped_ptr<SpdyFrame> frame_req( |
+ spdy_util_.ConstructSpdyGet(test_cases[i].extra_headers[0], |
+ test_cases[i].num_headers[0], |
+ false, 1, LOWEST, true)); |
+ |
+ MockWrite writes[] = { |
+ CreateMockWrite(*frame_req), |
+ }; |
+ |
+ // Construct the reply. |
+ scoped_ptr<SpdyFrame> frame_reply( |
+ spdy_util_.ConstructSpdyFrame(*test_cases[i].syn_reply, |
+ test_cases[i].extra_headers[1], |
+ test_cases[i].num_headers[1], |
+ NULL, |
+ 0)); |
+ |
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ MockRead reads[] = { |
+ CreateMockRead(*frame_reply), |
+ CreateMockRead(*body), |
+ MockRead(ASYNC, 0, 0) // EOF |
+ }; |
+ |
+ // Attach the headers to the request. |
+ int header_count = test_cases[i].num_headers[0]; |
+ |
+ HttpRequestInfo request = CreateGetRequest(); |
+ for (int ct = 0; ct < header_count; ct++) { |
+ const char* header_key = test_cases[i].extra_headers[0][ct * 2]; |
+ const char* header_value = test_cases[i].extra_headers[0][ct * 2 + 1]; |
+ request.extra_headers.SetHeader(header_key, header_value); |
+ } |
+ |
+ DelayedSocketData data(1, reads, arraysize(reads), |
+ writes, arraysize(writes)); |
+ NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY, |
+ BoundNetLog(), GetParam(), NULL); |
+ helper.RunToCompletion(&data); |
+ TransactionHelperResult out = helper.output(); |
+ |
+ EXPECT_EQ(OK, out.rv) << i; |
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line) << i; |
+ EXPECT_EQ("hello!", out.response_data) << i; |
+ |
+ // Test the response information. |
+ EXPECT_TRUE(out.response_info.response_time > |
+ out.response_info.request_time) << i; |
+ base::TimeDelta test_delay = out.response_info.response_time - |
+ out.response_info.request_time; |
+ base::TimeDelta min_expected_delay; |
+ min_expected_delay.FromMilliseconds(10); |
+ EXPECT_GT(test_delay.InMillisecondsF(), |
+ min_expected_delay.InMillisecondsF()) << i; |
+ EXPECT_EQ(out.response_info.vary_data.is_valid(), |
+ test_cases[i].vary_matches) << i; |
+ |
+ // Check the headers. |
+ scoped_refptr<HttpResponseHeaders> headers = out.response_info.headers; |
+ ASSERT_TRUE(headers.get() != NULL) << i; |
+ void* iter = NULL; |
+ std::string name, value, lines; |
+ while (headers->EnumerateHeaderLines(&iter, &name, &value)) { |
+ lines.append(name); |
+ lines.append(": "); |
+ lines.append(value); |
+ lines.append("\n"); |
+ } |
+ |
+ // Construct the expected header reply string. |
+ SpdyHeaderBlock reply_headers; |
+ AppendToHeaderBlock(test_cases[i].extra_headers[1], |
+ test_cases[i].num_headers[1], |
+ &reply_headers); |
+ std::string expected_reply = |
+ spdy_util_.ConstructSpdyReplyString(reply_headers); |
+ EXPECT_EQ(expected_reply, lines) << i; |
+ } |
+} |
+ |
+// Verify that we don't crash on invalid SynReply responses. |
+TEST_P(SpdyNetworkTransactionTest, InvalidSynReply) { |
+ const SpdyHeaderInfo kSynStartHeader = { |
+ SYN_REPLY, // Kind = SynReply |
+ 1, // Stream ID |
+ 0, // Associated stream ID |
+ ConvertRequestPriorityToSpdyPriority( |
+ LOWEST, spdy_util_.spdy_version()), |
+ kSpdyCredentialSlotUnused, |
+ CONTROL_FLAG_NONE, // Control Flags |
+ false, // Compressed |
+ RST_STREAM_INVALID, // Status |
+ NULL, // Data |
+ 0, // Length |
+ DATA_FLAG_NONE // Data Flags |
}; |
- MockRead reads2[] = { |
- CreateMockRead(*resp2, 2), |
- CreateMockRead(*body2, 3), |
- MockRead(ASYNC, 0, 0, 5) // EOF |
+ |
+ struct InvalidSynReplyTests { |
+ int num_headers; |
+ const char* headers[10]; |
+ } test_cases[] = { |
+ // SYN_REPLY missing status header |
+ { 4, |
+ { "cookie", "val1", |
+ "cookie", "val2", |
+ spdy_util_.GetPathKey(), "/index.php", |
+ spdy_util_.GetVersionKey(), "HTTP/1.1", |
+ NULL |
+ }, |
+ }, |
+ // SYN_REPLY missing version header |
+ { 2, |
+ { "status", "200", |
+ spdy_util_.GetPathKey(), "/index.php", |
+ NULL |
+ }, |
+ }, |
+ // SYN_REPLY with no headers |
+ { 0, { NULL }, }, |
}; |
- OrderedSocketData data(reads, arraysize(reads), |
- writes, arraysize(writes)); |
- OrderedSocketData data2(reads2, arraysize(reads2), |
- writes2, arraysize(writes2)); |
- // TODO(erikchen): Make test support SPDYSSL, SPDYNPN |
- HttpStreamFactory::set_force_spdy_over_ssl(false); |
- HttpStreamFactory::set_force_spdy_always(true); |
- TestDelegate d; |
- TestDelegate d2; |
- SpdyURLRequestContext spdy_url_request_context(GetParam().protocol); |
- { |
- net::URLRequest r( |
- GURL("http://www.google.com/"), &d, &spdy_url_request_context); |
- spdy_url_request_context.socket_factory(). |
- AddSocketDataProvider(&data); |
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { |
+ scoped_ptr<SpdyFrame> req( |
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
+ scoped_ptr<SpdyFrame> rst( |
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR)); |
+ MockWrite writes[] = { |
+ CreateMockWrite(*req), |
+ CreateMockWrite(*rst), |
+ }; |
- r.Start(); |
- base::MessageLoop::current()->Run(); |
+ scoped_ptr<SpdyFrame> resp( |
+ spdy_util_.ConstructSpdyFrame(kSynStartHeader, |
+ NULL, 0, |
+ test_cases[i].headers, |
+ test_cases[i].num_headers)); |
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ MockRead reads[] = { |
+ CreateMockRead(*resp), |
+ MockRead(ASYNC, 0, 0) // EOF |
+ }; |
- EXPECT_EQ(0, d.received_redirect_count()); |
- std::string contents("hello!"); |
- EXPECT_EQ(contents, d.data_received()); |
+ DelayedSocketData data(1, reads, arraysize(reads), |
+ writes, arraysize(writes)); |
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
+ BoundNetLog(), GetParam(), NULL); |
+ helper.RunToCompletion(&data); |
+ TransactionHelperResult out = helper.output(); |
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); |
+ } |
+} |
- net::URLRequest r2( |
- GURL("http://www.google.com/foo.dat"), &d2, &spdy_url_request_context); |
- spdy_url_request_context.socket_factory(). |
- AddSocketDataProvider(&data2); |
+// Verify that we don't crash on some corrupt frames. |
+TEST_P(SpdyNetworkTransactionTest, CorruptFrameSessionError) { |
+ // This is the length field that's too short. |
+ scoped_ptr<SpdyFrame> syn_reply_wrong_length( |
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ BufferedSpdyFramer framer(spdy_util_.spdy_version(), false); |
+ size_t right_size = |
+ (spdy_util_.spdy_version() < SPDY4) ? |
+ syn_reply_wrong_length->size() - framer.GetControlFrameHeaderSize() : |
+ syn_reply_wrong_length->size(); |
+ size_t wrong_size = right_size - 4; |
+ test::SetFrameLength(syn_reply_wrong_length.get(), |
+ wrong_size, |
+ spdy_util_.spdy_version()); |
- d2.set_quit_on_redirect(true); |
- r2.Start(); |
- base::MessageLoop::current()->Run(); |
- EXPECT_EQ(1, d2.received_redirect_count()); |
+ struct SynReplyTests { |
+ const SpdyFrame* syn_reply; |
+ } test_cases[] = { |
+ { syn_reply_wrong_length.get(), }, |
+ }; |
- r2.FollowDeferredRedirect(); |
- base::MessageLoop::current()->Run(); |
- EXPECT_EQ(1, d2.response_started_count()); |
- EXPECT_FALSE(d2.received_data_before_response()); |
- EXPECT_EQ(net::URLRequestStatus::SUCCESS, r2.status().status()); |
- std::string contents2("hello!"); |
- EXPECT_EQ(contents2, d2.data_received()); |
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { |
+ scoped_ptr<SpdyFrame> req( |
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
+ MockWrite writes[] = { CreateMockWrite(*req), MockWrite(ASYNC, 0, 0) // EOF |
+ }; |
+ |
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ MockRead reads[] = { |
+ MockRead(ASYNC, test_cases[i].syn_reply->data(), wrong_size), |
+ CreateMockRead(*body), |
+ MockRead(ASYNC, 0, 0) // EOF |
+ }; |
+ |
+ DelayedSocketData data(1, reads, arraysize(reads), |
+ writes, arraysize(writes)); |
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
+ BoundNetLog(), GetParam(), NULL); |
+ helper.RunToCompletion(&data); |
+ TransactionHelperResult out = helper.output(); |
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); |
} |
- data.CompleteRead(); |
- data2.CompleteRead(); |
- EXPECT_TRUE(data.at_read_eof()); |
- EXPECT_TRUE(data.at_write_eof()); |
- EXPECT_TRUE(data2.at_read_eof()); |
- EXPECT_TRUE(data2.at_write_eof()); |
} |
-TEST_P(SpdyNetworkTransactionTest, ServerPushSingleDataFrame) { |
- scoped_ptr<SpdyFrame> stream1_syn( |
+// Test that we shutdown correctly on write errors. |
+TEST_P(SpdyNetworkTransactionTest, WriteError) { |
+ scoped_ptr<SpdyFrame> req( |
spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- scoped_ptr<SpdyFrame> stream1_body( |
- spdy_util_.ConstructSpdyBodyFrame(1, true)); |
MockWrite writes[] = { |
- CreateMockWrite(*stream1_syn, 1), |
- }; |
- |
- scoped_ptr<SpdyFrame> |
- stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- scoped_ptr<SpdyFrame> |
- stream2_syn(spdy_util_.ConstructSpdyPush(NULL, |
- 0, |
- 2, |
- 1, |
- "http://www.google.com/foo.dat")); |
- const char kPushedData[] = "pushed"; |
- scoped_ptr<SpdyFrame> stream2_body( |
- spdy_util_.ConstructSpdyBodyFrame( |
- 2, kPushedData, strlen(kPushedData), true)); |
- MockRead reads[] = { |
- CreateMockRead(*stream1_reply, 2), |
- CreateMockRead(*stream2_syn, 3), |
- CreateMockRead(*stream1_body, 4, SYNCHRONOUS), |
- CreateMockRead(*stream2_body, 5), |
- MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause |
+ // We'll write 10 bytes successfully |
+ MockWrite(ASYNC, req->data(), 10), |
+ // Followed by ERROR! |
+ MockWrite(ASYNC, ERR_FAILED), |
}; |
- HttpResponseInfo response; |
- HttpResponseInfo response2; |
- std::string expected_push_result("pushed"); |
- OrderedSocketData data(reads, arraysize(reads), |
+ DelayedSocketData data(2, NULL, 0, |
writes, arraysize(writes)); |
- RunServerPushTest(&data, |
- &response, |
- &response2, |
- expected_push_result); |
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
+ BoundNetLog(), GetParam(), NULL); |
+ helper.RunToCompletion(&data); |
+ TransactionHelperResult out = helper.output(); |
+ EXPECT_EQ(ERR_FAILED, out.rv); |
+ data.Reset(); |
+} |
- // Verify the SYN_REPLY. |
- EXPECT_TRUE(response.headers.get() != NULL); |
- EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
+// Test that partial writes work. |
+TEST_P(SpdyNetworkTransactionTest, PartialWrite) { |
+ // Chop the SYN_STREAM frame into 5 chunks. |
+ scoped_ptr<SpdyFrame> req( |
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
+ const int kChunks = 5; |
+ scoped_ptr<MockWrite[]> writes(ChopWriteFrame(*req.get(), kChunks)); |
- // Verify the pushed stream. |
- EXPECT_TRUE(response2.headers.get() != NULL); |
- EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); |
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ MockRead reads[] = { |
+ CreateMockRead(*resp), |
+ CreateMockRead(*body), |
+ MockRead(ASYNC, 0, 0) // EOF |
+ }; |
+ |
+ DelayedSocketData data(kChunks, reads, arraysize(reads), |
+ writes.get(), kChunks); |
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
+ BoundNetLog(), GetParam(), NULL); |
+ helper.RunToCompletion(&data); |
+ TransactionHelperResult out = helper.output(); |
+ EXPECT_EQ(OK, out.rv); |
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); |
+ EXPECT_EQ("hello!", out.response_data); |
} |
-TEST_P(SpdyNetworkTransactionTest, ServerPushBeforeSynReply) { |
- scoped_ptr<SpdyFrame> stream1_syn( |
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- scoped_ptr<SpdyFrame> stream1_body( |
- spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+// In this test, we enable compression, but get a uncompressed SynReply from |
+// the server. Verify that teardown is all clean. |
+TEST_P(SpdyNetworkTransactionTest, DecompressFailureOnSynReply) { |
+ scoped_ptr<SpdyFrame> compressed( |
+ spdy_util_.ConstructSpdyGet(NULL, 0, true, 1, LOWEST, true)); |
+ scoped_ptr<SpdyFrame> rst( |
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR)); |
MockWrite writes[] = { |
- CreateMockWrite(*stream1_syn, 1), |
+ CreateMockWrite(*compressed), |
}; |
- scoped_ptr<SpdyFrame> |
- stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- scoped_ptr<SpdyFrame> |
- stream2_syn(spdy_util_.ConstructSpdyPush(NULL, |
- 0, |
- 2, |
- 1, |
- "http://www.google.com/foo.dat")); |
- const char kPushedData[] = "pushed"; |
- scoped_ptr<SpdyFrame> stream2_body( |
- spdy_util_.ConstructSpdyBodyFrame( |
- 2, kPushedData, strlen(kPushedData), true)); |
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
MockRead reads[] = { |
- CreateMockRead(*stream2_syn, 2), |
- CreateMockRead(*stream1_reply, 3), |
- CreateMockRead(*stream1_body, 4, SYNCHRONOUS), |
- CreateMockRead(*stream2_body, 5), |
- MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause |
+ CreateMockRead(*resp), |
}; |
- HttpResponseInfo response; |
- HttpResponseInfo response2; |
- std::string expected_push_result("pushed"); |
- OrderedSocketData data(reads, arraysize(reads), |
+ DelayedSocketData data(1, reads, arraysize(reads), |
writes, arraysize(writes)); |
- RunServerPushTest(&data, |
- &response, |
- &response2, |
- expected_push_result); |
- |
- // Verify the SYN_REPLY. |
- EXPECT_TRUE(response.headers.get() != NULL); |
- EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
- |
- // Verify the pushed stream. |
- EXPECT_TRUE(response2.headers.get() != NULL); |
- EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); |
+ SpdySessionDependencies* session_deps = |
+ CreateSpdySessionDependencies(GetParam()); |
+ session_deps->enable_compression = true; |
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
+ BoundNetLog(), GetParam(), session_deps); |
+ helper.RunToCompletion(&data); |
+ TransactionHelperResult out = helper.output(); |
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); |
+ data.Reset(); |
} |
-TEST_P(SpdyNetworkTransactionTest, ServerPushSingleDataFrame2) { |
- scoped_ptr<SpdyFrame> stream1_syn( |
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- MockWrite writes[] = { CreateMockWrite(*stream1_syn, 1), }; |
+// Test that the NetLog contains good data for a simple GET request. |
+TEST_P(SpdyNetworkTransactionTest, NetLog) { |
+ static const char* const kExtraHeaders[] = { |
+ "user-agent", "Chrome", |
+ }; |
+ scoped_ptr<SpdyFrame> req( |
+ spdy_util_.ConstructSpdyGet(kExtraHeaders, 1, false, 1, LOWEST, true)); |
+ MockWrite writes[] = { CreateMockWrite(*req) }; |
- scoped_ptr<SpdyFrame> |
- stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- scoped_ptr<SpdyFrame> |
- stream2_syn(spdy_util_.ConstructSpdyPush(NULL, |
- 0, |
- 2, |
- 1, |
- "http://www.google.com/foo.dat")); |
- const char kPushedData[] = "pushed"; |
- scoped_ptr<SpdyFrame> stream2_body( |
- spdy_util_.ConstructSpdyBodyFrame( |
- 2, kPushedData, strlen(kPushedData), true)); |
- scoped_ptr<SpdyFrame> |
- stream1_body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
MockRead reads[] = { |
- CreateMockRead(*stream1_reply, 2), |
- CreateMockRead(*stream2_syn, 3), |
- CreateMockRead(*stream2_body, 4), |
- CreateMockRead(*stream1_body, 5, SYNCHRONOUS), |
- MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause |
+ CreateMockRead(*resp), |
+ CreateMockRead(*body), |
+ MockRead(ASYNC, 0, 0) // EOF |
}; |
- HttpResponseInfo response; |
- HttpResponseInfo response2; |
- std::string expected_push_result("pushed"); |
- OrderedSocketData data(reads, arraysize(reads), |
+ CapturingBoundNetLog log; |
+ |
+ DelayedSocketData data(1, reads, arraysize(reads), |
writes, arraysize(writes)); |
- RunServerPushTest(&data, |
- &response, |
- &response2, |
- expected_push_result); |
+ NormalSpdyTransactionHelper helper(CreateGetRequestWithUserAgent(), |
+ DEFAULT_PRIORITY, |
+ log.bound(), GetParam(), NULL); |
+ helper.RunToCompletion(&data); |
+ TransactionHelperResult out = helper.output(); |
+ EXPECT_EQ(OK, out.rv); |
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); |
+ EXPECT_EQ("hello!", out.response_data); |
- // Verify the SYN_REPLY. |
- EXPECT_TRUE(response.headers.get() != NULL); |
- EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
+ // Check that the NetLog was filled reasonably. |
+ // This test is intentionally non-specific about the exact ordering of the |
+ // log; instead we just check to make sure that certain events exist, and that |
+ // they are in the right order. |
+ net::CapturingNetLog::CapturedEntryList entries; |
+ log.GetEntries(&entries); |
- // Verify the pushed stream. |
- EXPECT_TRUE(response2.headers.get() != NULL); |
- EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); |
+ EXPECT_LT(0u, entries.size()); |
+ int pos = 0; |
+ pos = net::ExpectLogContainsSomewhere(entries, 0, |
+ net::NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST, |
+ net::NetLog::PHASE_BEGIN); |
+ pos = net::ExpectLogContainsSomewhere(entries, pos + 1, |
+ net::NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST, |
+ net::NetLog::PHASE_END); |
+ pos = net::ExpectLogContainsSomewhere(entries, pos + 1, |
+ net::NetLog::TYPE_HTTP_TRANSACTION_READ_HEADERS, |
+ net::NetLog::PHASE_BEGIN); |
+ pos = net::ExpectLogContainsSomewhere(entries, pos + 1, |
+ net::NetLog::TYPE_HTTP_TRANSACTION_READ_HEADERS, |
+ net::NetLog::PHASE_END); |
+ pos = net::ExpectLogContainsSomewhere(entries, pos + 1, |
+ net::NetLog::TYPE_HTTP_TRANSACTION_READ_BODY, |
+ net::NetLog::PHASE_BEGIN); |
+ pos = net::ExpectLogContainsSomewhere(entries, pos + 1, |
+ net::NetLog::TYPE_HTTP_TRANSACTION_READ_BODY, |
+ net::NetLog::PHASE_END); |
+ |
+ // Check that we logged all the headers correctly |
+ pos = net::ExpectLogContainsSomewhere( |
+ entries, 0, |
+ net::NetLog::TYPE_SPDY_SESSION_SYN_STREAM, |
+ net::NetLog::PHASE_NONE); |
+ |
+ base::ListValue* header_list; |
+ ASSERT_TRUE(entries[pos].params.get()); |
+ ASSERT_TRUE(entries[pos].params->GetList("headers", &header_list)); |
+ |
+ std::vector<std::string> expected; |
+ expected.push_back(std::string(spdy_util_.GetHostKey()) + ": www.google.com"); |
+ expected.push_back(std::string(spdy_util_.GetPathKey()) + ": /"); |
+ expected.push_back(std::string(spdy_util_.GetSchemeKey()) + ": http"); |
+ expected.push_back(std::string(spdy_util_.GetVersionKey()) + ": HTTP/1.1"); |
+ expected.push_back(std::string(spdy_util_.GetMethodKey()) + ": GET"); |
+ expected.push_back("user-agent: Chrome"); |
+ EXPECT_EQ(expected.size(), header_list->GetSize()); |
+ for (std::vector<std::string>::const_iterator it = expected.begin(); |
+ it != expected.end(); |
+ ++it) { |
+ base::StringValue header(*it); |
+ EXPECT_NE(header_list->end(), header_list->Find(header)) << |
+ "Header not found: " << *it; |
+ } |
} |
-TEST_P(SpdyNetworkTransactionTest, ServerPushServerAborted) { |
- scoped_ptr<SpdyFrame> stream1_syn( |
+// Since we buffer the IO from the stream to the renderer, this test verifies |
+// that when we read out the maximum amount of data (e.g. we received 50 bytes |
+// on the network, but issued a Read for only 5 of those bytes) that the data |
+// flow still works correctly. |
+TEST_P(SpdyNetworkTransactionTest, BufferFull) { |
+ BufferedSpdyFramer framer(spdy_util_.spdy_version(), false); |
+ |
+ scoped_ptr<SpdyFrame> req( |
spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- scoped_ptr<SpdyFrame> stream1_body( |
- spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- MockWrite writes[] = { |
- CreateMockWrite(*stream1_syn, 1), |
+ MockWrite writes[] = { CreateMockWrite(*req) }; |
+ |
+ // 2 data frames in a single read. |
+ scoped_ptr<SpdyFrame> data_frame_1( |
+ framer.CreateDataFrame(1, "goodby", 6, DATA_FLAG_NONE)); |
+ scoped_ptr<SpdyFrame> data_frame_2( |
+ framer.CreateDataFrame(1, "e worl", 6, DATA_FLAG_NONE)); |
+ const SpdyFrame* data_frames[2] = { |
+ data_frame_1.get(), |
+ data_frame_2.get(), |
}; |
+ char combined_data_frames[100]; |
+ int combined_data_frames_len = |
+ CombineFrames(data_frames, arraysize(data_frames), |
+ combined_data_frames, arraysize(combined_data_frames)); |
+ scoped_ptr<SpdyFrame> last_frame( |
+ framer.CreateDataFrame(1, "d", 1, DATA_FLAG_FIN)); |
- scoped_ptr<SpdyFrame> |
- stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- scoped_ptr<SpdyFrame> |
- stream2_syn(spdy_util_.ConstructSpdyPush(NULL, |
- 0, |
- 2, |
- 1, |
- "http://www.google.com/foo.dat")); |
- scoped_ptr<SpdyFrame> stream2_rst( |
- spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_PROTOCOL_ERROR)); |
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
MockRead reads[] = { |
- CreateMockRead(*stream1_reply, 2), |
- CreateMockRead(*stream2_syn, 3), |
- CreateMockRead(*stream2_rst, 4), |
- CreateMockRead(*stream1_body, 5, SYNCHRONOUS), |
- MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause |
+ CreateMockRead(*resp), |
+ MockRead(ASYNC, ERR_IO_PENDING), // Force a pause |
+ MockRead(ASYNC, combined_data_frames, combined_data_frames_len), |
+ MockRead(ASYNC, ERR_IO_PENDING), // Force a pause |
+ CreateMockRead(*last_frame), |
+ MockRead(ASYNC, 0, 0) // EOF |
}; |
- OrderedSocketData data(reads, arraysize(reads), |
+ DelayedSocketData data(1, reads, arraysize(reads), |
writes, arraysize(writes)); |
+ |
+ TestCompletionCallback callback; |
+ |
NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
BoundNetLog(), GetParam(), NULL); |
- |
helper.RunPreTestSetup(); |
helper.AddData(&data); |
- |
HttpNetworkTransaction* trans = helper.trans(); |
- |
- // Start the transaction with basic parameters. |
- TestCompletionCallback callback; |
int rv = trans->Start( |
&CreateGetRequest(), callback.callback(), BoundNetLog()); |
EXPECT_EQ(ERR_IO_PENDING, rv); |
- rv = callback.WaitForResult(); |
- EXPECT_EQ(OK, rv); |
+ |
+ TransactionHelperResult out = helper.output(); |
+ out.rv = callback.WaitForResult(); |
+ EXPECT_EQ(out.rv, OK); |
+ |
+ const HttpResponseInfo* response = trans->GetResponseInfo(); |
+ EXPECT_TRUE(response->headers.get() != NULL); |
+ EXPECT_TRUE(response->was_fetched_via_spdy); |
+ out.status_line = response->headers->GetStatusLine(); |
+ out.response_info = *response; // Make a copy so we can verify. |
+ |
+ // Read Data |
+ TestCompletionCallback read_callback; |
+ |
+ std::string content; |
+ do { |
+ // Read small chunks at a time. |
+ const int kSmallReadSize = 3; |
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize)); |
+ rv = trans->Read(buf.get(), kSmallReadSize, read_callback.callback()); |
+ if (rv == net::ERR_IO_PENDING) { |
+ data.CompleteRead(); |
+ rv = read_callback.WaitForResult(); |
+ } |
+ if (rv > 0) { |
+ content.append(buf->data(), rv); |
+ } else if (rv < 0) { |
+ NOTREACHED(); |
+ } |
+ } while (rv > 0); |
+ |
+ out.response_data.swap(content); |
+ |
+ // Flush the MessageLoop while the SpdySessionDependencies (in particular, the |
+ // MockClientSocketFactory) are still alive. |
+ base::MessageLoop::current()->RunUntilIdle(); |
// Verify that we consumed all test data. |
- EXPECT_TRUE(data.at_read_eof()) << "Read count: " |
- << data.read_count() |
- << " Read index: " |
- << data.read_index(); |
- EXPECT_TRUE(data.at_write_eof()) << "Write count: " |
- << data.write_count() |
- << " Write index: " |
- << data.write_index(); |
+ helper.VerifyDataConsumed(); |
- // Verify the SYN_REPLY. |
- HttpResponseInfo response = *trans->GetResponseInfo(); |
- EXPECT_TRUE(response.headers.get() != NULL); |
- EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
+ EXPECT_EQ(OK, out.rv); |
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); |
+ EXPECT_EQ("goodbye world", out.response_data); |
} |
-// Verify that we don't leak streams and that we properly send a reset |
-// if the server pushes the same stream twice. |
-TEST_P(SpdyNetworkTransactionTest, ServerPushDuplicate) { |
- scoped_ptr<SpdyFrame> stream1_syn( |
+// Verify that basic buffering works; when multiple data frames arrive |
+// at the same time, ensure that we don't notify a read completion for |
+// each data frame individually. |
+TEST_P(SpdyNetworkTransactionTest, Buffering) { |
+ BufferedSpdyFramer framer(spdy_util_.spdy_version(), false); |
+ |
+ scoped_ptr<SpdyFrame> req( |
spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- scoped_ptr<SpdyFrame> stream1_body( |
- spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- scoped_ptr<SpdyFrame> stream3_rst( |
- spdy_util_.ConstructSpdyRstStream(4, RST_STREAM_PROTOCOL_ERROR)); |
- MockWrite writes[] = { |
- CreateMockWrite(*stream1_syn, 1), |
- CreateMockWrite(*stream3_rst, 5), |
+ MockWrite writes[] = { CreateMockWrite(*req) }; |
+ |
+ // 4 data frames in a single read. |
+ scoped_ptr<SpdyFrame> data_frame( |
+ framer.CreateDataFrame(1, "message", 7, DATA_FLAG_NONE)); |
+ scoped_ptr<SpdyFrame> data_frame_fin( |
+ framer.CreateDataFrame(1, "message", 7, DATA_FLAG_FIN)); |
+ const SpdyFrame* data_frames[4] = { |
+ data_frame.get(), |
+ data_frame.get(), |
+ data_frame.get(), |
+ data_frame_fin.get() |
}; |
+ char combined_data_frames[100]; |
+ int combined_data_frames_len = |
+ CombineFrames(data_frames, arraysize(data_frames), |
+ combined_data_frames, arraysize(combined_data_frames)); |
- scoped_ptr<SpdyFrame> |
- stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- scoped_ptr<SpdyFrame> |
- stream2_syn(spdy_util_.ConstructSpdyPush(NULL, |
- 0, |
- 2, |
- 1, |
- "http://www.google.com/foo.dat")); |
- const char kPushedData[] = "pushed"; |
- scoped_ptr<SpdyFrame> stream2_body( |
- spdy_util_.ConstructSpdyBodyFrame( |
- 2, kPushedData, strlen(kPushedData), true)); |
- scoped_ptr<SpdyFrame> |
- stream3_syn(spdy_util_.ConstructSpdyPush(NULL, |
- 0, |
- 4, |
- 1, |
- "http://www.google.com/foo.dat")); |
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
MockRead reads[] = { |
- CreateMockRead(*stream1_reply, 2), |
- CreateMockRead(*stream2_syn, 3), |
- CreateMockRead(*stream3_syn, 4), |
- CreateMockRead(*stream1_body, 6, SYNCHRONOUS), |
- CreateMockRead(*stream2_body, 7), |
- MockRead(ASYNC, ERR_IO_PENDING, 8), // Force a pause |
+ CreateMockRead(*resp), |
+ MockRead(ASYNC, ERR_IO_PENDING), // Force a pause |
+ MockRead(ASYNC, combined_data_frames, combined_data_frames_len), |
+ MockRead(ASYNC, 0, 0) // EOF |
}; |
- HttpResponseInfo response; |
- HttpResponseInfo response2; |
- std::string expected_push_result("pushed"); |
- OrderedSocketData data(reads, arraysize(reads), |
+ DelayedSocketData data(1, reads, arraysize(reads), |
writes, arraysize(writes)); |
- RunServerPushTest(&data, |
- &response, |
- &response2, |
- expected_push_result); |
- |
- // Verify the SYN_REPLY. |
- EXPECT_TRUE(response.headers.get() != NULL); |
- EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
- // Verify the pushed stream. |
- EXPECT_TRUE(response2.headers.get() != NULL); |
- EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); |
-} |
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
+ BoundNetLog(), GetParam(), NULL); |
+ helper.RunPreTestSetup(); |
+ helper.AddData(&data); |
+ HttpNetworkTransaction* trans = helper.trans(); |
-TEST_P(SpdyNetworkTransactionTest, ServerPushMultipleDataFrame) { |
- scoped_ptr<SpdyFrame> stream1_syn( |
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- scoped_ptr<SpdyFrame> stream1_body( |
- spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- MockWrite writes[] = { |
- CreateMockWrite(*stream1_syn, 1), |
- }; |
+ TestCompletionCallback callback; |
+ int rv = trans->Start( |
+ &CreateGetRequest(), callback.callback(), BoundNetLog()); |
+ EXPECT_EQ(ERR_IO_PENDING, rv); |
- scoped_ptr<SpdyFrame> |
- stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- scoped_ptr<SpdyFrame> |
- stream2_syn(spdy_util_.ConstructSpdyPush(NULL, |
- 0, |
- 2, |
- 1, |
- "http://www.google.com/foo.dat")); |
- static const char kPushedData[] = "pushed my darling hello my baby"; |
- scoped_ptr<SpdyFrame> stream2_body_base( |
- spdy_util_.ConstructSpdyBodyFrame( |
- 2, kPushedData, strlen(kPushedData), true)); |
- const size_t kChunkSize = strlen(kPushedData) / 4; |
- scoped_ptr<SpdyFrame> stream2_body1( |
- new SpdyFrame(stream2_body_base->data(), kChunkSize, false)); |
- scoped_ptr<SpdyFrame> stream2_body2( |
- new SpdyFrame(stream2_body_base->data() + kChunkSize, kChunkSize, false)); |
- scoped_ptr<SpdyFrame> stream2_body3( |
- new SpdyFrame(stream2_body_base->data() + 2 * kChunkSize, |
- kChunkSize, false)); |
- scoped_ptr<SpdyFrame> stream2_body4( |
- new SpdyFrame(stream2_body_base->data() + 3 * kChunkSize, |
- stream2_body_base->size() - 3 * kChunkSize, false)); |
- MockRead reads[] = { |
- CreateMockRead(*stream1_reply, 2), |
- CreateMockRead(*stream2_syn, 3), |
- CreateMockRead(*stream2_body1, 4), |
- CreateMockRead(*stream2_body2, 5), |
- CreateMockRead(*stream2_body3, 6), |
- CreateMockRead(*stream2_body4, 7), |
- CreateMockRead(*stream1_body, 8, SYNCHRONOUS), |
- MockRead(ASYNC, ERR_IO_PENDING, 9), // Force a pause |
- }; |
+ TransactionHelperResult out = helper.output(); |
+ out.rv = callback.WaitForResult(); |
+ EXPECT_EQ(out.rv, OK); |
- HttpResponseInfo response; |
- HttpResponseInfo response2; |
- std::string expected_push_result("pushed my darling hello my baby"); |
- OrderedSocketData data(reads, arraysize(reads), |
- writes, arraysize(writes)); |
- RunServerPushTest(&data, &response, &response2, kPushedData); |
+ const HttpResponseInfo* response = trans->GetResponseInfo(); |
+ EXPECT_TRUE(response->headers.get() != NULL); |
+ EXPECT_TRUE(response->was_fetched_via_spdy); |
+ out.status_line = response->headers->GetStatusLine(); |
+ out.response_info = *response; // Make a copy so we can verify. |
- // Verify the SYN_REPLY. |
- EXPECT_TRUE(response.headers.get() != NULL); |
- EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
+ // Read Data |
+ TestCompletionCallback read_callback; |
- // Verify the pushed stream. |
- EXPECT_TRUE(response2.headers.get() != NULL); |
- EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); |
-} |
+ std::string content; |
+ int reads_completed = 0; |
+ do { |
+ // Read small chunks at a time. |
+ const int kSmallReadSize = 14; |
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize)); |
+ rv = trans->Read(buf.get(), kSmallReadSize, read_callback.callback()); |
+ if (rv == net::ERR_IO_PENDING) { |
+ data.CompleteRead(); |
+ rv = read_callback.WaitForResult(); |
+ } |
+ if (rv > 0) { |
+ EXPECT_EQ(kSmallReadSize, rv); |
+ content.append(buf->data(), rv); |
+ } else if (rv < 0) { |
+ FAIL() << "Unexpected read error: " << rv; |
+ } |
+ reads_completed++; |
+ } while (rv > 0); |
-TEST_P(SpdyNetworkTransactionTest, ServerPushMultipleDataFrameInterrupted) { |
- scoped_ptr<SpdyFrame> stream1_syn( |
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- scoped_ptr<SpdyFrame> stream1_body( |
- spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- MockWrite writes[] = { |
- CreateMockWrite(*stream1_syn, 1), |
- }; |
+ EXPECT_EQ(3, reads_completed); // Reads are: 14 bytes, 14 bytes, 0 bytes. |
- scoped_ptr<SpdyFrame> |
- stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- scoped_ptr<SpdyFrame> |
- stream2_syn(spdy_util_.ConstructSpdyPush(NULL, |
- 0, |
- 2, |
- 1, |
- "http://www.google.com/foo.dat")); |
- static const char kPushedData[] = "pushed my darling hello my baby"; |
- scoped_ptr<SpdyFrame> stream2_body_base( |
- spdy_util_.ConstructSpdyBodyFrame( |
- 2, kPushedData, strlen(kPushedData), true)); |
- const size_t kChunkSize = strlen(kPushedData) / 4; |
- scoped_ptr<SpdyFrame> stream2_body1( |
- new SpdyFrame(stream2_body_base->data(), kChunkSize, false)); |
- scoped_ptr<SpdyFrame> stream2_body2( |
- new SpdyFrame(stream2_body_base->data() + kChunkSize, kChunkSize, false)); |
- scoped_ptr<SpdyFrame> stream2_body3( |
- new SpdyFrame(stream2_body_base->data() + 2 * kChunkSize, |
- kChunkSize, false)); |
- scoped_ptr<SpdyFrame> stream2_body4( |
- new SpdyFrame(stream2_body_base->data() + 3 * kChunkSize, |
- stream2_body_base->size() - 3 * kChunkSize, false)); |
- MockRead reads[] = { |
- CreateMockRead(*stream1_reply, 2), |
- CreateMockRead(*stream2_syn, 3), |
- CreateMockRead(*stream2_body1, 4), |
- CreateMockRead(*stream2_body2, 5), |
- MockRead(ASYNC, ERR_IO_PENDING, 6), // Force a pause |
- CreateMockRead(*stream2_body3, 7), |
- CreateMockRead(*stream2_body4, 8), |
- CreateMockRead(*stream1_body.get(), 9, SYNCHRONOUS), |
- MockRead(ASYNC, ERR_IO_PENDING, 10) // Force a pause. |
- }; |
+ out.response_data.swap(content); |
- HttpResponseInfo response; |
- HttpResponseInfo response2; |
- OrderedSocketData data(reads, arraysize(reads), |
- writes, arraysize(writes)); |
- RunServerPushTest(&data, &response, &response2, kPushedData); |
+ // Flush the MessageLoop while the SpdySessionDependencies (in particular, the |
+ // MockClientSocketFactory) are still alive. |
+ base::MessageLoop::current()->RunUntilIdle(); |
- // Verify the SYN_REPLY. |
- EXPECT_TRUE(response.headers.get() != NULL); |
- EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
+ // Verify that we consumed all test data. |
+ helper.VerifyDataConsumed(); |
- // Verify the pushed stream. |
- EXPECT_TRUE(response2.headers.get() != NULL); |
- EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); |
+ EXPECT_EQ(OK, out.rv); |
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); |
+ EXPECT_EQ("messagemessagemessagemessage", out.response_data); |
} |
-TEST_P(SpdyNetworkTransactionTest, ServerPushInvalidAssociatedStreamID0) { |
- scoped_ptr<SpdyFrame> stream1_syn( |
+// Verify the case where we buffer data but read it after it has been buffered. |
+TEST_P(SpdyNetworkTransactionTest, BufferedAll) { |
+ BufferedSpdyFramer framer(spdy_util_.spdy_version(), false); |
+ |
+ scoped_ptr<SpdyFrame> req( |
spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- scoped_ptr<SpdyFrame> stream1_body( |
- spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- scoped_ptr<SpdyFrame> stream2_rst( |
- spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_REFUSED_STREAM)); |
- MockWrite writes[] = { |
- CreateMockWrite(*stream1_syn, 1), |
- CreateMockWrite(*stream2_rst, 4), |
+ MockWrite writes[] = { CreateMockWrite(*req) }; |
+ |
+ // 5 data frames in a single read. |
+ scoped_ptr<SpdyFrame> syn_reply( |
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ // turn off FIN bit |
+ test::SetFrameFlags( |
+ syn_reply.get(), CONTROL_FLAG_NONE, spdy_util_.spdy_version()); |
+ scoped_ptr<SpdyFrame> data_frame( |
+ framer.CreateDataFrame(1, "message", 7, DATA_FLAG_NONE)); |
+ scoped_ptr<SpdyFrame> data_frame_fin( |
+ framer.CreateDataFrame(1, "message", 7, DATA_FLAG_FIN)); |
+ const SpdyFrame* frames[5] = { |
+ syn_reply.get(), |
+ data_frame.get(), |
+ data_frame.get(), |
+ data_frame.get(), |
+ data_frame_fin.get() |
}; |
+ char combined_frames[200]; |
+ int combined_frames_len = |
+ CombineFrames(frames, arraysize(frames), |
+ combined_frames, arraysize(combined_frames)); |
- scoped_ptr<SpdyFrame> |
- stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- scoped_ptr<SpdyFrame> |
- stream2_syn(spdy_util_.ConstructSpdyPush(NULL, |
- 0, |
- 2, |
- 0, |
- "http://www.google.com/foo.dat")); |
MockRead reads[] = { |
- CreateMockRead(*stream1_reply, 2), |
- CreateMockRead(*stream2_syn, 3), |
- CreateMockRead(*stream1_body, 4), |
- MockRead(ASYNC, ERR_IO_PENDING, 5) // Force a pause |
+ MockRead(ASYNC, combined_frames, combined_frames_len), |
+ MockRead(ASYNC, 0, 0) // EOF |
}; |
- OrderedSocketData data(reads, arraysize(reads), |
+ DelayedSocketData data(1, reads, arraysize(reads), |
writes, arraysize(writes)); |
+ |
NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
BoundNetLog(), GetParam(), NULL); |
- |
helper.RunPreTestSetup(); |
helper.AddData(&data); |
- |
HttpNetworkTransaction* trans = helper.trans(); |
- // Start the transaction with basic parameters. |
TestCompletionCallback callback; |
int rv = trans->Start( |
&CreateGetRequest(), callback.callback(), BoundNetLog()); |
EXPECT_EQ(ERR_IO_PENDING, rv); |
- rv = callback.WaitForResult(); |
- EXPECT_EQ(OK, rv); |
+ |
+ TransactionHelperResult out = helper.output(); |
+ out.rv = callback.WaitForResult(); |
+ EXPECT_EQ(out.rv, OK); |
+ |
+ const HttpResponseInfo* response = trans->GetResponseInfo(); |
+ EXPECT_TRUE(response->headers.get() != NULL); |
+ EXPECT_TRUE(response->was_fetched_via_spdy); |
+ out.status_line = response->headers->GetStatusLine(); |
+ out.response_info = *response; // Make a copy so we can verify. |
+ |
+ // Read Data |
+ TestCompletionCallback read_callback; |
+ |
+ std::string content; |
+ int reads_completed = 0; |
+ do { |
+ // Read small chunks at a time. |
+ const int kSmallReadSize = 14; |
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize)); |
+ rv = trans->Read(buf.get(), kSmallReadSize, read_callback.callback()); |
+ if (rv > 0) { |
+ EXPECT_EQ(kSmallReadSize, rv); |
+ content.append(buf->data(), rv); |
+ } else if (rv < 0) { |
+ FAIL() << "Unexpected read error: " << rv; |
+ } |
+ reads_completed++; |
+ } while (rv > 0); |
+ |
+ EXPECT_EQ(3, reads_completed); |
+ |
+ out.response_data.swap(content); |
+ |
+ // Flush the MessageLoop while the SpdySessionDependencies (in particular, the |
+ // MockClientSocketFactory) are still alive. |
+ base::MessageLoop::current()->RunUntilIdle(); |
// Verify that we consumed all test data. |
- EXPECT_TRUE(data.at_read_eof()) << "Read count: " |
- << data.read_count() |
- << " Read index: " |
- << data.read_index(); |
- EXPECT_TRUE(data.at_write_eof()) << "Write count: " |
- << data.write_count() |
- << " Write index: " |
- << data.write_index(); |
+ helper.VerifyDataConsumed(); |
- // Verify the SYN_REPLY. |
- HttpResponseInfo response = *trans->GetResponseInfo(); |
- EXPECT_TRUE(response.headers.get() != NULL); |
- EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
+ EXPECT_EQ(OK, out.rv); |
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); |
+ EXPECT_EQ("messagemessagemessagemessage", out.response_data); |
} |
-TEST_P(SpdyNetworkTransactionTest, ServerPushInvalidAssociatedStreamID9) { |
- scoped_ptr<SpdyFrame> stream1_syn( |
+// Verify the case where we buffer data and close the connection. |
+TEST_P(SpdyNetworkTransactionTest, BufferedClosed) { |
+ BufferedSpdyFramer framer(spdy_util_.spdy_version(), false); |
+ |
+ scoped_ptr<SpdyFrame> req( |
spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- scoped_ptr<SpdyFrame> stream1_body( |
- spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- scoped_ptr<SpdyFrame> stream2_rst( |
- spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_INVALID_STREAM)); |
- MockWrite writes[] = { |
- CreateMockWrite(*stream1_syn, 1), |
- CreateMockWrite(*stream2_rst, 4), |
- }; |
+ MockWrite writes[] = { CreateMockWrite(*req) }; |
- scoped_ptr<SpdyFrame> |
- stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- scoped_ptr<SpdyFrame> |
- stream2_syn(spdy_util_.ConstructSpdyPush(NULL, |
- 0, |
- 2, |
- 9, |
- "http://www.google.com/foo.dat")); |
- MockRead reads[] = { |
- CreateMockRead(*stream1_reply, 2), |
- CreateMockRead(*stream2_syn, 3), |
- CreateMockRead(*stream1_body, 4), |
- MockRead(ASYNC, ERR_IO_PENDING, 5), // Force a pause |
+ // All data frames in a single read. |
+ // NOTE: We don't FIN the stream. |
+ scoped_ptr<SpdyFrame> data_frame( |
+ framer.CreateDataFrame(1, "message", 7, DATA_FLAG_NONE)); |
+ const SpdyFrame* data_frames[4] = { |
+ data_frame.get(), |
+ data_frame.get(), |
+ data_frame.get(), |
+ data_frame.get() |
+ }; |
+ char combined_data_frames[100]; |
+ int combined_data_frames_len = |
+ CombineFrames(data_frames, arraysize(data_frames), |
+ combined_data_frames, arraysize(combined_data_frames)); |
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ MockRead reads[] = { |
+ CreateMockRead(*resp), |
+ MockRead(ASYNC, ERR_IO_PENDING), // Force a wait |
+ MockRead(ASYNC, combined_data_frames, combined_data_frames_len), |
+ MockRead(ASYNC, 0, 0) // EOF |
}; |
- OrderedSocketData data(reads, arraysize(reads), |
+ DelayedSocketData data(1, reads, arraysize(reads), |
writes, arraysize(writes)); |
+ |
NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
BoundNetLog(), GetParam(), NULL); |
- |
helper.RunPreTestSetup(); |
helper.AddData(&data); |
- |
HttpNetworkTransaction* trans = helper.trans(); |
- // Start the transaction with basic parameters. |
TestCompletionCallback callback; |
+ |
int rv = trans->Start( |
&CreateGetRequest(), callback.callback(), BoundNetLog()); |
EXPECT_EQ(ERR_IO_PENDING, rv); |
- rv = callback.WaitForResult(); |
- EXPECT_EQ(OK, rv); |
- // Verify that we consumed all test data. |
- EXPECT_TRUE(data.at_read_eof()) << "Read count: " |
- << data.read_count() |
- << " Read index: " |
- << data.read_index(); |
- EXPECT_TRUE(data.at_write_eof()) << "Write count: " |
- << data.write_count() |
- << " Write index: " |
- << data.write_index(); |
+ TransactionHelperResult out = helper.output(); |
+ out.rv = callback.WaitForResult(); |
+ EXPECT_EQ(out.rv, OK); |
- // Verify the SYN_REPLY. |
- HttpResponseInfo response = *trans->GetResponseInfo(); |
- EXPECT_TRUE(response.headers.get() != NULL); |
- EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
+ const HttpResponseInfo* response = trans->GetResponseInfo(); |
+ EXPECT_TRUE(response->headers.get() != NULL); |
+ EXPECT_TRUE(response->was_fetched_via_spdy); |
+ out.status_line = response->headers->GetStatusLine(); |
+ out.response_info = *response; // Make a copy so we can verify. |
+ |
+ // Read Data |
+ TestCompletionCallback read_callback; |
+ |
+ std::string content; |
+ int reads_completed = 0; |
+ do { |
+ // Read small chunks at a time. |
+ const int kSmallReadSize = 14; |
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize)); |
+ rv = trans->Read(buf.get(), kSmallReadSize, read_callback.callback()); |
+ if (rv == net::ERR_IO_PENDING) { |
+ data.CompleteRead(); |
+ rv = read_callback.WaitForResult(); |
+ } |
+ if (rv > 0) { |
+ content.append(buf->data(), rv); |
+ } else if (rv < 0) { |
+ // This test intentionally closes the connection, and will get an error. |
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, rv); |
+ break; |
+ } |
+ reads_completed++; |
+ } while (rv > 0); |
+ |
+ EXPECT_EQ(0, reads_completed); |
+ |
+ out.response_data.swap(content); |
+ |
+ // Flush the MessageLoop while the SpdySessionDependencies (in particular, the |
+ // MockClientSocketFactory) are still alive. |
+ base::MessageLoop::current()->RunUntilIdle(); |
+ |
+ // Verify that we consumed all test data. |
+ helper.VerifyDataConsumed(); |
} |
-TEST_P(SpdyNetworkTransactionTest, ServerPushNoURL) { |
- scoped_ptr<SpdyFrame> stream1_syn( |
+// Verify the case where we buffer data and cancel the transaction. |
+TEST_P(SpdyNetworkTransactionTest, BufferedCancelled) { |
+ BufferedSpdyFramer framer(spdy_util_.spdy_version(), false); |
+ |
+ scoped_ptr<SpdyFrame> req( |
spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- scoped_ptr<SpdyFrame> stream1_body( |
- spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- scoped_ptr<SpdyFrame> stream2_rst( |
- spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_PROTOCOL_ERROR)); |
- MockWrite writes[] = { |
- CreateMockWrite(*stream1_syn, 1), |
- CreateMockWrite(*stream2_rst, 4), |
- }; |
+ MockWrite writes[] = { CreateMockWrite(*req) }; |
- scoped_ptr<SpdyFrame> |
- stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- scoped_ptr<SpdyHeaderBlock> incomplete_headers(new SpdyHeaderBlock()); |
- (*incomplete_headers)["hello"] = "bye"; |
- (*incomplete_headers)[spdy_util_.GetStatusKey()] = "200 OK"; |
- (*incomplete_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1"; |
- scoped_ptr<SpdyFrame> stream2_syn( |
- spdy_util_.ConstructSpdyControlFrame(incomplete_headers.Pass(), |
- false, |
- 2, // Stream ID |
- LOWEST, |
- SYN_STREAM, |
- CONTROL_FLAG_NONE, |
- // Associated stream ID |
- 1)); |
+ // NOTE: We don't FIN the stream. |
+ scoped_ptr<SpdyFrame> data_frame( |
+ framer.CreateDataFrame(1, "message", 7, DATA_FLAG_NONE)); |
+ |
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
MockRead reads[] = { |
- CreateMockRead(*stream1_reply, 2), |
- CreateMockRead(*stream2_syn, 3), |
- CreateMockRead(*stream1_body, 4), |
- MockRead(ASYNC, ERR_IO_PENDING, 5) // Force a pause |
+ CreateMockRead(*resp), |
+ MockRead(ASYNC, ERR_IO_PENDING), // Force a wait |
+ CreateMockRead(*data_frame), |
+ MockRead(ASYNC, 0, 0) // EOF |
}; |
- OrderedSocketData data(reads, arraysize(reads), |
+ DelayedSocketData data(1, reads, arraysize(reads), |
writes, arraysize(writes)); |
+ |
NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
BoundNetLog(), GetParam(), NULL); |
- |
helper.RunPreTestSetup(); |
helper.AddData(&data); |
- |
HttpNetworkTransaction* trans = helper.trans(); |
- |
- // Start the transaction with basic parameters. |
TestCompletionCallback callback; |
+ |
int rv = trans->Start( |
&CreateGetRequest(), callback.callback(), BoundNetLog()); |
EXPECT_EQ(ERR_IO_PENDING, rv); |
- rv = callback.WaitForResult(); |
- EXPECT_EQ(OK, rv); |
- // Verify that we consumed all test data. |
- EXPECT_TRUE(data.at_read_eof()) << "Read count: " |
- << data.read_count() |
- << " Read index: " |
- << data.read_index(); |
- EXPECT_TRUE(data.at_write_eof()) << "Write count: " |
- << data.write_count() |
- << " Write index: " |
- << data.write_index(); |
- |
- // Verify the SYN_REPLY. |
- HttpResponseInfo response = *trans->GetResponseInfo(); |
- EXPECT_TRUE(response.headers.get() != NULL); |
- EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
-} |
- |
-// Verify that various SynReply headers parse correctly through the |
-// HTTP layer. |
-TEST_P(SpdyNetworkTransactionTest, SynReplyHeaders) { |
- struct SynReplyHeadersTests { |
- int num_headers; |
- const char* extra_headers[5]; |
- SpdyHeaderBlock expected_headers; |
- } test_cases[] = { |
- // This uses a multi-valued cookie header. |
- { 2, |
- { "cookie", "val1", |
- "cookie", "val2", // will get appended separated by NULL |
- NULL |
- }, |
- }, |
- // This is the minimalist set of headers. |
- { 0, |
- { NULL }, |
- }, |
- // Headers with a comma separated list. |
- { 1, |
- { "cookie", "val1,val2", |
- NULL |
- }, |
- } |
- }; |
- |
- test_cases[0].expected_headers["cookie"] = "val1"; |
- test_cases[0].expected_headers["cookie"] += '\0'; |
- test_cases[0].expected_headers["cookie"] += "val2"; |
- test_cases[0].expected_headers["hello"] = "bye"; |
- test_cases[0].expected_headers["status"] = "200"; |
- test_cases[0].expected_headers["version"] = "HTTP/1.1"; |
- |
- test_cases[1].expected_headers["hello"] = "bye"; |
- test_cases[1].expected_headers["status"] = "200"; |
- test_cases[1].expected_headers["version"] = "HTTP/1.1"; |
- test_cases[2].expected_headers["cookie"] = "val1,val2"; |
- test_cases[2].expected_headers["hello"] = "bye"; |
- test_cases[2].expected_headers["status"] = "200"; |
- test_cases[2].expected_headers["version"] = "HTTP/1.1"; |
+ TransactionHelperResult out = helper.output(); |
+ out.rv = callback.WaitForResult(); |
+ EXPECT_EQ(out.rv, OK); |
- for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { |
- scoped_ptr<SpdyFrame> req( |
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- MockWrite writes[] = { CreateMockWrite(*req) }; |
+ const HttpResponseInfo* response = trans->GetResponseInfo(); |
+ EXPECT_TRUE(response->headers.get() != NULL); |
+ EXPECT_TRUE(response->was_fetched_via_spdy); |
+ out.status_line = response->headers->GetStatusLine(); |
+ out.response_info = *response; // Make a copy so we can verify. |
- scoped_ptr<SpdyFrame> resp( |
- spdy_util_.ConstructSpdyGetSynReply(test_cases[i].extra_headers, |
- test_cases[i].num_headers, |
- 1)); |
- scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- MockRead reads[] = { |
- CreateMockRead(*resp), |
- CreateMockRead(*body), |
- MockRead(ASYNC, 0, 0) // EOF |
- }; |
+ // Read Data |
+ TestCompletionCallback read_callback; |
- DelayedSocketData data(1, reads, arraysize(reads), |
- writes, arraysize(writes)); |
- NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
- BoundNetLog(), GetParam(), NULL); |
- helper.RunToCompletion(&data); |
- TransactionHelperResult out = helper.output(); |
+ do { |
+ const int kReadSize = 256; |
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kReadSize)); |
+ rv = trans->Read(buf.get(), kReadSize, read_callback.callback()); |
+ if (rv == net::ERR_IO_PENDING) { |
+ // Complete the read now, which causes buffering to start. |
+ data.CompleteRead(); |
+ // Destroy the transaction, causing the stream to get cancelled |
+ // and orphaning the buffered IO task. |
+ helper.ResetTrans(); |
+ break; |
+ } |
+ // We shouldn't get here in this test. |
+ FAIL() << "Unexpected read: " << rv; |
+ } while (rv > 0); |
- EXPECT_EQ(OK, out.rv); |
- EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); |
- EXPECT_EQ("hello!", out.response_data); |
+ // Flush the MessageLoop; this will cause the buffered IO task |
+ // to run for the final time. |
+ base::MessageLoop::current()->RunUntilIdle(); |
- scoped_refptr<HttpResponseHeaders> headers = out.response_info.headers; |
- EXPECT_TRUE(headers.get() != NULL); |
- void* iter = NULL; |
- std::string name, value; |
- SpdyHeaderBlock header_block; |
- while (headers->EnumerateHeaderLines(&iter, &name, &value)) { |
- if (header_block[name].empty()) { |
- header_block[name] = value; |
- } else { |
- header_block[name] += '\0'; |
- header_block[name] += value; |
- } |
- } |
- EXPECT_EQ(test_cases[i].expected_headers, header_block); |
- } |
+ // Verify that we consumed all test data. |
+ helper.VerifyDataConsumed(); |
} |
-// Verify that various SynReply headers parse vary fields correctly |
-// through the HTTP layer, and the response matches the request. |
-TEST_P(SpdyNetworkTransactionTest, SynReplyHeadersVary) { |
- static const SpdyHeaderInfo syn_reply_info = { |
+// Test that if the server requests persistence of settings, that we save |
+// the settings in the HttpServerProperties. |
+TEST_P(SpdyNetworkTransactionTest, SettingsSaved) { |
+ static const SpdyHeaderInfo kSynReplyInfo = { |
SYN_REPLY, // Syn Reply |
1, // Stream ID |
0, // Associated Stream ID |
@@ -3918,2469 +4178,2211 @@ TEST_P(SpdyNetworkTransactionTest, SynReplyHeadersVary) { |
0, // Data Length |
DATA_FLAG_NONE // Data Flags |
}; |
- // Modify the following data to change/add test cases: |
- struct SynReplyTests { |
- const SpdyHeaderInfo* syn_reply; |
- bool vary_matches; |
- int num_headers[2]; |
- const char* extra_headers[2][16]; |
- } test_cases[] = { |
- // Test the case of a multi-valued cookie. When the value is delimited |
- // with NUL characters, it needs to be unfolded into multiple headers. |
- { |
- &syn_reply_info, |
- true, |
- { 1, 4 }, |
- { { "cookie", "val1,val2", |
- NULL |
- }, |
- { "vary", "cookie", |
- spdy_util_.GetStatusKey(), "200", |
- spdy_util_.GetPathKey(), "/index.php", |
- spdy_util_.GetVersionKey(), "HTTP/1.1", |
- NULL |
- } |
- } |
- }, { // Multiple vary fields. |
- &syn_reply_info, |
- true, |
- { 2, 5 }, |
- { { "friend", "barney", |
- "enemy", "snaggletooth", |
- NULL |
- }, |
- { "vary", "friend", |
- "vary", "enemy", |
- spdy_util_.GetStatusKey(), "200", |
- spdy_util_.GetPathKey(), "/index.php", |
- spdy_util_.GetVersionKey(), "HTTP/1.1", |
- NULL |
- } |
- } |
- }, { // Test a '*' vary field. |
- &syn_reply_info, |
- false, |
- { 1, 4 }, |
- { { "cookie", "val1,val2", |
- NULL |
- }, |
- { "vary", "*", |
- spdy_util_.GetStatusKey(), "200", |
- spdy_util_.GetPathKey(), "/index.php", |
- spdy_util_.GetVersionKey(), "HTTP/1.1", |
- NULL |
- } |
- } |
- }, { // Multiple comma-separated vary fields. |
- &syn_reply_info, |
- true, |
- { 2, 4 }, |
- { { "friend", "barney", |
- "enemy", "snaggletooth", |
- NULL |
- }, |
- { "vary", "friend,enemy", |
- spdy_util_.GetStatusKey(), "200", |
- spdy_util_.GetPathKey(), "/index.php", |
- spdy_util_.GetVersionKey(), "HTTP/1.1", |
- NULL |
- } |
- } |
- } |
- }; |
- |
- for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { |
- // Construct the request. |
- scoped_ptr<SpdyFrame> frame_req( |
- spdy_util_.ConstructSpdyGet(test_cases[i].extra_headers[0], |
- test_cases[i].num_headers[0], |
- false, 1, LOWEST, true)); |
- MockWrite writes[] = { |
- CreateMockWrite(*frame_req), |
- }; |
+ BoundNetLog net_log; |
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
+ net_log, GetParam(), NULL); |
+ helper.RunPreTestSetup(); |
- // Construct the reply. |
- scoped_ptr<SpdyFrame> frame_reply( |
- spdy_util_.ConstructSpdyFrame(*test_cases[i].syn_reply, |
- test_cases[i].extra_headers[1], |
- test_cases[i].num_headers[1], |
- NULL, |
- 0)); |
+ // Verify that no settings exist initially. |
+ HostPortPair host_port_pair("www.google.com", helper.port()); |
+ SpdySessionPool* spdy_session_pool = helper.session()->spdy_session_pool(); |
+ EXPECT_TRUE(spdy_session_pool->http_server_properties()->GetSpdySettings( |
+ host_port_pair).empty()); |
- scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- MockRead reads[] = { |
- CreateMockRead(*frame_reply), |
- CreateMockRead(*body), |
- MockRead(ASYNC, 0, 0) // EOF |
- }; |
+ // Construct the request. |
+ scoped_ptr<SpdyFrame> req( |
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
+ MockWrite writes[] = { CreateMockWrite(*req) }; |
- // Attach the headers to the request. |
- int header_count = test_cases[i].num_headers[0]; |
+ // Construct the reply. |
+ scoped_ptr<SpdyHeaderBlock> reply_headers(new SpdyHeaderBlock()); |
+ (*reply_headers)[spdy_util_.GetStatusKey()] = "200"; |
+ (*reply_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1"; |
+ scoped_ptr<SpdyFrame> reply( |
+ spdy_util_.ConstructSpdyFrame(kSynReplyInfo, reply_headers.Pass())); |
- HttpRequestInfo request = CreateGetRequest(); |
- for (int ct = 0; ct < header_count; ct++) { |
- const char* header_key = test_cases[i].extra_headers[0][ct * 2]; |
- const char* header_value = test_cases[i].extra_headers[0][ct * 2 + 1]; |
- request.extra_headers.SetHeader(header_key, header_value); |
- } |
+ const SpdySettingsIds kSampleId1 = SETTINGS_UPLOAD_BANDWIDTH; |
+ unsigned int kSampleValue1 = 0x0a0a0a0a; |
+ const SpdySettingsIds kSampleId2 = SETTINGS_DOWNLOAD_BANDWIDTH; |
+ unsigned int kSampleValue2 = 0x0b0b0b0b; |
+ const SpdySettingsIds kSampleId3 = SETTINGS_ROUND_TRIP_TIME; |
+ unsigned int kSampleValue3 = 0x0c0c0c0c; |
+ scoped_ptr<SpdyFrame> settings_frame; |
+ { |
+ // Construct the SETTINGS frame. |
+ SettingsMap settings; |
+ // First add a persisted setting. |
+ settings[kSampleId1] = |
+ SettingsFlagsAndValue(SETTINGS_FLAG_PLEASE_PERSIST, kSampleValue1); |
+ // Next add a non-persisted setting. |
+ settings[kSampleId2] = |
+ SettingsFlagsAndValue(SETTINGS_FLAG_NONE, kSampleValue2); |
+ // Next add another persisted setting. |
+ settings[kSampleId3] = |
+ SettingsFlagsAndValue(SETTINGS_FLAG_PLEASE_PERSIST, kSampleValue3); |
+ settings_frame.reset(spdy_util_.ConstructSpdySettings(settings)); |
+ } |
- DelayedSocketData data(1, reads, arraysize(reads), |
- writes, arraysize(writes)); |
- NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY, |
- BoundNetLog(), GetParam(), NULL); |
- helper.RunToCompletion(&data); |
- TransactionHelperResult out = helper.output(); |
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ MockRead reads[] = { |
+ CreateMockRead(*reply), |
+ CreateMockRead(*body), |
+ CreateMockRead(*settings_frame), |
+ MockRead(ASYNC, 0, 0) // EOF |
+ }; |
- EXPECT_EQ(OK, out.rv) << i; |
- EXPECT_EQ("HTTP/1.1 200 OK", out.status_line) << i; |
- EXPECT_EQ("hello!", out.response_data) << i; |
+ DelayedSocketData data(1, reads, arraysize(reads), |
+ writes, arraysize(writes)); |
+ helper.AddData(&data); |
+ helper.RunDefaultTest(); |
+ helper.VerifyDataConsumed(); |
+ TransactionHelperResult out = helper.output(); |
+ EXPECT_EQ(OK, out.rv); |
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); |
+ EXPECT_EQ("hello!", out.response_data); |
- // Test the response information. |
- EXPECT_TRUE(out.response_info.response_time > |
- out.response_info.request_time) << i; |
- base::TimeDelta test_delay = out.response_info.response_time - |
- out.response_info.request_time; |
- base::TimeDelta min_expected_delay; |
- min_expected_delay.FromMilliseconds(10); |
- EXPECT_GT(test_delay.InMillisecondsF(), |
- min_expected_delay.InMillisecondsF()) << i; |
- EXPECT_EQ(out.response_info.vary_data.is_valid(), |
- test_cases[i].vary_matches) << i; |
+ { |
+ // Verify we had two persisted settings. |
+ const SettingsMap& settings_map = |
+ spdy_session_pool->http_server_properties()->GetSpdySettings( |
+ host_port_pair); |
+ ASSERT_EQ(2u, settings_map.size()); |
- // Check the headers. |
- scoped_refptr<HttpResponseHeaders> headers = out.response_info.headers; |
- ASSERT_TRUE(headers.get() != NULL) << i; |
- void* iter = NULL; |
- std::string name, value, lines; |
- while (headers->EnumerateHeaderLines(&iter, &name, &value)) { |
- lines.append(name); |
- lines.append(": "); |
- lines.append(value); |
- lines.append("\n"); |
- } |
+ // Verify the first persisted setting. |
+ SettingsMap::const_iterator it1 = settings_map.find(kSampleId1); |
+ EXPECT_TRUE(it1 != settings_map.end()); |
+ SettingsFlagsAndValue flags_and_value1 = it1->second; |
+ EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value1.first); |
+ EXPECT_EQ(kSampleValue1, flags_and_value1.second); |
- // Construct the expected header reply string. |
- SpdyHeaderBlock reply_headers; |
- AppendToHeaderBlock(test_cases[i].extra_headers[1], |
- test_cases[i].num_headers[1], |
- &reply_headers); |
- std::string expected_reply = |
- spdy_util_.ConstructSpdyReplyString(reply_headers); |
- EXPECT_EQ(expected_reply, lines) << i; |
+ // Verify the second persisted setting. |
+ SettingsMap::const_iterator it3 = settings_map.find(kSampleId3); |
+ EXPECT_TRUE(it3 != settings_map.end()); |
+ SettingsFlagsAndValue flags_and_value3 = it3->second; |
+ EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value3.first); |
+ EXPECT_EQ(kSampleValue3, flags_and_value3.second); |
} |
} |
-// Verify that we don't crash on invalid SynReply responses. |
-TEST_P(SpdyNetworkTransactionTest, InvalidSynReply) { |
- const SpdyHeaderInfo kSynStartHeader = { |
- SYN_REPLY, // Kind = SynReply |
- 1, // Stream ID |
- 0, // Associated stream ID |
- ConvertRequestPriorityToSpdyPriority( |
- LOWEST, spdy_util_.spdy_version()), |
- kSpdyCredentialSlotUnused, |
- CONTROL_FLAG_NONE, // Control Flags |
- false, // Compressed |
- RST_STREAM_INVALID, // Status |
- NULL, // Data |
- 0, // Length |
- DATA_FLAG_NONE // Data Flags |
- }; |
+// Test that when there are settings saved that they are sent back to the |
+// server upon session establishment. |
+TEST_P(SpdyNetworkTransactionTest, SettingsPlayback) { |
+ static const SpdyHeaderInfo kSynReplyInfo = { |
+ SYN_REPLY, // Syn Reply |
+ 1, // Stream ID |
+ 0, // Associated Stream ID |
+ ConvertRequestPriorityToSpdyPriority( |
+ LOWEST, spdy_util_.spdy_version()), |
+ kSpdyCredentialSlotUnused, |
+ CONTROL_FLAG_NONE, // Control Flags |
+ false, // Compressed |
+ RST_STREAM_INVALID, // Status |
+ NULL, // Data |
+ 0, // Data Length |
+ DATA_FLAG_NONE // Data Flags |
+ }; |
+ |
+ BoundNetLog net_log; |
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
+ net_log, GetParam(), NULL); |
+ helper.RunPreTestSetup(); |
+ |
+ // Verify that no settings exist initially. |
+ HostPortPair host_port_pair("www.google.com", helper.port()); |
+ SpdySessionPool* spdy_session_pool = helper.session()->spdy_session_pool(); |
+ EXPECT_TRUE(spdy_session_pool->http_server_properties()->GetSpdySettings( |
+ host_port_pair).empty()); |
+ |
+ const SpdySettingsIds kSampleId1 = SETTINGS_UPLOAD_BANDWIDTH; |
+ unsigned int kSampleValue1 = 0x0a0a0a0a; |
+ const SpdySettingsIds kSampleId2 = SETTINGS_ROUND_TRIP_TIME; |
+ unsigned int kSampleValue2 = 0x0c0c0c0c; |
+ |
+ // First add a persisted setting. |
+ spdy_session_pool->http_server_properties()->SetSpdySetting( |
+ host_port_pair, |
+ kSampleId1, |
+ SETTINGS_FLAG_PLEASE_PERSIST, |
+ kSampleValue1); |
+ |
+ // Next add another persisted setting. |
+ spdy_session_pool->http_server_properties()->SetSpdySetting( |
+ host_port_pair, |
+ kSampleId2, |
+ SETTINGS_FLAG_PLEASE_PERSIST, |
+ kSampleValue2); |
- struct InvalidSynReplyTests { |
- int num_headers; |
- const char* headers[10]; |
- } test_cases[] = { |
- // SYN_REPLY missing status header |
- { 4, |
- { "cookie", "val1", |
- "cookie", "val2", |
- spdy_util_.GetPathKey(), "/index.php", |
- spdy_util_.GetVersionKey(), "HTTP/1.1", |
- NULL |
- }, |
- }, |
- // SYN_REPLY missing version header |
- { 2, |
- { "status", "200", |
- spdy_util_.GetPathKey(), "/index.php", |
- NULL |
- }, |
- }, |
- // SYN_REPLY with no headers |
- { 0, { NULL }, }, |
- }; |
+ EXPECT_EQ(2u, spdy_session_pool->http_server_properties()->GetSpdySettings( |
+ host_port_pair).size()); |
- for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { |
- scoped_ptr<SpdyFrame> req( |
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- scoped_ptr<SpdyFrame> rst( |
- spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR)); |
- MockWrite writes[] = { |
- CreateMockWrite(*req), |
- CreateMockWrite(*rst), |
- }; |
+ // Construct the SETTINGS frame. |
+ const SettingsMap& settings = |
+ spdy_session_pool->http_server_properties()->GetSpdySettings( |
+ host_port_pair); |
+ scoped_ptr<SpdyFrame> settings_frame( |
+ spdy_util_.ConstructSpdySettings(settings)); |
- scoped_ptr<SpdyFrame> resp( |
- spdy_util_.ConstructSpdyFrame(kSynStartHeader, |
- NULL, 0, |
- test_cases[i].headers, |
- test_cases[i].num_headers)); |
- scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- MockRead reads[] = { |
- CreateMockRead(*resp), |
- MockRead(ASYNC, 0, 0) // EOF |
- }; |
+ // Construct the request. |
+ scoped_ptr<SpdyFrame> req( |
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- DelayedSocketData data(1, reads, arraysize(reads), |
- writes, arraysize(writes)); |
- NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
- BoundNetLog(), GetParam(), NULL); |
- helper.RunToCompletion(&data); |
- TransactionHelperResult out = helper.output(); |
- EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); |
- } |
-} |
+ MockWrite writes[] = { |
+ CreateMockWrite(*settings_frame), |
+ CreateMockWrite(*req), |
+ }; |
-// Verify that we don't crash on some corrupt frames. |
-TEST_P(SpdyNetworkTransactionTest, CorruptFrameSessionError) { |
- // This is the length field that's too short. |
- scoped_ptr<SpdyFrame> syn_reply_wrong_length( |
- spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- BufferedSpdyFramer framer(spdy_util_.spdy_version(), false); |
- size_t right_size = |
- (spdy_util_.spdy_version() < SPDY4) ? |
- syn_reply_wrong_length->size() - framer.GetControlFrameHeaderSize() : |
- syn_reply_wrong_length->size(); |
- size_t wrong_size = right_size - 4; |
- test::SetFrameLength(syn_reply_wrong_length.get(), |
- wrong_size, |
- spdy_util_.spdy_version()); |
+ // Construct the reply. |
+ scoped_ptr<SpdyHeaderBlock> reply_headers(new SpdyHeaderBlock()); |
+ (*reply_headers)[spdy_util_.GetStatusKey()] = "200"; |
+ (*reply_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1"; |
+ scoped_ptr<SpdyFrame> reply( |
+ spdy_util_.ConstructSpdyFrame(kSynReplyInfo, reply_headers.Pass())); |
- struct SynReplyTests { |
- const SpdyFrame* syn_reply; |
- } test_cases[] = { |
- { syn_reply_wrong_length.get(), }, |
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ MockRead reads[] = { |
+ CreateMockRead(*reply), |
+ CreateMockRead(*body), |
+ MockRead(ASYNC, 0, 0) // EOF |
}; |
- for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { |
- scoped_ptr<SpdyFrame> req( |
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- MockWrite writes[] = { CreateMockWrite(*req), MockWrite(ASYNC, 0, 0) // EOF |
- }; |
+ DelayedSocketData data(2, reads, arraysize(reads), |
+ writes, arraysize(writes)); |
+ helper.AddData(&data); |
+ helper.RunDefaultTest(); |
+ helper.VerifyDataConsumed(); |
+ TransactionHelperResult out = helper.output(); |
+ EXPECT_EQ(OK, out.rv); |
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); |
+ EXPECT_EQ("hello!", out.response_data); |
- scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- MockRead reads[] = { |
- MockRead(ASYNC, test_cases[i].syn_reply->data(), wrong_size), |
- CreateMockRead(*body), |
- MockRead(ASYNC, 0, 0) // EOF |
- }; |
+ { |
+ // Verify we had two persisted settings. |
+ const SettingsMap& settings_map = |
+ spdy_session_pool->http_server_properties()->GetSpdySettings( |
+ host_port_pair); |
+ ASSERT_EQ(2u, settings_map.size()); |
- DelayedSocketData data(1, reads, arraysize(reads), |
- writes, arraysize(writes)); |
- NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
- BoundNetLog(), GetParam(), NULL); |
- helper.RunToCompletion(&data); |
- TransactionHelperResult out = helper.output(); |
- EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); |
+ // Verify the first persisted setting. |
+ SettingsMap::const_iterator it1 = settings_map.find(kSampleId1); |
+ EXPECT_TRUE(it1 != settings_map.end()); |
+ SettingsFlagsAndValue flags_and_value1 = it1->second; |
+ EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value1.first); |
+ EXPECT_EQ(kSampleValue1, flags_and_value1.second); |
+ |
+ // Verify the second persisted setting. |
+ SettingsMap::const_iterator it2 = settings_map.find(kSampleId2); |
+ EXPECT_TRUE(it2 != settings_map.end()); |
+ SettingsFlagsAndValue flags_and_value2 = it2->second; |
+ EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value2.first); |
+ EXPECT_EQ(kSampleValue2, flags_and_value2.second); |
} |
} |
-// Test that we shutdown correctly on write errors. |
-TEST_P(SpdyNetworkTransactionTest, WriteError) { |
+TEST_P(SpdyNetworkTransactionTest, GoAwayWithActiveStream) { |
scoped_ptr<SpdyFrame> req( |
spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- MockWrite writes[] = { |
- // We'll write 10 bytes successfully |
- MockWrite(ASYNC, req->data(), 10), |
- // Followed by ERROR! |
- MockWrite(ASYNC, ERR_FAILED), |
+ MockWrite writes[] = { CreateMockWrite(*req) }; |
+ |
+ scoped_ptr<SpdyFrame> go_away(spdy_util_.ConstructSpdyGoAway()); |
+ MockRead reads[] = { |
+ CreateMockRead(*go_away), |
+ MockRead(ASYNC, 0, 0), // EOF |
}; |
- DelayedSocketData data(2, NULL, 0, |
+ DelayedSocketData data(1, reads, arraysize(reads), |
writes, arraysize(writes)); |
NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
BoundNetLog(), GetParam(), NULL); |
+ helper.AddData(&data); |
helper.RunToCompletion(&data); |
TransactionHelperResult out = helper.output(); |
- EXPECT_EQ(ERR_FAILED, out.rv); |
- data.Reset(); |
+ EXPECT_EQ(ERR_ABORTED, out.rv); |
} |
-// Test that partial writes work. |
-TEST_P(SpdyNetworkTransactionTest, PartialWrite) { |
- // Chop the SYN_STREAM frame into 5 chunks. |
+TEST_P(SpdyNetworkTransactionTest, CloseWithActiveStream) { |
scoped_ptr<SpdyFrame> req( |
spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- const int kChunks = 5; |
- scoped_ptr<MockWrite[]> writes(ChopWriteFrame(*req.get(), kChunks)); |
+ MockWrite writes[] = { CreateMockWrite(*req) }; |
scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
MockRead reads[] = { |
CreateMockRead(*resp), |
- CreateMockRead(*body), |
- MockRead(ASYNC, 0, 0) // EOF |
+ MockRead(SYNCHRONOUS, 0, 0) // EOF |
}; |
- DelayedSocketData data(kChunks, reads, arraysize(reads), |
- writes.get(), kChunks); |
+ DelayedSocketData data(1, reads, arraysize(reads), |
+ writes, arraysize(writes)); |
+ BoundNetLog log; |
NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
- BoundNetLog(), GetParam(), NULL); |
- helper.RunToCompletion(&data); |
- TransactionHelperResult out = helper.output(); |
- EXPECT_EQ(OK, out.rv); |
- EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); |
- EXPECT_EQ("hello!", out.response_data); |
+ log, GetParam(), NULL); |
+ helper.RunPreTestSetup(); |
+ helper.AddData(&data); |
+ HttpNetworkTransaction* trans = helper.trans(); |
+ |
+ TestCompletionCallback callback; |
+ TransactionHelperResult out; |
+ out.rv = trans->Start(&CreateGetRequest(), callback.callback(), log); |
+ |
+ EXPECT_EQ(out.rv, ERR_IO_PENDING); |
+ out.rv = callback.WaitForResult(); |
+ EXPECT_EQ(out.rv, OK); |
+ |
+ const HttpResponseInfo* response = trans->GetResponseInfo(); |
+ EXPECT_TRUE(response->headers.get() != NULL); |
+ EXPECT_TRUE(response->was_fetched_via_spdy); |
+ out.rv = ReadTransaction(trans, &out.response_data); |
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, out.rv); |
+ |
+ // Verify that we consumed all test data. |
+ helper.VerifyDataConsumed(); |
} |
-// In this test, we enable compression, but get a uncompressed SynReply from |
-// the server. Verify that teardown is all clean. |
-TEST_P(SpdyNetworkTransactionTest, DecompressFailureOnSynReply) { |
- scoped_ptr<SpdyFrame> compressed( |
- spdy_util_.ConstructSpdyGet(NULL, 0, true, 1, LOWEST, true)); |
- scoped_ptr<SpdyFrame> rst( |
- spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR)); |
- MockWrite writes[] = { |
- CreateMockWrite(*compressed), |
- }; |
+// Test to make sure we can correctly connect through a proxy. |
+TEST_P(SpdyNetworkTransactionTest, ProxyConnect) { |
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
+ BoundNetLog(), GetParam(), NULL); |
+ helper.session_deps().reset(CreateSpdySessionDependencies( |
+ GetParam(), |
+ ProxyService::CreateFixedFromPacResult("PROXY myproxy:70"))); |
+ helper.SetSession(make_scoped_refptr( |
+ SpdySessionDependencies::SpdyCreateSession(helper.session_deps().get()))); |
+ helper.RunPreTestSetup(); |
+ HttpNetworkTransaction* trans = helper.trans(); |
+ const char kConnect443[] = {"CONNECT www.google.com:443 HTTP/1.1\r\n" |
+ "Host: www.google.com\r\n" |
+ "Proxy-Connection: keep-alive\r\n\r\n"}; |
+ const char kConnect80[] = {"CONNECT www.google.com:80 HTTP/1.1\r\n" |
+ "Host: www.google.com\r\n" |
+ "Proxy-Connection: keep-alive\r\n\r\n"}; |
+ const char kHTTP200[] = {"HTTP/1.1 200 OK\r\n\r\n"}; |
+ scoped_ptr<SpdyFrame> req( |
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- MockRead reads[] = { |
- CreateMockRead(*resp), |
- }; |
- DelayedSocketData data(1, reads, arraysize(reads), |
- writes, arraysize(writes)); |
- SpdySessionDependencies* session_deps = |
- CreateSpdySessionDependencies(GetParam()); |
- session_deps->enable_compression = true; |
- NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
- BoundNetLog(), GetParam(), session_deps); |
- helper.RunToCompletion(&data); |
- TransactionHelperResult out = helper.output(); |
- EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); |
- data.Reset(); |
-} |
+ MockWrite writes_SPDYNPN[] = { |
+ MockWrite(SYNCHRONOUS, kConnect443, arraysize(kConnect443) - 1, 0), |
+ CreateMockWrite(*req, 2), |
+ }; |
+ MockRead reads_SPDYNPN[] = { |
+ MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1), |
+ CreateMockRead(*resp, 3), |
+ CreateMockRead(*body.get(), 4), |
+ MockRead(ASYNC, 0, 0, 5), |
+ }; |
-// Test that the NetLog contains good data for a simple GET request. |
-TEST_P(SpdyNetworkTransactionTest, NetLog) { |
- static const char* const kExtraHeaders[] = { |
- "user-agent", "Chrome", |
+ MockWrite writes_SPDYSSL[] = { |
+ MockWrite(SYNCHRONOUS, kConnect80, arraysize(kConnect80) - 1, 0), |
+ CreateMockWrite(*req, 2), |
+ }; |
+ MockRead reads_SPDYSSL[] = { |
+ MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1), |
+ CreateMockRead(*resp, 3), |
+ CreateMockRead(*body.get(), 4), |
+ MockRead(ASYNC, 0, 0, 5), |
}; |
- scoped_ptr<SpdyFrame> req( |
- spdy_util_.ConstructSpdyGet(kExtraHeaders, 1, false, 1, LOWEST, true)); |
- MockWrite writes[] = { CreateMockWrite(*req) }; |
- scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- MockRead reads[] = { |
- CreateMockRead(*resp), |
- CreateMockRead(*body), |
- MockRead(ASYNC, 0, 0) // EOF |
+ MockWrite writes_SPDYNOSSL[] = { |
+ CreateMockWrite(*req, 0), |
}; |
- CapturingBoundNetLog log; |
+ MockRead reads_SPDYNOSSL[] = { |
+ CreateMockRead(*resp, 1), |
+ CreateMockRead(*body.get(), 2), |
+ MockRead(ASYNC, 0, 0, 3), |
+ }; |
- DelayedSocketData data(1, reads, arraysize(reads), |
- writes, arraysize(writes)); |
- NormalSpdyTransactionHelper helper(CreateGetRequestWithUserAgent(), |
- DEFAULT_PRIORITY, |
- log.bound(), GetParam(), NULL); |
- helper.RunToCompletion(&data); |
- TransactionHelperResult out = helper.output(); |
- EXPECT_EQ(OK, out.rv); |
- EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); |
- EXPECT_EQ("hello!", out.response_data); |
+ scoped_ptr<OrderedSocketData> data; |
+ switch(GetParam().ssl_type) { |
+ case SPDYNOSSL: |
+ data.reset(new OrderedSocketData(reads_SPDYNOSSL, |
+ arraysize(reads_SPDYNOSSL), |
+ writes_SPDYNOSSL, |
+ arraysize(writes_SPDYNOSSL))); |
+ break; |
+ case SPDYSSL: |
+ data.reset(new OrderedSocketData(reads_SPDYSSL, |
+ arraysize(reads_SPDYSSL), |
+ writes_SPDYSSL, |
+ arraysize(writes_SPDYSSL))); |
+ break; |
+ case SPDYNPN: |
+ data.reset(new OrderedSocketData(reads_SPDYNPN, |
+ arraysize(reads_SPDYNPN), |
+ writes_SPDYNPN, |
+ arraysize(writes_SPDYNPN))); |
+ break; |
+ default: |
+ NOTREACHED(); |
+ } |
- // Check that the NetLog was filled reasonably. |
- // This test is intentionally non-specific about the exact ordering of the |
- // log; instead we just check to make sure that certain events exist, and that |
- // they are in the right order. |
- net::CapturingNetLog::CapturedEntryList entries; |
- log.GetEntries(&entries); |
+ helper.AddData(data.get()); |
+ TestCompletionCallback callback; |
- EXPECT_LT(0u, entries.size()); |
- int pos = 0; |
- pos = net::ExpectLogContainsSomewhere(entries, 0, |
- net::NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST, |
- net::NetLog::PHASE_BEGIN); |
- pos = net::ExpectLogContainsSomewhere(entries, pos + 1, |
- net::NetLog::TYPE_HTTP_TRANSACTION_SEND_REQUEST, |
- net::NetLog::PHASE_END); |
- pos = net::ExpectLogContainsSomewhere(entries, pos + 1, |
- net::NetLog::TYPE_HTTP_TRANSACTION_READ_HEADERS, |
- net::NetLog::PHASE_BEGIN); |
- pos = net::ExpectLogContainsSomewhere(entries, pos + 1, |
- net::NetLog::TYPE_HTTP_TRANSACTION_READ_HEADERS, |
- net::NetLog::PHASE_END); |
- pos = net::ExpectLogContainsSomewhere(entries, pos + 1, |
- net::NetLog::TYPE_HTTP_TRANSACTION_READ_BODY, |
- net::NetLog::PHASE_BEGIN); |
- pos = net::ExpectLogContainsSomewhere(entries, pos + 1, |
- net::NetLog::TYPE_HTTP_TRANSACTION_READ_BODY, |
- net::NetLog::PHASE_END); |
+ int rv = trans->Start( |
+ &CreateGetRequest(), callback.callback(), BoundNetLog()); |
+ EXPECT_EQ(ERR_IO_PENDING, rv); |
- // Check that we logged all the headers correctly |
- pos = net::ExpectLogContainsSomewhere( |
- entries, 0, |
- net::NetLog::TYPE_SPDY_SESSION_SYN_STREAM, |
- net::NetLog::PHASE_NONE); |
+ rv = callback.WaitForResult(); |
+ EXPECT_EQ(0, rv); |
- base::ListValue* header_list; |
- ASSERT_TRUE(entries[pos].params.get()); |
- ASSERT_TRUE(entries[pos].params->GetList("headers", &header_list)); |
+ // Verify the SYN_REPLY. |
+ HttpResponseInfo response = *trans->GetResponseInfo(); |
+ EXPECT_TRUE(response.headers.get() != NULL); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
- std::vector<std::string> expected; |
- expected.push_back(std::string(spdy_util_.GetHostKey()) + ": www.google.com"); |
- expected.push_back(std::string(spdy_util_.GetPathKey()) + ": /"); |
- expected.push_back(std::string(spdy_util_.GetSchemeKey()) + ": http"); |
- expected.push_back(std::string(spdy_util_.GetVersionKey()) + ": HTTP/1.1"); |
- expected.push_back(std::string(spdy_util_.GetMethodKey()) + ": GET"); |
- expected.push_back("user-agent: Chrome"); |
- EXPECT_EQ(expected.size(), header_list->GetSize()); |
- for (std::vector<std::string>::const_iterator it = expected.begin(); |
- it != expected.end(); |
- ++it) { |
- base::StringValue header(*it); |
- EXPECT_NE(header_list->end(), header_list->Find(header)) << |
- "Header not found: " << *it; |
- } |
+ std::string response_data; |
+ ASSERT_EQ(OK, ReadTransaction(trans, &response_data)); |
+ EXPECT_EQ("hello!", response_data); |
+ helper.VerifyDataConsumed(); |
} |
-// Since we buffer the IO from the stream to the renderer, this test verifies |
-// that when we read out the maximum amount of data (e.g. we received 50 bytes |
-// on the network, but issued a Read for only 5 of those bytes) that the data |
-// flow still works correctly. |
-TEST_P(SpdyNetworkTransactionTest, BufferFull) { |
- BufferedSpdyFramer framer(spdy_util_.spdy_version(), false); |
+// Test to make sure we can correctly connect through a proxy to www.google.com, |
+// if there already exists a direct spdy connection to www.google.com. See |
+// http://crbug.com/49874 |
+TEST_P(SpdyNetworkTransactionTest, DirectConnectProxyReconnect) { |
+ // When setting up the first transaction, we store the SpdySessionPool so that |
+ // we can use the same pool in the second transaction. |
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
+ BoundNetLog(), GetParam(), NULL); |
+ |
+ // Use a proxy service which returns a proxy fallback list from DIRECT to |
+ // myproxy:70. For this test there will be no fallback, so it is equivalent |
+ // to simply DIRECT. The reason for appending the second proxy is to verify |
+ // that the session pool key used does is just "DIRECT". |
+ helper.session_deps().reset(CreateSpdySessionDependencies( |
+ GetParam(), |
+ ProxyService::CreateFixedFromPacResult("DIRECT; PROXY myproxy:70"))); |
+ helper.SetSession(make_scoped_refptr( |
+ SpdySessionDependencies::SpdyCreateSession(helper.session_deps().get()))); |
+ |
+ SpdySessionPool* spdy_session_pool = helper.session()->spdy_session_pool(); |
+ helper.RunPreTestSetup(); |
+ // Construct and send a simple GET request. |
scoped_ptr<SpdyFrame> req( |
spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- MockWrite writes[] = { CreateMockWrite(*req) }; |
- |
- // 2 data frames in a single read. |
- scoped_ptr<SpdyFrame> data_frame_1( |
- framer.CreateDataFrame(1, "goodby", 6, DATA_FLAG_NONE)); |
- scoped_ptr<SpdyFrame> data_frame_2( |
- framer.CreateDataFrame(1, "e worl", 6, DATA_FLAG_NONE)); |
- const SpdyFrame* data_frames[2] = { |
- data_frame_1.get(), |
- data_frame_2.get(), |
+ MockWrite writes[] = { |
+ CreateMockWrite(*req, 1), |
}; |
- char combined_data_frames[100]; |
- int combined_data_frames_len = |
- CombineFrames(data_frames, arraysize(data_frames), |
- combined_data_frames, arraysize(combined_data_frames)); |
- scoped_ptr<SpdyFrame> last_frame( |
- framer.CreateDataFrame(1, "d", 1, DATA_FLAG_FIN)); |
scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
MockRead reads[] = { |
- CreateMockRead(*resp), |
- MockRead(ASYNC, ERR_IO_PENDING), // Force a pause |
- MockRead(ASYNC, combined_data_frames, combined_data_frames_len), |
- MockRead(ASYNC, ERR_IO_PENDING), // Force a pause |
- CreateMockRead(*last_frame), |
- MockRead(ASYNC, 0, 0) // EOF |
+ CreateMockRead(*resp, 2), |
+ CreateMockRead(*body, 3), |
+ MockRead(ASYNC, ERR_IO_PENDING, 4), // Force a pause |
+ MockRead(ASYNC, 0, 5) // EOF |
}; |
- |
- DelayedSocketData data(1, reads, arraysize(reads), |
+ OrderedSocketData data(reads, arraysize(reads), |
writes, arraysize(writes)); |
- |
- TestCompletionCallback callback; |
- |
- NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
- BoundNetLog(), GetParam(), NULL); |
- helper.RunPreTestSetup(); |
helper.AddData(&data); |
HttpNetworkTransaction* trans = helper.trans(); |
- int rv = trans->Start( |
+ |
+ TestCompletionCallback callback; |
+ TransactionHelperResult out; |
+ out.rv = trans->Start( |
&CreateGetRequest(), callback.callback(), BoundNetLog()); |
- EXPECT_EQ(ERR_IO_PENDING, rv); |
- TransactionHelperResult out = helper.output(); |
+ EXPECT_EQ(out.rv, ERR_IO_PENDING); |
out.rv = callback.WaitForResult(); |
EXPECT_EQ(out.rv, OK); |
const HttpResponseInfo* response = trans->GetResponseInfo(); |
EXPECT_TRUE(response->headers.get() != NULL); |
EXPECT_TRUE(response->was_fetched_via_spdy); |
+ out.rv = ReadTransaction(trans, &out.response_data); |
+ EXPECT_EQ(OK, out.rv); |
out.status_line = response->headers->GetStatusLine(); |
- out.response_info = *response; // Make a copy so we can verify. |
+ EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); |
+ EXPECT_EQ("hello!", out.response_data); |
+ |
+ // Check that the SpdySession is still in the SpdySessionPool. |
+ HostPortPair host_port_pair("www.google.com", helper.port()); |
+ SpdySessionKey session_pool_key_direct( |
+ host_port_pair, ProxyServer::Direct(), kPrivacyModeDisabled); |
+ EXPECT_TRUE(spdy_session_pool->HasSession(session_pool_key_direct)); |
+ SpdySessionKey session_pool_key_proxy( |
+ host_port_pair, |
+ ProxyServer::FromURI("www.foo.com", ProxyServer::SCHEME_HTTP), |
+ kPrivacyModeDisabled); |
+ EXPECT_FALSE(spdy_session_pool->HasSession(session_pool_key_proxy)); |
+ |
+ // Set up data for the proxy connection. |
+ const char kConnect443[] = {"CONNECT www.google.com:443 HTTP/1.1\r\n" |
+ "Host: www.google.com\r\n" |
+ "Proxy-Connection: keep-alive\r\n\r\n"}; |
+ const char kConnect80[] = {"CONNECT www.google.com:80 HTTP/1.1\r\n" |
+ "Host: www.google.com\r\n" |
+ "Proxy-Connection: keep-alive\r\n\r\n"}; |
+ const char kHTTP200[] = {"HTTP/1.1 200 OK\r\n\r\n"}; |
+ scoped_ptr<SpdyFrame> req2(spdy_util_.ConstructSpdyGet( |
+ "http://www.google.com/foo.dat", false, 1, LOWEST)); |
+ scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- // Read Data |
- TestCompletionCallback read_callback; |
+ MockWrite writes_SPDYNPN[] = { |
+ MockWrite(SYNCHRONOUS, kConnect443, arraysize(kConnect443) - 1, 0), |
+ CreateMockWrite(*req2, 2), |
+ }; |
+ MockRead reads_SPDYNPN[] = { |
+ MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1), |
+ CreateMockRead(*resp2, 3), |
+ CreateMockRead(*body2, 4), |
+ MockRead(ASYNC, 0, 5) // EOF |
+ }; |
- std::string content; |
- do { |
- // Read small chunks at a time. |
- const int kSmallReadSize = 3; |
- scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize)); |
- rv = trans->Read(buf.get(), kSmallReadSize, read_callback.callback()); |
- if (rv == net::ERR_IO_PENDING) { |
- data.CompleteRead(); |
- rv = read_callback.WaitForResult(); |
- } |
- if (rv > 0) { |
- content.append(buf->data(), rv); |
- } else if (rv < 0) { |
- NOTREACHED(); |
- } |
- } while (rv > 0); |
+ MockWrite writes_SPDYNOSSL[] = { |
+ CreateMockWrite(*req2, 0), |
+ }; |
+ MockRead reads_SPDYNOSSL[] = { |
+ CreateMockRead(*resp2, 1), |
+ CreateMockRead(*body2, 2), |
+ MockRead(ASYNC, 0, 3) // EOF |
+ }; |
- out.response_data.swap(content); |
+ MockWrite writes_SPDYSSL[] = { |
+ MockWrite(SYNCHRONOUS, kConnect80, arraysize(kConnect80) - 1, 0), |
+ CreateMockWrite(*req2, 2), |
+ }; |
+ MockRead reads_SPDYSSL[] = { |
+ MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1), |
+ CreateMockRead(*resp2, 3), |
+ CreateMockRead(*body2, 4), |
+ MockRead(ASYNC, 0, 0, 5), |
+ }; |
- // Flush the MessageLoop while the SpdySessionDependencies (in particular, the |
- // MockClientSocketFactory) are still alive. |
- base::MessageLoop::current()->RunUntilIdle(); |
+ scoped_ptr<OrderedSocketData> data_proxy; |
+ switch(GetParam().ssl_type) { |
+ case SPDYNPN: |
+ data_proxy.reset(new OrderedSocketData(reads_SPDYNPN, |
+ arraysize(reads_SPDYNPN), |
+ writes_SPDYNPN, |
+ arraysize(writes_SPDYNPN))); |
+ break; |
+ case SPDYNOSSL: |
+ data_proxy.reset(new OrderedSocketData(reads_SPDYNOSSL, |
+ arraysize(reads_SPDYNOSSL), |
+ writes_SPDYNOSSL, |
+ arraysize(writes_SPDYNOSSL))); |
+ break; |
+ case SPDYSSL: |
+ data_proxy.reset(new OrderedSocketData(reads_SPDYSSL, |
+ arraysize(reads_SPDYSSL), |
+ writes_SPDYSSL, |
+ arraysize(writes_SPDYSSL))); |
+ break; |
+ default: |
+ NOTREACHED(); |
+ } |
- // Verify that we consumed all test data. |
- helper.VerifyDataConsumed(); |
+ // Create another request to www.google.com, but this time through a proxy. |
+ HttpRequestInfo request_proxy; |
+ request_proxy.method = "GET"; |
+ request_proxy.url = GURL("http://www.google.com/foo.dat"); |
+ request_proxy.load_flags = 0; |
+ scoped_ptr<SpdySessionDependencies> ssd_proxy( |
+ CreateSpdySessionDependencies(GetParam())); |
+ // Ensure that this transaction uses the same SpdySessionPool. |
+ scoped_refptr<HttpNetworkSession> session_proxy( |
+ SpdySessionDependencies::SpdyCreateSession(ssd_proxy.get())); |
+ NormalSpdyTransactionHelper helper_proxy(request_proxy, DEFAULT_PRIORITY, |
+ BoundNetLog(), GetParam(), NULL); |
+ HttpNetworkSessionPeer session_peer(session_proxy); |
+ scoped_ptr<net::ProxyService> proxy_service( |
+ ProxyService::CreateFixedFromPacResult("PROXY myproxy:70")); |
+ session_peer.SetProxyService(proxy_service.get()); |
+ helper_proxy.session_deps().swap(ssd_proxy); |
+ helper_proxy.SetSession(session_proxy); |
+ helper_proxy.RunPreTestSetup(); |
+ helper_proxy.AddData(data_proxy.get()); |
- EXPECT_EQ(OK, out.rv); |
- EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); |
- EXPECT_EQ("goodbye world", out.response_data); |
-} |
+ HttpNetworkTransaction* trans_proxy = helper_proxy.trans(); |
+ TestCompletionCallback callback_proxy; |
+ int rv = trans_proxy->Start( |
+ &request_proxy, callback_proxy.callback(), BoundNetLog()); |
+ EXPECT_EQ(ERR_IO_PENDING, rv); |
+ rv = callback_proxy.WaitForResult(); |
+ EXPECT_EQ(0, rv); |
-// Verify that basic buffering works; when multiple data frames arrive |
-// at the same time, ensure that we don't notify a read completion for |
-// each data frame individually. |
-TEST_P(SpdyNetworkTransactionTest, Buffering) { |
- BufferedSpdyFramer framer(spdy_util_.spdy_version(), false); |
+ HttpResponseInfo response_proxy = *trans_proxy->GetResponseInfo(); |
+ EXPECT_TRUE(response_proxy.headers.get() != NULL); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response_proxy.headers->GetStatusLine()); |
- scoped_ptr<SpdyFrame> req( |
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- MockWrite writes[] = { CreateMockWrite(*req) }; |
+ std::string response_data; |
+ ASSERT_EQ(OK, ReadTransaction(trans_proxy, &response_data)); |
+ EXPECT_EQ("hello!", response_data); |
- // 4 data frames in a single read. |
- scoped_ptr<SpdyFrame> data_frame( |
- framer.CreateDataFrame(1, "message", 7, DATA_FLAG_NONE)); |
- scoped_ptr<SpdyFrame> data_frame_fin( |
- framer.CreateDataFrame(1, "message", 7, DATA_FLAG_FIN)); |
- const SpdyFrame* data_frames[4] = { |
- data_frame.get(), |
- data_frame.get(), |
- data_frame.get(), |
- data_frame_fin.get() |
- }; |
- char combined_data_frames[100]; |
- int combined_data_frames_len = |
- CombineFrames(data_frames, arraysize(data_frames), |
- combined_data_frames, arraysize(combined_data_frames)); |
+ data.CompleteRead(); |
+ helper_proxy.VerifyDataConsumed(); |
+} |
+// When we get a TCP-level RST, we need to retry a HttpNetworkTransaction |
+// on a new connection, if the connection was previously known to be good. |
+// This can happen when a server reboots without saying goodbye, or when |
+// we're behind a NAT that masked the RST. |
+TEST_P(SpdyNetworkTransactionTest, VerifyRetryOnConnectionReset) { |
scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
MockRead reads[] = { |
CreateMockRead(*resp), |
- MockRead(ASYNC, ERR_IO_PENDING), // Force a pause |
- MockRead(ASYNC, combined_data_frames, combined_data_frames_len), |
+ CreateMockRead(*body), |
+ MockRead(ASYNC, ERR_IO_PENDING), |
+ MockRead(ASYNC, ERR_CONNECTION_RESET), |
+ }; |
+ |
+ MockRead reads2[] = { |
+ CreateMockRead(*resp), |
+ CreateMockRead(*body), |
MockRead(ASYNC, 0, 0) // EOF |
}; |
- DelayedSocketData data(1, reads, arraysize(reads), |
- writes, arraysize(writes)); |
+ // This test has a couple of variants. |
+ enum { |
+ // Induce the RST while waiting for our transaction to send. |
+ VARIANT_RST_DURING_SEND_COMPLETION, |
+ // Induce the RST while waiting for our transaction to read. |
+ // In this case, the send completed - everything copied into the SNDBUF. |
+ VARIANT_RST_DURING_READ_COMPLETION |
+ }; |
- NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
- BoundNetLog(), GetParam(), NULL); |
- helper.RunPreTestSetup(); |
- helper.AddData(&data); |
- HttpNetworkTransaction* trans = helper.trans(); |
+ for (int variant = VARIANT_RST_DURING_SEND_COMPLETION; |
+ variant <= VARIANT_RST_DURING_READ_COMPLETION; |
+ ++variant) { |
+ DelayedSocketData data1(1, reads, arraysize(reads), NULL, 0); |
- TestCompletionCallback callback; |
- int rv = trans->Start( |
- &CreateGetRequest(), callback.callback(), BoundNetLog()); |
- EXPECT_EQ(ERR_IO_PENDING, rv); |
+ DelayedSocketData data2(1, reads2, arraysize(reads2), NULL, 0); |
- TransactionHelperResult out = helper.output(); |
- out.rv = callback.WaitForResult(); |
- EXPECT_EQ(out.rv, OK); |
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
+ BoundNetLog(), GetParam(), NULL); |
+ helper.AddData(&data1); |
+ helper.AddData(&data2); |
+ helper.RunPreTestSetup(); |
- const HttpResponseInfo* response = trans->GetResponseInfo(); |
- EXPECT_TRUE(response->headers.get() != NULL); |
- EXPECT_TRUE(response->was_fetched_via_spdy); |
- out.status_line = response->headers->GetStatusLine(); |
- out.response_info = *response; // Make a copy so we can verify. |
+ for (int i = 0; i < 2; ++i) { |
+ scoped_ptr<HttpNetworkTransaction> trans( |
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); |
- // Read Data |
- TestCompletionCallback read_callback; |
+ TestCompletionCallback callback; |
+ int rv = trans->Start( |
+ &helper.request(), callback.callback(), BoundNetLog()); |
+ EXPECT_EQ(ERR_IO_PENDING, rv); |
+ // On the second transaction, we trigger the RST. |
+ if (i == 1) { |
+ if (variant == VARIANT_RST_DURING_READ_COMPLETION) { |
+ // Writes to the socket complete asynchronously on SPDY by running |
+ // through the message loop. Complete the write here. |
+ base::MessageLoop::current()->RunUntilIdle(); |
+ } |
- std::string content; |
- int reads_completed = 0; |
- do { |
- // Read small chunks at a time. |
- const int kSmallReadSize = 14; |
- scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize)); |
- rv = trans->Read(buf.get(), kSmallReadSize, read_callback.callback()); |
- if (rv == net::ERR_IO_PENDING) { |
- data.CompleteRead(); |
- rv = read_callback.WaitForResult(); |
- } |
- if (rv > 0) { |
- EXPECT_EQ(kSmallReadSize, rv); |
- content.append(buf->data(), rv); |
- } else if (rv < 0) { |
- FAIL() << "Unexpected read error: " << rv; |
- } |
- reads_completed++; |
- } while (rv > 0); |
+ // Now schedule the ERR_CONNECTION_RESET. |
+ EXPECT_EQ(3u, data1.read_index()); |
+ data1.CompleteRead(); |
+ EXPECT_EQ(4u, data1.read_index()); |
+ } |
+ rv = callback.WaitForResult(); |
+ EXPECT_EQ(OK, rv); |
- EXPECT_EQ(3, reads_completed); // Reads are: 14 bytes, 14 bytes, 0 bytes. |
+ const HttpResponseInfo* response = trans->GetResponseInfo(); |
+ ASSERT_TRUE(response != NULL); |
+ EXPECT_TRUE(response->headers.get() != NULL); |
+ EXPECT_TRUE(response->was_fetched_via_spdy); |
+ std::string response_data; |
+ rv = ReadTransaction(trans.get(), &response_data); |
+ EXPECT_EQ(OK, rv); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); |
+ EXPECT_EQ("hello!", response_data); |
+ } |
- out.response_data.swap(content); |
+ helper.VerifyDataConsumed(); |
+ } |
+} |
- // Flush the MessageLoop while the SpdySessionDependencies (in particular, the |
- // MockClientSocketFactory) are still alive. |
- base::MessageLoop::current()->RunUntilIdle(); |
+// Test that turning SPDY on and off works properly. |
+TEST_P(SpdyNetworkTransactionTest, SpdyOnOffToggle) { |
+ net::HttpStreamFactory::set_spdy_enabled(true); |
+ scoped_ptr<SpdyFrame> req( |
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
+ MockWrite spdy_writes[] = { CreateMockWrite(*req) }; |
- // Verify that we consumed all test data. |
- helper.VerifyDataConsumed(); |
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ MockRead spdy_reads[] = { |
+ CreateMockRead(*resp), |
+ CreateMockRead(*body), |
+ MockRead(ASYNC, 0, 0) // EOF |
+ }; |
+ DelayedSocketData data(1, spdy_reads, arraysize(spdy_reads), |
+ spdy_writes, arraysize(spdy_writes)); |
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
+ BoundNetLog(), GetParam(), NULL); |
+ helper.RunToCompletion(&data); |
+ TransactionHelperResult out = helper.output(); |
EXPECT_EQ(OK, out.rv); |
EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); |
- EXPECT_EQ("messagemessagemessagemessage", out.response_data); |
+ EXPECT_EQ("hello!", out.response_data); |
+ |
+ net::HttpStreamFactory::set_spdy_enabled(false); |
+ MockRead http_reads[] = { |
+ MockRead("HTTP/1.1 200 OK\r\n\r\n"), |
+ MockRead("hello from http"), |
+ MockRead(SYNCHRONOUS, OK), |
+ }; |
+ DelayedSocketData data2(1, http_reads, arraysize(http_reads), NULL, 0); |
+ NormalSpdyTransactionHelper helper2(CreateGetRequest(), DEFAULT_PRIORITY, |
+ BoundNetLog(), GetParam(), NULL); |
+ helper2.SetSpdyDisabled(); |
+ helper2.RunToCompletion(&data2); |
+ TransactionHelperResult out2 = helper2.output(); |
+ EXPECT_EQ(OK, out2.rv); |
+ EXPECT_EQ("HTTP/1.1 200 OK", out2.status_line); |
+ EXPECT_EQ("hello from http", out2.response_data); |
+ |
+ net::HttpStreamFactory::set_spdy_enabled(true); |
} |
-// Verify the case where we buffer data but read it after it has been buffered. |
-TEST_P(SpdyNetworkTransactionTest, BufferedAll) { |
- BufferedSpdyFramer framer(spdy_util_.spdy_version(), false); |
+// Tests that Basic authentication works over SPDY |
+TEST_P(SpdyNetworkTransactionTest, SpdyBasicAuth) { |
+ net::HttpStreamFactory::set_spdy_enabled(true); |
- scoped_ptr<SpdyFrame> req( |
+ // The first request will be a bare GET, the second request will be a |
+ // GET with an Authorization header. |
+ scoped_ptr<SpdyFrame> req_get( |
spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- MockWrite writes[] = { CreateMockWrite(*req) }; |
- |
- // 5 data frames in a single read. |
- scoped_ptr<SpdyFrame> syn_reply( |
- spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- // turn off FIN bit |
- test::SetFrameFlags( |
- syn_reply.get(), CONTROL_FLAG_NONE, spdy_util_.spdy_version()); |
- scoped_ptr<SpdyFrame> data_frame( |
- framer.CreateDataFrame(1, "message", 7, DATA_FLAG_NONE)); |
- scoped_ptr<SpdyFrame> data_frame_fin( |
- framer.CreateDataFrame(1, "message", 7, DATA_FLAG_FIN)); |
- const SpdyFrame* frames[5] = { |
- syn_reply.get(), |
- data_frame.get(), |
- data_frame.get(), |
- data_frame.get(), |
- data_frame_fin.get() |
+ const char* const kExtraAuthorizationHeaders[] = { |
+ "authorization", "Basic Zm9vOmJhcg==" |
+ }; |
+ scoped_ptr<SpdyFrame> req_get_authorization( |
+ spdy_util_.ConstructSpdyGet(kExtraAuthorizationHeaders, |
+ arraysize(kExtraAuthorizationHeaders) / 2, |
+ false, 3, LOWEST, true)); |
+ MockWrite spdy_writes[] = { |
+ CreateMockWrite(*req_get, 1), |
+ CreateMockWrite(*req_get_authorization, 4), |
}; |
- char combined_frames[200]; |
- int combined_frames_len = |
- CombineFrames(frames, arraysize(frames), |
- combined_frames, arraysize(combined_frames)); |
- MockRead reads[] = { |
- MockRead(ASYNC, combined_frames, combined_frames_len), |
- MockRead(ASYNC, 0, 0) // EOF |
+ // The first response is a 401 authentication challenge, and the second |
+ // response will be a 200 response since the second request includes a valid |
+ // Authorization header. |
+ const char* const kExtraAuthenticationHeaders[] = { |
+ "www-authenticate", |
+ "Basic realm=\"MyRealm\"" |
+ }; |
+ scoped_ptr<SpdyFrame> resp_authentication( |
+ spdy_util_.ConstructSpdySynReplyError( |
+ "401 Authentication Required", |
+ kExtraAuthenticationHeaders, |
+ arraysize(kExtraAuthenticationHeaders) / 2, |
+ 1)); |
+ scoped_ptr<SpdyFrame> body_authentication( |
+ spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ scoped_ptr<SpdyFrame> resp_data( |
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3)); |
+ scoped_ptr<SpdyFrame> body_data(spdy_util_.ConstructSpdyBodyFrame(3, true)); |
+ MockRead spdy_reads[] = { |
+ CreateMockRead(*resp_authentication, 2), |
+ CreateMockRead(*body_authentication, 3), |
+ CreateMockRead(*resp_data, 5), |
+ CreateMockRead(*body_data, 6), |
+ MockRead(ASYNC, 0, 7), |
}; |
- DelayedSocketData data(1, reads, arraysize(reads), |
- writes, arraysize(writes)); |
+ OrderedSocketData data(spdy_reads, arraysize(spdy_reads), |
+ spdy_writes, arraysize(spdy_writes)); |
+ HttpRequestInfo request(CreateGetRequest()); |
+ BoundNetLog net_log; |
+ NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY, |
+ net_log, GetParam(), NULL); |
- NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
- BoundNetLog(), GetParam(), NULL); |
helper.RunPreTestSetup(); |
helper.AddData(&data); |
HttpNetworkTransaction* trans = helper.trans(); |
- |
TestCompletionCallback callback; |
- int rv = trans->Start( |
- &CreateGetRequest(), callback.callback(), BoundNetLog()); |
- EXPECT_EQ(ERR_IO_PENDING, rv); |
+ const int rv_start = trans->Start(&request, callback.callback(), net_log); |
+ EXPECT_EQ(ERR_IO_PENDING, rv_start); |
+ const int rv_start_complete = callback.WaitForResult(); |
+ EXPECT_EQ(OK, rv_start_complete); |
- TransactionHelperResult out = helper.output(); |
- out.rv = callback.WaitForResult(); |
- EXPECT_EQ(out.rv, OK); |
+ // Make sure the response has an auth challenge. |
+ const HttpResponseInfo* const response_start = trans->GetResponseInfo(); |
+ ASSERT_TRUE(response_start != NULL); |
+ ASSERT_TRUE(response_start->headers.get() != NULL); |
+ EXPECT_EQ(401, response_start->headers->response_code()); |
+ EXPECT_TRUE(response_start->was_fetched_via_spdy); |
+ AuthChallengeInfo* auth_challenge = response_start->auth_challenge.get(); |
+ ASSERT_TRUE(auth_challenge != NULL); |
+ EXPECT_FALSE(auth_challenge->is_proxy); |
+ EXPECT_EQ("basic", auth_challenge->scheme); |
+ EXPECT_EQ("MyRealm", auth_challenge->realm); |
- const HttpResponseInfo* response = trans->GetResponseInfo(); |
- EXPECT_TRUE(response->headers.get() != NULL); |
- EXPECT_TRUE(response->was_fetched_via_spdy); |
- out.status_line = response->headers->GetStatusLine(); |
- out.response_info = *response; // Make a copy so we can verify. |
+ // Restart with a username/password. |
+ AuthCredentials credentials(ASCIIToUTF16("foo"), ASCIIToUTF16("bar")); |
+ TestCompletionCallback callback_restart; |
+ const int rv_restart = trans->RestartWithAuth( |
+ credentials, callback_restart.callback()); |
+ EXPECT_EQ(ERR_IO_PENDING, rv_restart); |
+ const int rv_restart_complete = callback_restart.WaitForResult(); |
+ EXPECT_EQ(OK, rv_restart_complete); |
+ // TODO(cbentzel): This is actually the same response object as before, but |
+ // data has changed. |
+ const HttpResponseInfo* const response_restart = trans->GetResponseInfo(); |
+ ASSERT_TRUE(response_restart != NULL); |
+ ASSERT_TRUE(response_restart->headers.get() != NULL); |
+ EXPECT_EQ(200, response_restart->headers->response_code()); |
+ EXPECT_TRUE(response_restart->auth_challenge.get() == NULL); |
+} |
- // Read Data |
- TestCompletionCallback read_callback; |
+TEST_P(SpdyNetworkTransactionTest, ServerPushWithHeaders) { |
+ scoped_ptr<SpdyFrame> stream1_syn( |
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
+ scoped_ptr<SpdyFrame> stream1_body( |
+ spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ MockWrite writes[] = { |
+ CreateMockWrite(*stream1_syn, 1), |
+ }; |
- std::string content; |
- int reads_completed = 0; |
- do { |
- // Read small chunks at a time. |
- const int kSmallReadSize = 14; |
- scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize)); |
- rv = trans->Read(buf.get(), kSmallReadSize, read_callback.callback()); |
- if (rv > 0) { |
- EXPECT_EQ(kSmallReadSize, rv); |
- content.append(buf->data(), rv); |
- } else if (rv < 0) { |
- FAIL() << "Unexpected read error: " << rv; |
- } |
- reads_completed++; |
- } while (rv > 0); |
+ scoped_ptr<SpdyHeaderBlock> initial_headers(new SpdyHeaderBlock()); |
+ spdy_util_.AddUrlToHeaderBlock( |
+ "http://www.google.com/foo.dat", initial_headers.get()); |
+ scoped_ptr<SpdyFrame> stream2_syn( |
+ spdy_util_.ConstructSpdyControlFrame(initial_headers.Pass(), |
+ false, |
+ 2, |
+ LOWEST, |
+ SYN_STREAM, |
+ CONTROL_FLAG_NONE, |
+ 1)); |
- EXPECT_EQ(3, reads_completed); |
+ scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock()); |
+ (*late_headers)["hello"] = "bye"; |
+ (*late_headers)[spdy_util_.GetStatusKey()] = "200"; |
+ (*late_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1"; |
+ scoped_ptr<SpdyFrame> stream2_headers( |
+ spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(), |
+ false, |
+ 2, |
+ LOWEST, |
+ HEADERS, |
+ CONTROL_FLAG_NONE, |
+ 0)); |
- out.response_data.swap(content); |
+ scoped_ptr<SpdyFrame> |
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ const char kPushedData[] = "pushed"; |
+ scoped_ptr<SpdyFrame> stream2_body( |
+ spdy_util_.ConstructSpdyBodyFrame( |
+ 2, kPushedData, strlen(kPushedData), true)); |
+ MockRead reads[] = { |
+ CreateMockRead(*stream1_reply, 2), |
+ CreateMockRead(*stream2_syn, 3), |
+ CreateMockRead(*stream2_headers, 4), |
+ CreateMockRead(*stream1_body, 5, SYNCHRONOUS), |
+ CreateMockRead(*stream2_body, 5), |
+ MockRead(ASYNC, ERR_IO_PENDING, 7), // Force a pause |
+ }; |
- // Flush the MessageLoop while the SpdySessionDependencies (in particular, the |
- // MockClientSocketFactory) are still alive. |
- base::MessageLoop::current()->RunUntilIdle(); |
+ HttpResponseInfo response; |
+ HttpResponseInfo response2; |
+ std::string expected_push_result("pushed"); |
+ OrderedSocketData data(reads, arraysize(reads), |
+ writes, arraysize(writes)); |
+ RunServerPushTest(&data, |
+ &response, |
+ &response2, |
+ expected_push_result); |
- // Verify that we consumed all test data. |
- helper.VerifyDataConsumed(); |
+ // Verify the SYN_REPLY. |
+ EXPECT_TRUE(response.headers.get() != NULL); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
- EXPECT_EQ(OK, out.rv); |
- EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); |
- EXPECT_EQ("messagemessagemessagemessage", out.response_data); |
+ // Verify the pushed stream. |
+ EXPECT_TRUE(response2.headers.get() != NULL); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); |
} |
-// Verify the case where we buffer data and close the connection. |
-TEST_P(SpdyNetworkTransactionTest, BufferedClosed) { |
- BufferedSpdyFramer framer(spdy_util_.spdy_version(), false); |
+TEST_P(SpdyNetworkTransactionTest, ServerPushClaimBeforeHeaders) { |
+ // We push a stream and attempt to claim it before the headers come down. |
+ scoped_ptr<SpdyFrame> stream1_syn( |
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
+ scoped_ptr<SpdyFrame> stream1_body( |
+ spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ MockWrite writes[] = { |
+ CreateMockWrite(*stream1_syn, 0, SYNCHRONOUS), |
+ }; |
+ |
+ scoped_ptr<SpdyHeaderBlock> initial_headers(new SpdyHeaderBlock()); |
+ spdy_util_.AddUrlToHeaderBlock( |
+ "http://www.google.com/foo.dat", initial_headers.get()); |
+ scoped_ptr<SpdyFrame> stream2_syn( |
+ spdy_util_.ConstructSpdyControlFrame(initial_headers.Pass(), |
+ false, |
+ 2, |
+ LOWEST, |
+ SYN_STREAM, |
+ CONTROL_FLAG_NONE, |
+ 1)); |
- scoped_ptr<SpdyFrame> req( |
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- MockWrite writes[] = { CreateMockWrite(*req) }; |
+ scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock()); |
+ (*late_headers)["hello"] = "bye"; |
+ (*late_headers)[spdy_util_.GetStatusKey()] = "200"; |
+ (*late_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1"; |
+ scoped_ptr<SpdyFrame> stream2_headers( |
+ spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(), |
+ false, |
+ 2, |
+ LOWEST, |
+ HEADERS, |
+ CONTROL_FLAG_NONE, |
+ 0)); |
- // All data frames in a single read. |
- // NOTE: We don't FIN the stream. |
- scoped_ptr<SpdyFrame> data_frame( |
- framer.CreateDataFrame(1, "message", 7, DATA_FLAG_NONE)); |
- const SpdyFrame* data_frames[4] = { |
- data_frame.get(), |
- data_frame.get(), |
- data_frame.get(), |
- data_frame.get() |
- }; |
- char combined_data_frames[100]; |
- int combined_data_frames_len = |
- CombineFrames(data_frames, arraysize(data_frames), |
- combined_data_frames, arraysize(combined_data_frames)); |
- scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyFrame> |
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ const char kPushedData[] = "pushed"; |
+ scoped_ptr<SpdyFrame> stream2_body( |
+ spdy_util_.ConstructSpdyBodyFrame( |
+ 2, kPushedData, strlen(kPushedData), true)); |
MockRead reads[] = { |
- CreateMockRead(*resp), |
- MockRead(ASYNC, ERR_IO_PENDING), // Force a wait |
- MockRead(ASYNC, combined_data_frames, combined_data_frames_len), |
- MockRead(ASYNC, 0, 0) // EOF |
+ CreateMockRead(*stream1_reply, 1), |
+ CreateMockRead(*stream2_syn, 2), |
+ CreateMockRead(*stream1_body, 3), |
+ CreateMockRead(*stream2_headers, 4), |
+ CreateMockRead(*stream2_body, 5), |
+ MockRead(ASYNC, 0, 6), // EOF |
}; |
- DelayedSocketData data(1, reads, arraysize(reads), |
- writes, arraysize(writes)); |
+ HttpResponseInfo response; |
+ HttpResponseInfo response2; |
+ std::string expected_push_result("pushed"); |
+ DeterministicSocketData data(reads, arraysize(reads), |
+ writes, arraysize(writes)); |
NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
BoundNetLog(), GetParam(), NULL); |
+ helper.SetDeterministic(); |
+ helper.AddDeterministicData(&data); |
helper.RunPreTestSetup(); |
- helper.AddData(&data); |
+ |
HttpNetworkTransaction* trans = helper.trans(); |
- TestCompletionCallback callback; |
+ // Run until we've received the primary SYN_STREAM, the pushed SYN_STREAM, |
+ // and the body of the primary stream, but before we've received the HEADERS |
+ // for the pushed stream. |
+ data.SetStop(3); |
+ // Start the transaction. |
+ TestCompletionCallback callback; |
int rv = trans->Start( |
&CreateGetRequest(), callback.callback(), BoundNetLog()); |
EXPECT_EQ(ERR_IO_PENDING, rv); |
+ data.Run(); |
+ rv = callback.WaitForResult(); |
+ EXPECT_EQ(0, rv); |
- TransactionHelperResult out = helper.output(); |
- out.rv = callback.WaitForResult(); |
- EXPECT_EQ(out.rv, OK); |
+ // Request the pushed path. At this point, we've received the push, but the |
+ // headers are not yet complete. |
+ scoped_ptr<HttpNetworkTransaction> trans2( |
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); |
+ rv = trans2->Start( |
+ &CreateGetPushRequest(), callback.callback(), BoundNetLog()); |
+ EXPECT_EQ(ERR_IO_PENDING, rv); |
+ data.RunFor(3); |
+ base::MessageLoop::current()->RunUntilIdle(); |
- const HttpResponseInfo* response = trans->GetResponseInfo(); |
- EXPECT_TRUE(response->headers.get() != NULL); |
- EXPECT_TRUE(response->was_fetched_via_spdy); |
- out.status_line = response->headers->GetStatusLine(); |
- out.response_info = *response; // Make a copy so we can verify. |
+ // Read the server push body. |
+ std::string result2; |
+ ReadResult(trans2.get(), &data, &result2); |
+ // Read the response body. |
+ std::string result; |
+ ReadResult(trans, &data, &result); |
- // Read Data |
- TestCompletionCallback read_callback; |
+ // Verify that the received push data is same as the expected push data. |
+ EXPECT_EQ(result2.compare(expected_push_result), 0) |
+ << "Received data: " |
+ << result2 |
+ << "||||| Expected data: " |
+ << expected_push_result; |
- std::string content; |
- int reads_completed = 0; |
- do { |
- // Read small chunks at a time. |
- const int kSmallReadSize = 14; |
- scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kSmallReadSize)); |
- rv = trans->Read(buf.get(), kSmallReadSize, read_callback.callback()); |
- if (rv == net::ERR_IO_PENDING) { |
- data.CompleteRead(); |
- rv = read_callback.WaitForResult(); |
- } |
- if (rv > 0) { |
- content.append(buf->data(), rv); |
- } else if (rv < 0) { |
- // This test intentionally closes the connection, and will get an error. |
- EXPECT_EQ(ERR_CONNECTION_CLOSED, rv); |
- break; |
- } |
- reads_completed++; |
- } while (rv > 0); |
+ // Verify the SYN_REPLY. |
+ // Copy the response info, because trans goes away. |
+ response = *trans->GetResponseInfo(); |
+ response2 = *trans2->GetResponseInfo(); |
- EXPECT_EQ(0, reads_completed); |
+ VerifyStreamsClosed(helper); |
- out.response_data.swap(content); |
+ // Verify the SYN_REPLY. |
+ EXPECT_TRUE(response.headers.get() != NULL); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
- // Flush the MessageLoop while the SpdySessionDependencies (in particular, the |
- // MockClientSocketFactory) are still alive. |
- base::MessageLoop::current()->RunUntilIdle(); |
+ // Verify the pushed stream. |
+ EXPECT_TRUE(response2.headers.get() != NULL); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); |
+ |
+ // Read the final EOF (which will close the session) |
+ data.RunFor(1); |
// Verify that we consumed all test data. |
- helper.VerifyDataConsumed(); |
+ EXPECT_TRUE(data.at_read_eof()); |
+ EXPECT_TRUE(data.at_write_eof()); |
} |
-// Verify the case where we buffer data and cancel the transaction. |
-TEST_P(SpdyNetworkTransactionTest, BufferedCancelled) { |
- BufferedSpdyFramer framer(spdy_util_.spdy_version(), false); |
- |
- scoped_ptr<SpdyFrame> req( |
+TEST_P(SpdyNetworkTransactionTest, ServerPushWithTwoHeaderFrames) { |
+ // We push a stream and attempt to claim it before the headers come down. |
+ scoped_ptr<SpdyFrame> stream1_syn( |
spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- MockWrite writes[] = { CreateMockWrite(*req) }; |
- |
- // NOTE: We don't FIN the stream. |
- scoped_ptr<SpdyFrame> data_frame( |
- framer.CreateDataFrame(1, "message", 7, DATA_FLAG_NONE)); |
- |
- scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- MockRead reads[] = { |
- CreateMockRead(*resp), |
- MockRead(ASYNC, ERR_IO_PENDING), // Force a wait |
- CreateMockRead(*data_frame), |
- MockRead(ASYNC, 0, 0) // EOF |
+ scoped_ptr<SpdyFrame> stream1_body( |
+ spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ MockWrite writes[] = { |
+ CreateMockWrite(*stream1_syn, 0, SYNCHRONOUS), |
}; |
- DelayedSocketData data(1, reads, arraysize(reads), |
- writes, arraysize(writes)); |
- |
- NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
- BoundNetLog(), GetParam(), NULL); |
- helper.RunPreTestSetup(); |
- helper.AddData(&data); |
- HttpNetworkTransaction* trans = helper.trans(); |
- TestCompletionCallback callback; |
- |
- int rv = trans->Start( |
- &CreateGetRequest(), callback.callback(), BoundNetLog()); |
- EXPECT_EQ(ERR_IO_PENDING, rv); |
- |
- TransactionHelperResult out = helper.output(); |
- out.rv = callback.WaitForResult(); |
- EXPECT_EQ(out.rv, OK); |
- |
- const HttpResponseInfo* response = trans->GetResponseInfo(); |
- EXPECT_TRUE(response->headers.get() != NULL); |
- EXPECT_TRUE(response->was_fetched_via_spdy); |
- out.status_line = response->headers->GetStatusLine(); |
- out.response_info = *response; // Make a copy so we can verify. |
- |
- // Read Data |
- TestCompletionCallback read_callback; |
- |
- do { |
- const int kReadSize = 256; |
- scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kReadSize)); |
- rv = trans->Read(buf.get(), kReadSize, read_callback.callback()); |
- if (rv == net::ERR_IO_PENDING) { |
- // Complete the read now, which causes buffering to start. |
- data.CompleteRead(); |
- // Destroy the transaction, causing the stream to get cancelled |
- // and orphaning the buffered IO task. |
- helper.ResetTrans(); |
- break; |
- } |
- // We shouldn't get here in this test. |
- FAIL() << "Unexpected read: " << rv; |
- } while (rv > 0); |
+ scoped_ptr<SpdyHeaderBlock> initial_headers(new SpdyHeaderBlock()); |
+ spdy_util_.AddUrlToHeaderBlock( |
+ "http://www.google.com/foo.dat", initial_headers.get()); |
+ scoped_ptr<SpdyFrame> stream2_syn( |
+ spdy_util_.ConstructSpdyControlFrame(initial_headers.Pass(), |
+ false, |
+ 2, |
+ LOWEST, |
+ SYN_STREAM, |
+ CONTROL_FLAG_NONE, |
+ 1)); |
- // Flush the MessageLoop; this will cause the buffered IO task |
- // to run for the final time. |
- base::MessageLoop::current()->RunUntilIdle(); |
+ scoped_ptr<SpdyHeaderBlock> middle_headers(new SpdyHeaderBlock()); |
+ (*middle_headers)["hello"] = "bye"; |
+ scoped_ptr<SpdyFrame> stream2_headers1( |
+ spdy_util_.ConstructSpdyControlFrame(middle_headers.Pass(), |
+ false, |
+ 2, |
+ LOWEST, |
+ HEADERS, |
+ CONTROL_FLAG_NONE, |
+ 0)); |
- // Verify that we consumed all test data. |
- helper.VerifyDataConsumed(); |
-} |
+ scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock()); |
+ (*late_headers)[spdy_util_.GetStatusKey()] = "200"; |
+ (*late_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1"; |
+ scoped_ptr<SpdyFrame> stream2_headers2( |
+ spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(), |
+ false, |
+ 2, |
+ LOWEST, |
+ HEADERS, |
+ CONTROL_FLAG_NONE, |
+ 0)); |
-// Test that if the server requests persistence of settings, that we save |
-// the settings in the HttpServerProperties. |
-TEST_P(SpdyNetworkTransactionTest, SettingsSaved) { |
- static const SpdyHeaderInfo kSynReplyInfo = { |
- SYN_REPLY, // Syn Reply |
- 1, // Stream ID |
- 0, // Associated Stream ID |
- ConvertRequestPriorityToSpdyPriority( |
- LOWEST, spdy_util_.spdy_version()), |
- kSpdyCredentialSlotUnused, |
- CONTROL_FLAG_NONE, // Control Flags |
- false, // Compressed |
- RST_STREAM_INVALID, // Status |
- NULL, // Data |
- 0, // Data Length |
- DATA_FLAG_NONE // Data Flags |
+ scoped_ptr<SpdyFrame> |
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ const char kPushedData[] = "pushed"; |
+ scoped_ptr<SpdyFrame> stream2_body( |
+ spdy_util_.ConstructSpdyBodyFrame( |
+ 2, kPushedData, strlen(kPushedData), true)); |
+ MockRead reads[] = { |
+ CreateMockRead(*stream1_reply, 1), |
+ CreateMockRead(*stream2_syn, 2), |
+ CreateMockRead(*stream1_body, 3), |
+ CreateMockRead(*stream2_headers1, 4), |
+ CreateMockRead(*stream2_headers2, 5), |
+ CreateMockRead(*stream2_body, 6), |
+ MockRead(ASYNC, 0, 7), // EOF |
}; |
- BoundNetLog net_log; |
+ HttpResponseInfo response; |
+ HttpResponseInfo response2; |
+ std::string expected_push_result("pushed"); |
+ DeterministicSocketData data(reads, arraysize(reads), |
+ writes, arraysize(writes)); |
+ |
NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
- net_log, GetParam(), NULL); |
+ BoundNetLog(), GetParam(), NULL); |
+ helper.SetDeterministic(); |
+ helper.AddDeterministicData(&data); |
helper.RunPreTestSetup(); |
- // Verify that no settings exist initially. |
- HostPortPair host_port_pair("www.google.com", helper.port()); |
- SpdySessionPool* spdy_session_pool = helper.session()->spdy_session_pool(); |
- EXPECT_TRUE(spdy_session_pool->http_server_properties()->GetSpdySettings( |
- host_port_pair).empty()); |
+ HttpNetworkTransaction* trans = helper.trans(); |
- // Construct the request. |
- scoped_ptr<SpdyFrame> req( |
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- MockWrite writes[] = { CreateMockWrite(*req) }; |
+ // Run until we've received the primary SYN_STREAM, the pushed SYN_STREAM, |
+ // the first HEADERS frame, and the body of the primary stream, but before |
+ // we've received the final HEADERS for the pushed stream. |
+ data.SetStop(4); |
- // Construct the reply. |
- scoped_ptr<SpdyHeaderBlock> reply_headers(new SpdyHeaderBlock()); |
- (*reply_headers)[spdy_util_.GetStatusKey()] = "200"; |
- (*reply_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1"; |
- scoped_ptr<SpdyFrame> reply( |
- spdy_util_.ConstructSpdyFrame(kSynReplyInfo, reply_headers.Pass())); |
+ // Start the transaction. |
+ TestCompletionCallback callback; |
+ int rv = trans->Start( |
+ &CreateGetRequest(), callback.callback(), BoundNetLog()); |
+ EXPECT_EQ(ERR_IO_PENDING, rv); |
+ data.Run(); |
+ rv = callback.WaitForResult(); |
+ EXPECT_EQ(0, rv); |
- const SpdySettingsIds kSampleId1 = SETTINGS_UPLOAD_BANDWIDTH; |
- unsigned int kSampleValue1 = 0x0a0a0a0a; |
- const SpdySettingsIds kSampleId2 = SETTINGS_DOWNLOAD_BANDWIDTH; |
- unsigned int kSampleValue2 = 0x0b0b0b0b; |
- const SpdySettingsIds kSampleId3 = SETTINGS_ROUND_TRIP_TIME; |
- unsigned int kSampleValue3 = 0x0c0c0c0c; |
- scoped_ptr<SpdyFrame> settings_frame; |
- { |
- // Construct the SETTINGS frame. |
- SettingsMap settings; |
- // First add a persisted setting. |
- settings[kSampleId1] = |
- SettingsFlagsAndValue(SETTINGS_FLAG_PLEASE_PERSIST, kSampleValue1); |
- // Next add a non-persisted setting. |
- settings[kSampleId2] = |
- SettingsFlagsAndValue(SETTINGS_FLAG_NONE, kSampleValue2); |
- // Next add another persisted setting. |
- settings[kSampleId3] = |
- SettingsFlagsAndValue(SETTINGS_FLAG_PLEASE_PERSIST, kSampleValue3); |
- settings_frame.reset(spdy_util_.ConstructSpdySettings(settings)); |
- } |
+ // Request the pushed path. At this point, we've received the push, but the |
+ // headers are not yet complete. |
+ scoped_ptr<HttpNetworkTransaction> trans2( |
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); |
+ rv = trans2->Start( |
+ &CreateGetPushRequest(), callback.callback(), BoundNetLog()); |
+ EXPECT_EQ(ERR_IO_PENDING, rv); |
+ data.RunFor(3); |
+ base::MessageLoop::current()->RunUntilIdle(); |
- scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- MockRead reads[] = { |
- CreateMockRead(*reply), |
- CreateMockRead(*body), |
- CreateMockRead(*settings_frame), |
- MockRead(ASYNC, 0, 0) // EOF |
- }; |
+ // Read the server push body. |
+ std::string result2; |
+ ReadResult(trans2.get(), &data, &result2); |
+ // Read the response body. |
+ std::string result; |
+ ReadResult(trans, &data, &result); |
- DelayedSocketData data(1, reads, arraysize(reads), |
- writes, arraysize(writes)); |
- helper.AddData(&data); |
- helper.RunDefaultTest(); |
- helper.VerifyDataConsumed(); |
- TransactionHelperResult out = helper.output(); |
- EXPECT_EQ(OK, out.rv); |
- EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); |
- EXPECT_EQ("hello!", out.response_data); |
+ // Verify that the received push data is same as the expected push data. |
+ EXPECT_EQ(expected_push_result, result2); |
- { |
- // Verify we had two persisted settings. |
- const SettingsMap& settings_map = |
- spdy_session_pool->http_server_properties()->GetSpdySettings( |
- host_port_pair); |
- ASSERT_EQ(2u, settings_map.size()); |
+ // Verify the SYN_REPLY. |
+ // Copy the response info, because trans goes away. |
+ response = *trans->GetResponseInfo(); |
+ response2 = *trans2->GetResponseInfo(); |
- // Verify the first persisted setting. |
- SettingsMap::const_iterator it1 = settings_map.find(kSampleId1); |
- EXPECT_TRUE(it1 != settings_map.end()); |
- SettingsFlagsAndValue flags_and_value1 = it1->second; |
- EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value1.first); |
- EXPECT_EQ(kSampleValue1, flags_and_value1.second); |
+ VerifyStreamsClosed(helper); |
- // Verify the second persisted setting. |
- SettingsMap::const_iterator it3 = settings_map.find(kSampleId3); |
- EXPECT_TRUE(it3 != settings_map.end()); |
- SettingsFlagsAndValue flags_and_value3 = it3->second; |
- EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value3.first); |
- EXPECT_EQ(kSampleValue3, flags_and_value3.second); |
+ // Verify the SYN_REPLY. |
+ EXPECT_TRUE(response.headers.get() != NULL); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
+ |
+ // Verify the pushed stream. |
+ EXPECT_TRUE(response2.headers.get() != NULL); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); |
+ |
+ // Verify we got all the headers |
+ if (spdy_util_.spdy_version() < SPDY3) { |
+ EXPECT_TRUE(response2.headers->HasHeaderValue( |
+ "url", |
+ "http://www.google.com/foo.dat")); |
+ } else { |
+ EXPECT_TRUE(response2.headers->HasHeaderValue( |
+ "scheme", "http")); |
+ EXPECT_TRUE(response2.headers->HasHeaderValue( |
+ "host", "www.google.com")); |
+ EXPECT_TRUE(response2.headers->HasHeaderValue( |
+ "path", "/foo.dat")); |
} |
+ EXPECT_TRUE(response2.headers->HasHeaderValue("hello", "bye")); |
+ EXPECT_TRUE(response2.headers->HasHeaderValue("status", "200")); |
+ EXPECT_TRUE(response2.headers->HasHeaderValue("version", "HTTP/1.1")); |
+ |
+ // Read the final EOF (which will close the session) |
+ data.RunFor(1); |
+ |
+ // Verify that we consumed all test data. |
+ EXPECT_TRUE(data.at_read_eof()); |
+ EXPECT_TRUE(data.at_write_eof()); |
} |
-// Test that when there are settings saved that they are sent back to the |
-// server upon session establishment. |
-TEST_P(SpdyNetworkTransactionTest, SettingsPlayback) { |
- static const SpdyHeaderInfo kSynReplyInfo = { |
- SYN_REPLY, // Syn Reply |
- 1, // Stream ID |
- 0, // Associated Stream ID |
- ConvertRequestPriorityToSpdyPriority( |
- LOWEST, spdy_util_.spdy_version()), |
- kSpdyCredentialSlotUnused, |
- CONTROL_FLAG_NONE, // Control Flags |
- false, // Compressed |
- RST_STREAM_INVALID, // Status |
- NULL, // Data |
- 0, // Data Length |
- DATA_FLAG_NONE // Data Flags |
+TEST_P(SpdyNetworkTransactionTest, ServerPushWithNoStatusHeaderFrames) { |
+ // We push a stream and attempt to claim it before the headers come down. |
+ scoped_ptr<SpdyFrame> stream1_syn( |
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
+ scoped_ptr<SpdyFrame> stream1_body( |
+ spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ MockWrite writes[] = { |
+ CreateMockWrite(*stream1_syn, 0, SYNCHRONOUS), |
}; |
- BoundNetLog net_log; |
- NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
- net_log, GetParam(), NULL); |
- helper.RunPreTestSetup(); |
- |
- // Verify that no settings exist initially. |
- HostPortPair host_port_pair("www.google.com", helper.port()); |
- SpdySessionPool* spdy_session_pool = helper.session()->spdy_session_pool(); |
- EXPECT_TRUE(spdy_session_pool->http_server_properties()->GetSpdySettings( |
- host_port_pair).empty()); |
+ scoped_ptr<SpdyHeaderBlock> initial_headers(new SpdyHeaderBlock()); |
+ spdy_util_.AddUrlToHeaderBlock( |
+ "http://www.google.com/foo.dat", initial_headers.get()); |
+ scoped_ptr<SpdyFrame> stream2_syn( |
+ spdy_util_.ConstructSpdyControlFrame(initial_headers.Pass(), |
+ false, |
+ 2, |
+ LOWEST, |
+ SYN_STREAM, |
+ CONTROL_FLAG_NONE, |
+ 1)); |
- const SpdySettingsIds kSampleId1 = SETTINGS_UPLOAD_BANDWIDTH; |
- unsigned int kSampleValue1 = 0x0a0a0a0a; |
- const SpdySettingsIds kSampleId2 = SETTINGS_ROUND_TRIP_TIME; |
- unsigned int kSampleValue2 = 0x0c0c0c0c; |
+ scoped_ptr<SpdyHeaderBlock> middle_headers(new SpdyHeaderBlock()); |
+ (*middle_headers)["hello"] = "bye"; |
+ scoped_ptr<SpdyFrame> stream2_headers1( |
+ spdy_util_.ConstructSpdyControlFrame(middle_headers.Pass(), |
+ false, |
+ 2, |
+ LOWEST, |
+ HEADERS, |
+ CONTROL_FLAG_NONE, |
+ 0)); |
- // First add a persisted setting. |
- spdy_session_pool->http_server_properties()->SetSpdySetting( |
- host_port_pair, |
- kSampleId1, |
- SETTINGS_FLAG_PLEASE_PERSIST, |
- kSampleValue1); |
+ scoped_ptr<SpdyFrame> |
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ const char kPushedData[] = "pushed"; |
+ scoped_ptr<SpdyFrame> stream2_body( |
+ spdy_util_.ConstructSpdyBodyFrame( |
+ 2, kPushedData, strlen(kPushedData), true)); |
+ MockRead reads[] = { |
+ CreateMockRead(*stream1_reply, 1), |
+ CreateMockRead(*stream2_syn, 2), |
+ CreateMockRead(*stream1_body, 3), |
+ CreateMockRead(*stream2_headers1, 4), |
+ CreateMockRead(*stream2_body, 5), |
+ MockRead(ASYNC, 0, 6), // EOF |
+ }; |
- // Next add another persisted setting. |
- spdy_session_pool->http_server_properties()->SetSpdySetting( |
- host_port_pair, |
- kSampleId2, |
- SETTINGS_FLAG_PLEASE_PERSIST, |
- kSampleValue2); |
+ DeterministicSocketData data(reads, arraysize(reads), |
+ writes, arraysize(writes)); |
- EXPECT_EQ(2u, spdy_session_pool->http_server_properties()->GetSpdySettings( |
- host_port_pair).size()); |
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
+ BoundNetLog(), GetParam(), NULL); |
+ helper.SetDeterministic(); |
+ helper.AddDeterministicData(&data); |
+ helper.RunPreTestSetup(); |
- // Construct the SETTINGS frame. |
- const SettingsMap& settings = |
- spdy_session_pool->http_server_properties()->GetSpdySettings( |
- host_port_pair); |
- scoped_ptr<SpdyFrame> settings_frame( |
- spdy_util_.ConstructSpdySettings(settings)); |
+ HttpNetworkTransaction* trans = helper.trans(); |
- // Construct the request. |
- scoped_ptr<SpdyFrame> req( |
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
+ // Run until we've received the primary SYN_STREAM, the pushed SYN_STREAM, |
+ // the first HEADERS frame, and the body of the primary stream, but before |
+ // we've received the final HEADERS for the pushed stream. |
+ data.SetStop(4); |
- MockWrite writes[] = { |
- CreateMockWrite(*settings_frame), |
- CreateMockWrite(*req), |
- }; |
+ // Start the transaction. |
+ TestCompletionCallback callback; |
+ int rv = trans->Start( |
+ &CreateGetRequest(), callback.callback(), BoundNetLog()); |
+ EXPECT_EQ(ERR_IO_PENDING, rv); |
+ data.Run(); |
+ rv = callback.WaitForResult(); |
+ EXPECT_EQ(0, rv); |
- // Construct the reply. |
- scoped_ptr<SpdyHeaderBlock> reply_headers(new SpdyHeaderBlock()); |
- (*reply_headers)[spdy_util_.GetStatusKey()] = "200"; |
- (*reply_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1"; |
- scoped_ptr<SpdyFrame> reply( |
- spdy_util_.ConstructSpdyFrame(kSynReplyInfo, reply_headers.Pass())); |
+ // Request the pushed path. At this point, we've received the push, but the |
+ // headers are not yet complete. |
+ scoped_ptr<HttpNetworkTransaction> trans2( |
+ new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); |
+ rv = trans2->Start( |
+ &CreateGetPushRequest(), callback.callback(), BoundNetLog()); |
+ EXPECT_EQ(ERR_IO_PENDING, rv); |
+ data.RunFor(2); |
+ base::MessageLoop::current()->RunUntilIdle(); |
- scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- MockRead reads[] = { |
- CreateMockRead(*reply), |
- CreateMockRead(*body), |
- MockRead(ASYNC, 0, 0) // EOF |
- }; |
+ // Read the server push body. |
+ std::string result2; |
+ ReadResult(trans2.get(), &data, &result2); |
+ // Read the response body. |
+ std::string result; |
+ ReadResult(trans, &data, &result); |
+ EXPECT_EQ("hello!", result); |
- DelayedSocketData data(2, reads, arraysize(reads), |
- writes, arraysize(writes)); |
- helper.AddData(&data); |
- helper.RunDefaultTest(); |
- helper.VerifyDataConsumed(); |
- TransactionHelperResult out = helper.output(); |
- EXPECT_EQ(OK, out.rv); |
- EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); |
- EXPECT_EQ("hello!", out.response_data); |
+ // Verify that we haven't received any push data. |
+ EXPECT_EQ("", result2); |
- { |
- // Verify we had two persisted settings. |
- const SettingsMap& settings_map = |
- spdy_session_pool->http_server_properties()->GetSpdySettings( |
- host_port_pair); |
- ASSERT_EQ(2u, settings_map.size()); |
+ // Verify the SYN_REPLY. |
+ // Copy the response info, because trans goes away. |
+ HttpResponseInfo response = *trans->GetResponseInfo(); |
+ ASSERT_TRUE(trans2->GetResponseInfo() == NULL); |
- // Verify the first persisted setting. |
- SettingsMap::const_iterator it1 = settings_map.find(kSampleId1); |
- EXPECT_TRUE(it1 != settings_map.end()); |
- SettingsFlagsAndValue flags_and_value1 = it1->second; |
- EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value1.first); |
- EXPECT_EQ(kSampleValue1, flags_and_value1.second); |
+ VerifyStreamsClosed(helper); |
- // Verify the second persisted setting. |
- SettingsMap::const_iterator it2 = settings_map.find(kSampleId2); |
- EXPECT_TRUE(it2 != settings_map.end()); |
- SettingsFlagsAndValue flags_and_value2 = it2->second; |
- EXPECT_EQ(SETTINGS_FLAG_PERSISTED, flags_and_value2.first); |
- EXPECT_EQ(kSampleValue2, flags_and_value2.second); |
- } |
+ // Verify the SYN_REPLY. |
+ EXPECT_TRUE(response.headers.get() != NULL); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
+ |
+ // Read the final EOF (which will close the session). |
+ data.RunFor(1); |
+ |
+ // Verify that we consumed all test data. |
+ EXPECT_TRUE(data.at_read_eof()); |
+ EXPECT_TRUE(data.at_write_eof()); |
} |
-TEST_P(SpdyNetworkTransactionTest, GoAwayWithActiveStream) { |
+TEST_P(SpdyNetworkTransactionTest, SynReplyWithHeaders) { |
scoped_ptr<SpdyFrame> req( |
spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- MockWrite writes[] = { CreateMockWrite(*req) }; |
+ scoped_ptr<SpdyFrame> rst( |
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR)); |
+ MockWrite writes[] = { |
+ CreateMockWrite(*req), |
+ CreateMockWrite(*rst), |
+ }; |
- scoped_ptr<SpdyFrame> go_away(spdy_util_.ConstructSpdyGoAway()); |
+ scoped_ptr<SpdyHeaderBlock> initial_headers(new SpdyHeaderBlock()); |
+ (*initial_headers)[spdy_util_.GetStatusKey()] = "200 OK"; |
+ (*initial_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1"; |
+ scoped_ptr<SpdyFrame> stream1_reply( |
+ spdy_util_.ConstructSpdyControlFrame(initial_headers.Pass(), |
+ false, |
+ 1, |
+ LOWEST, |
+ SYN_REPLY, |
+ CONTROL_FLAG_NONE, |
+ 0)); |
+ |
+ scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock()); |
+ (*late_headers)["hello"] = "bye"; |
+ scoped_ptr<SpdyFrame> stream1_headers( |
+ spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(), |
+ false, |
+ 1, |
+ LOWEST, |
+ HEADERS, |
+ CONTROL_FLAG_NONE, |
+ 0)); |
+ scoped_ptr<SpdyFrame> stream1_body( |
+ spdy_util_.ConstructSpdyBodyFrame(1, true)); |
MockRead reads[] = { |
- CreateMockRead(*go_away), |
- MockRead(ASYNC, 0, 0), // EOF |
+ CreateMockRead(*stream1_reply), |
+ CreateMockRead(*stream1_headers), |
+ CreateMockRead(*stream1_body), |
+ MockRead(ASYNC, 0, 0) // EOF |
}; |
DelayedSocketData data(1, reads, arraysize(reads), |
writes, arraysize(writes)); |
NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
BoundNetLog(), GetParam(), NULL); |
- helper.AddData(&data); |
helper.RunToCompletion(&data); |
TransactionHelperResult out = helper.output(); |
- EXPECT_EQ(ERR_ABORTED, out.rv); |
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); |
} |
-TEST_P(SpdyNetworkTransactionTest, CloseWithActiveStream) { |
+TEST_P(SpdyNetworkTransactionTest, SynReplyWithLateHeaders) { |
scoped_ptr<SpdyFrame> req( |
spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- MockWrite writes[] = { CreateMockWrite(*req) }; |
+ scoped_ptr<SpdyFrame> rst( |
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR)); |
+ MockWrite writes[] = { |
+ CreateMockWrite(*req), |
+ CreateMockWrite(*rst), |
+ }; |
- scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyHeaderBlock> initial_headers(new SpdyHeaderBlock()); |
+ (*initial_headers)[spdy_util_.GetStatusKey()] = "200 OK"; |
+ (*initial_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1"; |
+ scoped_ptr<SpdyFrame> stream1_reply( |
+ spdy_util_.ConstructSpdyControlFrame(initial_headers.Pass(), |
+ false, |
+ 1, |
+ LOWEST, |
+ SYN_REPLY, |
+ CONTROL_FLAG_NONE, |
+ 0)); |
+ |
+ scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock()); |
+ (*late_headers)["hello"] = "bye"; |
+ scoped_ptr<SpdyFrame> stream1_headers( |
+ spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(), |
+ false, |
+ 1, |
+ LOWEST, |
+ HEADERS, |
+ CONTROL_FLAG_NONE, |
+ 0)); |
+ scoped_ptr<SpdyFrame> stream1_body( |
+ spdy_util_.ConstructSpdyBodyFrame(1, false)); |
+ scoped_ptr<SpdyFrame> stream1_body2( |
+ spdy_util_.ConstructSpdyBodyFrame(1, true)); |
MockRead reads[] = { |
- CreateMockRead(*resp), |
- MockRead(SYNCHRONOUS, 0, 0) // EOF |
+ CreateMockRead(*stream1_reply), |
+ CreateMockRead(*stream1_body), |
+ CreateMockRead(*stream1_headers), |
+ CreateMockRead(*stream1_body2), |
+ MockRead(ASYNC, 0, 0) // EOF |
}; |
DelayedSocketData data(1, reads, arraysize(reads), |
writes, arraysize(writes)); |
- BoundNetLog log; |
- NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
- log, GetParam(), NULL); |
- helper.RunPreTestSetup(); |
- helper.AddData(&data); |
- HttpNetworkTransaction* trans = helper.trans(); |
- |
- TestCompletionCallback callback; |
- TransactionHelperResult out; |
- out.rv = trans->Start(&CreateGetRequest(), callback.callback(), log); |
- |
- EXPECT_EQ(out.rv, ERR_IO_PENDING); |
- out.rv = callback.WaitForResult(); |
- EXPECT_EQ(out.rv, OK); |
- |
- const HttpResponseInfo* response = trans->GetResponseInfo(); |
- EXPECT_TRUE(response->headers.get() != NULL); |
- EXPECT_TRUE(response->was_fetched_via_spdy); |
- out.rv = ReadTransaction(trans, &out.response_data); |
- EXPECT_EQ(ERR_CONNECTION_CLOSED, out.rv); |
- |
- // Verify that we consumed all test data. |
- helper.VerifyDataConsumed(); |
-} |
- |
-// Test to make sure we can correctly connect through a proxy. |
-TEST_P(SpdyNetworkTransactionTest, ProxyConnect) { |
NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
BoundNetLog(), GetParam(), NULL); |
- helper.session_deps().reset(CreateSpdySessionDependencies( |
- GetParam(), |
- ProxyService::CreateFixedFromPacResult("PROXY myproxy:70"))); |
- helper.SetSession(make_scoped_refptr( |
- SpdySessionDependencies::SpdyCreateSession(helper.session_deps().get()))); |
- helper.RunPreTestSetup(); |
- HttpNetworkTransaction* trans = helper.trans(); |
+ helper.RunToCompletion(&data); |
+ TransactionHelperResult out = helper.output(); |
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); |
+} |
- const char kConnect443[] = {"CONNECT www.google.com:443 HTTP/1.1\r\n" |
- "Host: www.google.com\r\n" |
- "Proxy-Connection: keep-alive\r\n\r\n"}; |
- const char kConnect80[] = {"CONNECT www.google.com:80 HTTP/1.1\r\n" |
- "Host: www.google.com\r\n" |
- "Proxy-Connection: keep-alive\r\n\r\n"}; |
- const char kHTTP200[] = {"HTTP/1.1 200 OK\r\n\r\n"}; |
- scoped_ptr<SpdyFrame> req( |
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+TEST_P(SpdyNetworkTransactionTest, ServerPushCrossOriginCorrectness) { |
+ // In this test we want to verify that we can't accidentally push content |
+ // which can't be pushed by this content server. |
+ // This test assumes that: |
+ // - if we're requesting http://www.foo.com/barbaz |
+ // - the browser has made a connection to "www.foo.com". |
- MockWrite writes_SPDYNPN[] = { |
- MockWrite(SYNCHRONOUS, kConnect443, arraysize(kConnect443) - 1, 0), |
- CreateMockWrite(*req, 2), |
- }; |
- MockRead reads_SPDYNPN[] = { |
- MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1), |
- CreateMockRead(*resp, 3), |
- CreateMockRead(*body.get(), 4), |
- MockRead(ASYNC, 0, 0, 5), |
- }; |
+ // A list of the URL to fetch, followed by the URL being pushed. |
+ static const char* const kTestCases[] = { |
+ "http://www.google.com/foo.html", |
+ "http://www.google.com:81/foo.js", // Bad port |
- MockWrite writes_SPDYSSL[] = { |
- MockWrite(SYNCHRONOUS, kConnect80, arraysize(kConnect80) - 1, 0), |
- CreateMockWrite(*req, 2), |
- }; |
- MockRead reads_SPDYSSL[] = { |
- MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1), |
- CreateMockRead(*resp, 3), |
- CreateMockRead(*body.get(), 4), |
- MockRead(ASYNC, 0, 0, 5), |
- }; |
+ "http://www.google.com/foo.html", |
+ "https://www.google.com/foo.js", // Bad protocol |
- MockWrite writes_SPDYNOSSL[] = { |
- CreateMockWrite(*req, 0), |
- }; |
+ "http://www.google.com/foo.html", |
+ "ftp://www.google.com/foo.js", // Invalid Protocol |
- MockRead reads_SPDYNOSSL[] = { |
- CreateMockRead(*resp, 1), |
- CreateMockRead(*body.get(), 2), |
- MockRead(ASYNC, 0, 0, 3), |
- }; |
+ "http://www.google.com/foo.html", |
+ "http://blat.www.google.com/foo.js", // Cross subdomain |
- scoped_ptr<OrderedSocketData> data; |
- switch(GetParam().ssl_type) { |
- case SPDYNOSSL: |
- data.reset(new OrderedSocketData(reads_SPDYNOSSL, |
- arraysize(reads_SPDYNOSSL), |
- writes_SPDYNOSSL, |
- arraysize(writes_SPDYNOSSL))); |
- break; |
- case SPDYSSL: |
- data.reset(new OrderedSocketData(reads_SPDYSSL, |
- arraysize(reads_SPDYSSL), |
- writes_SPDYSSL, |
- arraysize(writes_SPDYSSL))); |
- break; |
- case SPDYNPN: |
- data.reset(new OrderedSocketData(reads_SPDYNPN, |
- arraysize(reads_SPDYNPN), |
- writes_SPDYNPN, |
- arraysize(writes_SPDYNPN))); |
- break; |
- default: |
- NOTREACHED(); |
- } |
+ "http://www.google.com/foo.html", |
+ "http://www.foo.com/foo.js", // Cross domain |
+ }; |
- helper.AddData(data.get()); |
- TestCompletionCallback callback; |
+ for (size_t index = 0; index < arraysize(kTestCases); index += 2) { |
+ const char* url_to_fetch = kTestCases[index]; |
+ const char* url_to_push = kTestCases[index + 1]; |
- int rv = trans->Start( |
- &CreateGetRequest(), callback.callback(), BoundNetLog()); |
- EXPECT_EQ(ERR_IO_PENDING, rv); |
+ scoped_ptr<SpdyFrame> stream1_syn( |
+ spdy_util_.ConstructSpdyGet(url_to_fetch, false, 1, LOWEST)); |
+ scoped_ptr<SpdyFrame> stream1_body( |
+ spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ scoped_ptr<SpdyFrame> push_rst( |
+ spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_REFUSED_STREAM)); |
+ MockWrite writes[] = { |
+ CreateMockWrite(*stream1_syn, 1), |
+ CreateMockWrite(*push_rst, 4), |
+ }; |
- rv = callback.WaitForResult(); |
- EXPECT_EQ(0, rv); |
+ scoped_ptr<SpdyFrame> |
+ stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyFrame> |
+ stream2_syn(spdy_util_.ConstructSpdyPush(NULL, |
+ 0, |
+ 2, |
+ 1, |
+ url_to_push)); |
+ const char kPushedData[] = "pushed"; |
+ scoped_ptr<SpdyFrame> stream2_body( |
+ spdy_util_.ConstructSpdyBodyFrame( |
+ 2, kPushedData, strlen(kPushedData), true)); |
+ scoped_ptr<SpdyFrame> rst( |
+ spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_CANCEL)); |
- // Verify the SYN_REPLY. |
- HttpResponseInfo response = *trans->GetResponseInfo(); |
- EXPECT_TRUE(response.headers.get() != NULL); |
- EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
+ MockRead reads[] = { |
+ CreateMockRead(*stream1_reply, 2), |
+ CreateMockRead(*stream2_syn, 3), |
+ CreateMockRead(*stream1_body, 5, SYNCHRONOUS), |
+ CreateMockRead(*stream2_body, 6), |
+ MockRead(ASYNC, ERR_IO_PENDING, 7), // Force a pause |
+ }; |
- std::string response_data; |
- ASSERT_EQ(OK, ReadTransaction(trans, &response_data)); |
- EXPECT_EQ("hello!", response_data); |
- helper.VerifyDataConsumed(); |
-} |
+ HttpResponseInfo response; |
+ OrderedSocketData data(reads, arraysize(reads), |
+ writes, arraysize(writes)); |
-// Test to make sure we can correctly connect through a proxy to www.google.com, |
-// if there already exists a direct spdy connection to www.google.com. See |
-// http://crbug.com/49874 |
-TEST_P(SpdyNetworkTransactionTest, DirectConnectProxyReconnect) { |
- // When setting up the first transaction, we store the SpdySessionPool so that |
- // we can use the same pool in the second transaction. |
- NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
- BoundNetLog(), GetParam(), NULL); |
+ HttpRequestInfo request; |
+ request.method = "GET"; |
+ request.url = GURL(url_to_fetch); |
+ request.load_flags = 0; |
- // Use a proxy service which returns a proxy fallback list from DIRECT to |
- // myproxy:70. For this test there will be no fallback, so it is equivalent |
- // to simply DIRECT. The reason for appending the second proxy is to verify |
- // that the session pool key used does is just "DIRECT". |
- helper.session_deps().reset(CreateSpdySessionDependencies( |
- GetParam(), |
- ProxyService::CreateFixedFromPacResult("DIRECT; PROXY myproxy:70"))); |
- helper.SetSession(make_scoped_refptr( |
- SpdySessionDependencies::SpdyCreateSession(helper.session_deps().get()))); |
+ // Enable cross-origin push. Since we are not using a proxy, this should |
+ // not actually enable cross-origin SPDY push. |
+ scoped_ptr<SpdySessionDependencies> session_deps( |
+ CreateSpdySessionDependencies(GetParam())); |
+ session_deps->trusted_spdy_proxy = "123.45.67.89:8080"; |
+ NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY, |
+ BoundNetLog(), GetParam(), |
+ session_deps.release()); |
+ helper.RunPreTestSetup(); |
+ helper.AddData(&data); |
- SpdySessionPool* spdy_session_pool = helper.session()->spdy_session_pool(); |
- helper.RunPreTestSetup(); |
+ HttpNetworkTransaction* trans = helper.trans(); |
- // Construct and send a simple GET request. |
- scoped_ptr<SpdyFrame> req( |
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- MockWrite writes[] = { |
- CreateMockWrite(*req, 1), |
- }; |
+ // Start the transaction with basic parameters. |
+ TestCompletionCallback callback; |
- scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- MockRead reads[] = { |
- CreateMockRead(*resp, 2), |
- CreateMockRead(*body, 3), |
- MockRead(ASYNC, ERR_IO_PENDING, 4), // Force a pause |
- MockRead(ASYNC, 0, 5) // EOF |
- }; |
- OrderedSocketData data(reads, arraysize(reads), |
- writes, arraysize(writes)); |
- helper.AddData(&data); |
- HttpNetworkTransaction* trans = helper.trans(); |
+ int rv = trans->Start(&request, callback.callback(), BoundNetLog()); |
+ EXPECT_EQ(ERR_IO_PENDING, rv); |
+ rv = callback.WaitForResult(); |
- TestCompletionCallback callback; |
- TransactionHelperResult out; |
- out.rv = trans->Start( |
- &CreateGetRequest(), callback.callback(), BoundNetLog()); |
+ // Read the response body. |
+ std::string result; |
+ ReadResult(trans, &data, &result); |
- EXPECT_EQ(out.rv, ERR_IO_PENDING); |
- out.rv = callback.WaitForResult(); |
- EXPECT_EQ(out.rv, OK); |
+ // Verify that we consumed all test data. |
+ EXPECT_TRUE(data.at_read_eof()); |
+ EXPECT_TRUE(data.at_write_eof()); |
- const HttpResponseInfo* response = trans->GetResponseInfo(); |
- EXPECT_TRUE(response->headers.get() != NULL); |
- EXPECT_TRUE(response->was_fetched_via_spdy); |
- out.rv = ReadTransaction(trans, &out.response_data); |
- EXPECT_EQ(OK, out.rv); |
- out.status_line = response->headers->GetStatusLine(); |
- EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); |
- EXPECT_EQ("hello!", out.response_data); |
+ // Verify the SYN_REPLY. |
+ // Copy the response info, because trans goes away. |
+ response = *trans->GetResponseInfo(); |
- // Check that the SpdySession is still in the SpdySessionPool. |
- HostPortPair host_port_pair("www.google.com", helper.port()); |
- SpdySessionKey session_pool_key_direct( |
- host_port_pair, ProxyServer::Direct(), kPrivacyModeDisabled); |
- EXPECT_TRUE(spdy_session_pool->HasSession(session_pool_key_direct)); |
- SpdySessionKey session_pool_key_proxy( |
- host_port_pair, |
- ProxyServer::FromURI("www.foo.com", ProxyServer::SCHEME_HTTP), |
- kPrivacyModeDisabled); |
- EXPECT_FALSE(spdy_session_pool->HasSession(session_pool_key_proxy)); |
+ VerifyStreamsClosed(helper); |
- // Set up data for the proxy connection. |
- const char kConnect443[] = {"CONNECT www.google.com:443 HTTP/1.1\r\n" |
- "Host: www.google.com\r\n" |
- "Proxy-Connection: keep-alive\r\n\r\n"}; |
- const char kConnect80[] = {"CONNECT www.google.com:80 HTTP/1.1\r\n" |
- "Host: www.google.com\r\n" |
- "Proxy-Connection: keep-alive\r\n\r\n"}; |
- const char kHTTP200[] = {"HTTP/1.1 200 OK\r\n\r\n"}; |
- scoped_ptr<SpdyFrame> req2(spdy_util_.ConstructSpdyGet( |
- "http://www.google.com/foo.dat", false, 1, LOWEST)); |
- scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ // Verify the SYN_REPLY. |
+ EXPECT_TRUE(response.headers.get() != NULL); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
+ } |
+} |
- MockWrite writes_SPDYNPN[] = { |
- MockWrite(SYNCHRONOUS, kConnect443, arraysize(kConnect443) - 1, 0), |
- CreateMockWrite(*req2, 2), |
- }; |
- MockRead reads_SPDYNPN[] = { |
- MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1), |
- CreateMockRead(*resp2, 3), |
- CreateMockRead(*body2, 4), |
- MockRead(ASYNC, 0, 5) // EOF |
+TEST_P(SpdyNetworkTransactionTest, RetryAfterRefused) { |
+ // Construct the request. |
+ scoped_ptr<SpdyFrame> req( |
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
+ scoped_ptr<SpdyFrame> req2( |
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, LOWEST, true)); |
+ MockWrite writes[] = { |
+ CreateMockWrite(*req, 1), |
+ CreateMockWrite(*req2, 3), |
}; |
- MockWrite writes_SPDYNOSSL[] = { |
- CreateMockWrite(*req2, 0), |
- }; |
- MockRead reads_SPDYNOSSL[] = { |
- CreateMockRead(*resp2, 1), |
- CreateMockRead(*body2, 2), |
- MockRead(ASYNC, 0, 3) // EOF |
+ scoped_ptr<SpdyFrame> refused( |
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_REFUSED_STREAM)); |
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3)); |
+ scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(3, true)); |
+ MockRead reads[] = { |
+ CreateMockRead(*refused, 2), |
+ CreateMockRead(*resp, 4), |
+ CreateMockRead(*body, 5), |
+ MockRead(ASYNC, 0, 6) // EOF |
}; |
- MockWrite writes_SPDYSSL[] = { |
- MockWrite(SYNCHRONOUS, kConnect80, arraysize(kConnect80) - 1, 0), |
- CreateMockWrite(*req2, 2), |
- }; |
- MockRead reads_SPDYSSL[] = { |
- MockRead(SYNCHRONOUS, kHTTP200, arraysize(kHTTP200) - 1, 1), |
- CreateMockRead(*resp2, 3), |
- CreateMockRead(*body2, 4), |
- MockRead(ASYNC, 0, 0, 5), |
- }; |
+ OrderedSocketData data(reads, arraysize(reads), |
+ writes, arraysize(writes)); |
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
+ BoundNetLog(), GetParam(), NULL); |
- scoped_ptr<OrderedSocketData> data_proxy; |
- switch(GetParam().ssl_type) { |
- case SPDYNPN: |
- data_proxy.reset(new OrderedSocketData(reads_SPDYNPN, |
- arraysize(reads_SPDYNPN), |
- writes_SPDYNPN, |
- arraysize(writes_SPDYNPN))); |
- break; |
- case SPDYNOSSL: |
- data_proxy.reset(new OrderedSocketData(reads_SPDYNOSSL, |
- arraysize(reads_SPDYNOSSL), |
- writes_SPDYNOSSL, |
- arraysize(writes_SPDYNOSSL))); |
- break; |
- case SPDYSSL: |
- data_proxy.reset(new OrderedSocketData(reads_SPDYSSL, |
- arraysize(reads_SPDYSSL), |
- writes_SPDYSSL, |
- arraysize(writes_SPDYSSL))); |
- break; |
- default: |
- NOTREACHED(); |
- } |
+ helper.RunPreTestSetup(); |
+ helper.AddData(&data); |
- // Create another request to www.google.com, but this time through a proxy. |
- HttpRequestInfo request_proxy; |
- request_proxy.method = "GET"; |
- request_proxy.url = GURL("http://www.google.com/foo.dat"); |
- request_proxy.load_flags = 0; |
- scoped_ptr<SpdySessionDependencies> ssd_proxy( |
- CreateSpdySessionDependencies(GetParam())); |
- // Ensure that this transaction uses the same SpdySessionPool. |
- scoped_refptr<HttpNetworkSession> session_proxy( |
- SpdySessionDependencies::SpdyCreateSession(ssd_proxy.get())); |
- NormalSpdyTransactionHelper helper_proxy(request_proxy, DEFAULT_PRIORITY, |
- BoundNetLog(), GetParam(), NULL); |
- HttpNetworkSessionPeer session_peer(session_proxy); |
- scoped_ptr<net::ProxyService> proxy_service( |
- ProxyService::CreateFixedFromPacResult("PROXY myproxy:70")); |
- session_peer.SetProxyService(proxy_service.get()); |
- helper_proxy.session_deps().swap(ssd_proxy); |
- helper_proxy.SetSession(session_proxy); |
- helper_proxy.RunPreTestSetup(); |
- helper_proxy.AddData(data_proxy.get()); |
+ HttpNetworkTransaction* trans = helper.trans(); |
- HttpNetworkTransaction* trans_proxy = helper_proxy.trans(); |
- TestCompletionCallback callback_proxy; |
- int rv = trans_proxy->Start( |
- &request_proxy, callback_proxy.callback(), BoundNetLog()); |
+ // Start the transaction with basic parameters. |
+ TestCompletionCallback callback; |
+ int rv = trans->Start( |
+ &CreateGetRequest(), callback.callback(), BoundNetLog()); |
EXPECT_EQ(ERR_IO_PENDING, rv); |
- rv = callback_proxy.WaitForResult(); |
- EXPECT_EQ(0, rv); |
- |
- HttpResponseInfo response_proxy = *trans_proxy->GetResponseInfo(); |
- EXPECT_TRUE(response_proxy.headers.get() != NULL); |
- EXPECT_EQ("HTTP/1.1 200 OK", response_proxy.headers->GetStatusLine()); |
+ rv = callback.WaitForResult(); |
+ EXPECT_EQ(OK, rv); |
- std::string response_data; |
- ASSERT_EQ(OK, ReadTransaction(trans_proxy, &response_data)); |
- EXPECT_EQ("hello!", response_data); |
+ // Verify that we consumed all test data. |
+ EXPECT_TRUE(data.at_read_eof()) << "Read count: " |
+ << data.read_count() |
+ << " Read index: " |
+ << data.read_index(); |
+ EXPECT_TRUE(data.at_write_eof()) << "Write count: " |
+ << data.write_count() |
+ << " Write index: " |
+ << data.write_index(); |
- data.CompleteRead(); |
- helper_proxy.VerifyDataConsumed(); |
+ // Verify the SYN_REPLY. |
+ HttpResponseInfo response = *trans->GetResponseInfo(); |
+ EXPECT_TRUE(response.headers.get() != NULL); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
} |
-// When we get a TCP-level RST, we need to retry a HttpNetworkTransaction |
-// on a new connection, if the connection was previously known to be good. |
-// This can happen when a server reboots without saying goodbye, or when |
-// we're behind a NAT that masked the RST. |
-TEST_P(SpdyNetworkTransactionTest, VerifyRetryOnConnectionReset) { |
- scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- MockRead reads[] = { |
- CreateMockRead(*resp), |
- CreateMockRead(*body), |
- MockRead(ASYNC, ERR_IO_PENDING), |
- MockRead(ASYNC, ERR_CONNECTION_RESET), |
- }; |
- |
- MockRead reads2[] = { |
- CreateMockRead(*resp), |
- CreateMockRead(*body), |
- MockRead(ASYNC, 0, 0) // EOF |
+TEST_P(SpdyNetworkTransactionTest, OutOfOrderSynStream) { |
+ // This first request will start to establish the SpdySession. |
+ // Then we will start the second (MEDIUM priority) and then third |
+ // (HIGHEST priority) request in such a way that the third will actually |
+ // start before the second, causing the second to be numbered differently |
+ // than the order they were created. |
+ scoped_ptr<SpdyFrame> req1( |
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
+ scoped_ptr<SpdyFrame> req2( |
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, HIGHEST, true)); |
+ scoped_ptr<SpdyFrame> req3( |
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 5, MEDIUM, true)); |
+ MockWrite writes[] = { |
+ CreateMockWrite(*req1, 0), |
+ CreateMockWrite(*req2, 3), |
+ CreateMockWrite(*req3, 4), |
}; |
- // This test has a couple of variants. |
- enum { |
- // Induce the RST while waiting for our transaction to send. |
- VARIANT_RST_DURING_SEND_COMPLETION, |
- // Induce the RST while waiting for our transaction to read. |
- // In this case, the send completed - everything copied into the SNDBUF. |
- VARIANT_RST_DURING_READ_COMPLETION |
+ scoped_ptr<SpdyFrame> resp1(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyFrame> body1(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
+ scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3)); |
+ scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(3, true)); |
+ scoped_ptr<SpdyFrame> resp3(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 5)); |
+ scoped_ptr<SpdyFrame> body3(spdy_util_.ConstructSpdyBodyFrame(5, true)); |
+ MockRead reads[] = { |
+ CreateMockRead(*resp1, 1), |
+ CreateMockRead(*body1, 2), |
+ CreateMockRead(*resp2, 5), |
+ CreateMockRead(*body2, 6), |
+ CreateMockRead(*resp3, 7), |
+ CreateMockRead(*body3, 8), |
+ MockRead(ASYNC, 0, 9) // EOF |
}; |
- for (int variant = VARIANT_RST_DURING_SEND_COMPLETION; |
- variant <= VARIANT_RST_DURING_READ_COMPLETION; |
- ++variant) { |
- DelayedSocketData data1(1, reads, arraysize(reads), NULL, 0); |
+ DeterministicSocketData data(reads, arraysize(reads), |
+ writes, arraysize(writes)); |
+ NormalSpdyTransactionHelper helper(CreateGetRequest(), LOWEST, |
+ BoundNetLog(), GetParam(), NULL); |
+ helper.SetDeterministic(); |
+ helper.RunPreTestSetup(); |
+ helper.AddDeterministicData(&data); |
- DelayedSocketData data2(1, reads2, arraysize(reads2), NULL, 0); |
+ // Start the first transaction to set up the SpdySession |
+ HttpNetworkTransaction* trans = helper.trans(); |
+ TestCompletionCallback callback; |
+ HttpRequestInfo info1 = CreateGetRequest(); |
+ int rv = trans->Start(&info1, callback.callback(), BoundNetLog()); |
+ EXPECT_EQ(ERR_IO_PENDING, rv); |
- NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
- BoundNetLog(), GetParam(), NULL); |
- helper.AddData(&data1); |
- helper.AddData(&data2); |
- helper.RunPreTestSetup(); |
+ // Run the message loop, but do not allow the write to complete. |
+ // This leaves the SpdySession with a write pending, which prevents |
+ // SpdySession from attempting subsequent writes until this write completes. |
+ base::MessageLoop::current()->RunUntilIdle(); |
- for (int i = 0; i < 2; ++i) { |
- scoped_ptr<HttpNetworkTransaction> trans( |
- new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); |
+ // Now, start both new transactions |
+ HttpRequestInfo info2 = CreateGetRequest(); |
+ TestCompletionCallback callback2; |
+ scoped_ptr<HttpNetworkTransaction> trans2( |
+ new HttpNetworkTransaction(MEDIUM, helper.session().get())); |
+ rv = trans2->Start(&info2, callback2.callback(), BoundNetLog()); |
+ EXPECT_EQ(ERR_IO_PENDING, rv); |
+ base::MessageLoop::current()->RunUntilIdle(); |
- TestCompletionCallback callback; |
- int rv = trans->Start( |
- &helper.request(), callback.callback(), BoundNetLog()); |
- EXPECT_EQ(ERR_IO_PENDING, rv); |
- // On the second transaction, we trigger the RST. |
- if (i == 1) { |
- if (variant == VARIANT_RST_DURING_READ_COMPLETION) { |
- // Writes to the socket complete asynchronously on SPDY by running |
- // through the message loop. Complete the write here. |
- base::MessageLoop::current()->RunUntilIdle(); |
- } |
+ HttpRequestInfo info3 = CreateGetRequest(); |
+ TestCompletionCallback callback3; |
+ scoped_ptr<HttpNetworkTransaction> trans3( |
+ new HttpNetworkTransaction(HIGHEST, helper.session().get())); |
+ rv = trans3->Start(&info3, callback3.callback(), BoundNetLog()); |
+ EXPECT_EQ(ERR_IO_PENDING, rv); |
+ base::MessageLoop::current()->RunUntilIdle(); |
- // Now schedule the ERR_CONNECTION_RESET. |
- EXPECT_EQ(3u, data1.read_index()); |
- data1.CompleteRead(); |
- EXPECT_EQ(4u, data1.read_index()); |
- } |
- rv = callback.WaitForResult(); |
- EXPECT_EQ(OK, rv); |
+ // We now have two SYN_STREAM frames queued up which will be |
+ // dequeued only once the first write completes, which we |
+ // now allow to happen. |
+ data.RunFor(2); |
+ EXPECT_EQ(OK, callback.WaitForResult()); |
- const HttpResponseInfo* response = trans->GetResponseInfo(); |
- ASSERT_TRUE(response != NULL); |
- EXPECT_TRUE(response->headers.get() != NULL); |
- EXPECT_TRUE(response->was_fetched_via_spdy); |
- std::string response_data; |
- rv = ReadTransaction(trans.get(), &response_data); |
- EXPECT_EQ(OK, rv); |
- EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); |
- EXPECT_EQ("hello!", response_data); |
- } |
+ // And now we can allow everything else to run to completion. |
+ data.SetStop(10); |
+ data.Run(); |
+ EXPECT_EQ(OK, callback2.WaitForResult()); |
+ EXPECT_EQ(OK, callback3.WaitForResult()); |
- helper.VerifyDataConsumed(); |
- } |
+ helper.VerifyDataConsumed(); |
} |
-// Test that turning SPDY on and off works properly. |
-TEST_P(SpdyNetworkTransactionTest, SpdyOnOffToggle) { |
- net::HttpStreamFactory::set_spdy_enabled(true); |
- scoped_ptr<SpdyFrame> req( |
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- MockWrite spdy_writes[] = { CreateMockWrite(*req) }; |
+// The tests below are only for SPDY/3 and above. |
+ |
+// Test that sent data frames and received WINDOW_UPDATE frames change |
+// the send_window_size_ correctly. |
+ |
+// WINDOW_UPDATE is different than most other frames in that it can arrive |
+// while the client is still sending the request body. In order to enforce |
+// this scenario, we feed a couple of dummy frames and give a delay of 0 to |
+// socket data provider, so that initial read that is done as soon as the |
+// stream is created, succeeds and schedules another read. This way reads |
+// and writes are interleaved; after doing a full frame write, SpdyStream |
+// will break out of DoLoop and will read and process a WINDOW_UPDATE. |
+// Once our WINDOW_UPDATE is read, we cannot send SYN_REPLY right away |
+// since request has not been completely written, therefore we feed |
+// enough number of WINDOW_UPDATEs to finish the first read and cause a |
+// write, leading to a complete write of request body; after that we send |
+// a reply with a body, to cause a graceful shutdown. |
- scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- MockRead spdy_reads[] = { |
- CreateMockRead(*resp), |
- CreateMockRead(*body), |
- MockRead(ASYNC, 0, 0) // EOF |
- }; |
+// TODO(agayev): develop a socket data provider where both, reads and |
+// writes are ordered so that writing tests like these are easy and rewrite |
+// all these tests using it. Right now we are working around the |
+// limitations as described above and it's not deterministic, tests may |
+// fail under specific circumstances. |
+TEST_P(SpdyNetworkTransactionTest, WindowUpdateReceived) { |
+ if (GetParam().protocol < kProtoSPDY3) |
+ return; |
- DelayedSocketData data(1, spdy_reads, arraysize(spdy_reads), |
- spdy_writes, arraysize(spdy_writes)); |
- NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
- BoundNetLog(), GetParam(), NULL); |
- helper.RunToCompletion(&data); |
- TransactionHelperResult out = helper.output(); |
- EXPECT_EQ(OK, out.rv); |
- EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); |
- EXPECT_EQ("hello!", out.response_data); |
+ static int kFrameCount = 2; |
+ scoped_ptr<std::string> content( |
+ new std::string(kMaxSpdyFrameChunkSize, 'a')); |
+ scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyPost( |
+ kRequestUrl, 1, kMaxSpdyFrameChunkSize * kFrameCount, LOWEST, NULL, 0)); |
+ scoped_ptr<SpdyFrame> body( |
+ spdy_util_.ConstructSpdyBodyFrame( |
+ 1, content->c_str(), content->size(), false)); |
+ scoped_ptr<SpdyFrame> body_end( |
+ spdy_util_.ConstructSpdyBodyFrame( |
+ 1, content->c_str(), content->size(), true)); |
- net::HttpStreamFactory::set_spdy_enabled(false); |
- MockRead http_reads[] = { |
- MockRead("HTTP/1.1 200 OK\r\n\r\n"), |
- MockRead("hello from http"), |
- MockRead(SYNCHRONOUS, OK), |
+ MockWrite writes[] = { |
+ CreateMockWrite(*req, 0), |
+ CreateMockWrite(*body, 1), |
+ CreateMockWrite(*body_end, 2), |
}; |
- DelayedSocketData data2(1, http_reads, arraysize(http_reads), NULL, 0); |
- NormalSpdyTransactionHelper helper2(CreateGetRequest(), DEFAULT_PRIORITY, |
- BoundNetLog(), GetParam(), NULL); |
- helper2.SetSpdyDisabled(); |
- helper2.RunToCompletion(&data2); |
- TransactionHelperResult out2 = helper2.output(); |
- EXPECT_EQ(OK, out2.rv); |
- EXPECT_EQ("HTTP/1.1 200 OK", out2.status_line); |
- EXPECT_EQ("hello from http", out2.response_data); |
- net::HttpStreamFactory::set_spdy_enabled(true); |
-} |
+ static const int32 kDeltaWindowSize = 0xff; |
+ static const int kDeltaCount = 4; |
+ scoped_ptr<SpdyFrame> window_update( |
+ spdy_util_.ConstructSpdyWindowUpdate(1, kDeltaWindowSize)); |
+ scoped_ptr<SpdyFrame> window_update_dummy( |
+ spdy_util_.ConstructSpdyWindowUpdate(2, kDeltaWindowSize)); |
+ scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); |
+ MockRead reads[] = { |
+ CreateMockRead(*window_update_dummy, 3), |
+ CreateMockRead(*window_update_dummy, 4), |
+ CreateMockRead(*window_update_dummy, 5), |
+ CreateMockRead(*window_update, 6), // Four updates, therefore window |
+ CreateMockRead(*window_update, 7), // size should increase by |
+ CreateMockRead(*window_update, 8), // kDeltaWindowSize * 4 |
+ CreateMockRead(*window_update, 9), |
+ CreateMockRead(*resp, 10), |
+ CreateMockRead(*body_end, 11), |
+ MockRead(ASYNC, 0, 0, 12) // EOF |
+ }; |
-// Tests that Basic authentication works over SPDY |
-TEST_P(SpdyNetworkTransactionTest, SpdyBasicAuth) { |
- net::HttpStreamFactory::set_spdy_enabled(true); |
+ DeterministicSocketData data(reads, arraysize(reads), |
+ writes, arraysize(writes)); |
- // The first request will be a bare GET, the second request will be a |
- // GET with an Authorization header. |
- scoped_ptr<SpdyFrame> req_get( |
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- const char* const kExtraAuthorizationHeaders[] = { |
- "authorization", "Basic Zm9vOmJhcg==" |
- }; |
- scoped_ptr<SpdyFrame> req_get_authorization( |
- spdy_util_.ConstructSpdyGet(kExtraAuthorizationHeaders, |
- arraysize(kExtraAuthorizationHeaders) / 2, |
- false, 3, LOWEST, true)); |
- MockWrite spdy_writes[] = { |
- CreateMockWrite(*req_get, 1), |
- CreateMockWrite(*req_get_authorization, 4), |
- }; |
+ ScopedVector<UploadElementReader> element_readers; |
+ for (int i = 0; i < kFrameCount; ++i) { |
+ element_readers.push_back( |
+ new UploadBytesElementReader(content->c_str(), content->size())); |
+ } |
+ UploadDataStream upload_data_stream(&element_readers, 0); |
- // The first response is a 401 authentication challenge, and the second |
- // response will be a 200 response since the second request includes a valid |
- // Authorization header. |
- const char* const kExtraAuthenticationHeaders[] = { |
- "www-authenticate", |
- "Basic realm=\"MyRealm\"" |
- }; |
- scoped_ptr<SpdyFrame> resp_authentication( |
- spdy_util_.ConstructSpdySynReplyError( |
- "401 Authentication Required", |
- kExtraAuthenticationHeaders, |
- arraysize(kExtraAuthenticationHeaders) / 2, |
- 1)); |
- scoped_ptr<SpdyFrame> body_authentication( |
- spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- scoped_ptr<SpdyFrame> resp_data( |
- spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3)); |
- scoped_ptr<SpdyFrame> body_data(spdy_util_.ConstructSpdyBodyFrame(3, true)); |
- MockRead spdy_reads[] = { |
- CreateMockRead(*resp_authentication, 2), |
- CreateMockRead(*body_authentication, 3), |
- CreateMockRead(*resp_data, 5), |
- CreateMockRead(*body_data, 6), |
- MockRead(ASYNC, 0, 7), |
- }; |
+ // Setup the request |
+ HttpRequestInfo request; |
+ request.method = "POST"; |
+ request.url = GURL(kDefaultURL); |
+ request.upload_data_stream = &upload_data_stream; |
- OrderedSocketData data(spdy_reads, arraysize(spdy_reads), |
- spdy_writes, arraysize(spdy_writes)); |
- HttpRequestInfo request(CreateGetRequest()); |
- BoundNetLog net_log; |
NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY, |
- net_log, GetParam(), NULL); |
- |
+ BoundNetLog(), GetParam(), NULL); |
+ helper.SetDeterministic(); |
+ helper.AddDeterministicData(&data); |
helper.RunPreTestSetup(); |
- helper.AddData(&data); |
+ |
HttpNetworkTransaction* trans = helper.trans(); |
- TestCompletionCallback callback; |
- const int rv_start = trans->Start(&request, callback.callback(), net_log); |
- EXPECT_EQ(ERR_IO_PENDING, rv_start); |
- const int rv_start_complete = callback.WaitForResult(); |
- EXPECT_EQ(OK, rv_start_complete); |
- // Make sure the response has an auth challenge. |
- const HttpResponseInfo* const response_start = trans->GetResponseInfo(); |
- ASSERT_TRUE(response_start != NULL); |
- ASSERT_TRUE(response_start->headers.get() != NULL); |
- EXPECT_EQ(401, response_start->headers->response_code()); |
- EXPECT_TRUE(response_start->was_fetched_via_spdy); |
- AuthChallengeInfo* auth_challenge = response_start->auth_challenge.get(); |
- ASSERT_TRUE(auth_challenge != NULL); |
- EXPECT_FALSE(auth_challenge->is_proxy); |
- EXPECT_EQ("basic", auth_challenge->scheme); |
- EXPECT_EQ("MyRealm", auth_challenge->realm); |
+ TestCompletionCallback callback; |
+ int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); |
- // Restart with a username/password. |
- AuthCredentials credentials(ASCIIToUTF16("foo"), ASCIIToUTF16("bar")); |
- TestCompletionCallback callback_restart; |
- const int rv_restart = trans->RestartWithAuth( |
- credentials, callback_restart.callback()); |
- EXPECT_EQ(ERR_IO_PENDING, rv_restart); |
- const int rv_restart_complete = callback_restart.WaitForResult(); |
- EXPECT_EQ(OK, rv_restart_complete); |
- // TODO(cbentzel): This is actually the same response object as before, but |
- // data has changed. |
- const HttpResponseInfo* const response_restart = trans->GetResponseInfo(); |
- ASSERT_TRUE(response_restart != NULL); |
- ASSERT_TRUE(response_restart->headers.get() != NULL); |
- EXPECT_EQ(200, response_restart->headers->response_code()); |
- EXPECT_TRUE(response_restart->auth_challenge.get() == NULL); |
-} |
+ EXPECT_EQ(ERR_IO_PENDING, rv); |
-TEST_P(SpdyNetworkTransactionTest, ServerPushWithHeaders) { |
- scoped_ptr<SpdyFrame> stream1_syn( |
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- scoped_ptr<SpdyFrame> stream1_body( |
- spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- MockWrite writes[] = { |
- CreateMockWrite(*stream1_syn, 1), |
- }; |
+ data.RunFor(11); |
- scoped_ptr<SpdyHeaderBlock> initial_headers(new SpdyHeaderBlock()); |
- spdy_util_.AddUrlToHeaderBlock( |
- "http://www.google.com/foo.dat", initial_headers.get()); |
- scoped_ptr<SpdyFrame> stream2_syn( |
- spdy_util_.ConstructSpdyControlFrame(initial_headers.Pass(), |
- false, |
- 2, |
- LOWEST, |
- SYN_STREAM, |
- CONTROL_FLAG_NONE, |
- 1)); |
+ SpdyHttpStream* stream = static_cast<SpdyHttpStream*>(trans->stream_.get()); |
+ ASSERT_TRUE(stream != NULL); |
+ ASSERT_TRUE(stream->stream() != NULL); |
+ EXPECT_EQ(static_cast<int>(kSpdyStreamInitialWindowSize) + |
+ kDeltaWindowSize * kDeltaCount - |
+ kMaxSpdyFrameChunkSize * kFrameCount, |
+ stream->stream()->send_window_size()); |
- scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock()); |
- (*late_headers)["hello"] = "bye"; |
- (*late_headers)[spdy_util_.GetStatusKey()] = "200"; |
- (*late_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1"; |
- scoped_ptr<SpdyFrame> stream2_headers( |
- spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(), |
- false, |
- 2, |
- LOWEST, |
- HEADERS, |
- CONTROL_FLAG_NONE, |
- 0)); |
+ data.RunFor(1); |
- scoped_ptr<SpdyFrame> |
- stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- const char kPushedData[] = "pushed"; |
- scoped_ptr<SpdyFrame> stream2_body( |
- spdy_util_.ConstructSpdyBodyFrame( |
- 2, kPushedData, strlen(kPushedData), true)); |
- MockRead reads[] = { |
- CreateMockRead(*stream1_reply, 2), |
- CreateMockRead(*stream2_syn, 3), |
- CreateMockRead(*stream2_headers, 4), |
- CreateMockRead(*stream1_body, 5, SYNCHRONOUS), |
- CreateMockRead(*stream2_body, 5), |
- MockRead(ASYNC, ERR_IO_PENDING, 7), // Force a pause |
- }; |
+ rv = callback.WaitForResult(); |
+ EXPECT_EQ(OK, rv); |
- HttpResponseInfo response; |
- HttpResponseInfo response2; |
- std::string expected_push_result("pushed"); |
- OrderedSocketData data(reads, arraysize(reads), |
- writes, arraysize(writes)); |
- RunServerPushTest(&data, |
- &response, |
- &response2, |
- expected_push_result); |
+ helper.VerifyDataConsumed(); |
+} |
- // Verify the SYN_REPLY. |
- EXPECT_TRUE(response.headers.get() != NULL); |
- EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
+// Test that received data frames and sent WINDOW_UPDATE frames change |
+// the recv_window_size_ correctly. |
+TEST_P(SpdyNetworkTransactionTest, WindowUpdateSent) { |
+ if (GetParam().protocol < kProtoSPDY3) |
+ return; |
- // Verify the pushed stream. |
- EXPECT_TRUE(response2.headers.get() != NULL); |
- EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); |
-} |
+ // Set the data in the body frame large enough to trigger sending a |
+ // WINDOW_UPDATE by the stream. |
+ const std::string body_data(kSpdyStreamInitialWindowSize / 2 + 1, 'x'); |
-TEST_P(SpdyNetworkTransactionTest, ServerPushClaimBeforeHeaders) { |
- // We push a stream and attempt to claim it before the headers come down. |
- scoped_ptr<SpdyFrame> stream1_syn( |
+ scoped_ptr<SpdyFrame> req( |
spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- scoped_ptr<SpdyFrame> stream1_body( |
- spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- MockWrite writes[] = { |
- CreateMockWrite(*stream1_syn, 0, SYNCHRONOUS), |
- }; |
- |
- scoped_ptr<SpdyHeaderBlock> initial_headers(new SpdyHeaderBlock()); |
- spdy_util_.AddUrlToHeaderBlock( |
- "http://www.google.com/foo.dat", initial_headers.get()); |
- scoped_ptr<SpdyFrame> stream2_syn( |
- spdy_util_.ConstructSpdyControlFrame(initial_headers.Pass(), |
- false, |
- 2, |
- LOWEST, |
- SYN_STREAM, |
- CONTROL_FLAG_NONE, |
- 1)); |
+ scoped_ptr<SpdyFrame> session_window_update( |
+ spdy_util_.ConstructSpdyWindowUpdate(0, body_data.size())); |
+ scoped_ptr<SpdyFrame> window_update( |
+ spdy_util_.ConstructSpdyWindowUpdate(1, body_data.size())); |
- scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock()); |
- (*late_headers)["hello"] = "bye"; |
- (*late_headers)[spdy_util_.GetStatusKey()] = "200"; |
- (*late_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1"; |
- scoped_ptr<SpdyFrame> stream2_headers( |
- spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(), |
- false, |
- 2, |
- LOWEST, |
- HEADERS, |
- CONTROL_FLAG_NONE, |
- 0)); |
+ std::vector<MockWrite> writes; |
+ writes.push_back(CreateMockWrite(*req)); |
+ if (GetParam().protocol >= kProtoSPDY31) |
+ writes.push_back(CreateMockWrite(*session_window_update)); |
+ writes.push_back(CreateMockWrite(*window_update)); |
- scoped_ptr<SpdyFrame> |
- stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- const char kPushedData[] = "pushed"; |
- scoped_ptr<SpdyFrame> stream2_body( |
+ scoped_ptr<SpdyFrame> resp( |
+ spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
+ scoped_ptr<SpdyFrame> body_no_fin( |
spdy_util_.ConstructSpdyBodyFrame( |
- 2, kPushedData, strlen(kPushedData), true)); |
+ 1, body_data.data(), body_data.size(), false)); |
+ scoped_ptr<SpdyFrame> body_fin( |
+ spdy_util_.ConstructSpdyBodyFrame(1, NULL, 0, true)); |
MockRead reads[] = { |
- CreateMockRead(*stream1_reply, 1), |
- CreateMockRead(*stream2_syn, 2), |
- CreateMockRead(*stream1_body, 3), |
- CreateMockRead(*stream2_headers, 4), |
- CreateMockRead(*stream2_body, 5), |
- MockRead(ASYNC, 0, 6), // EOF |
+ CreateMockRead(*resp), |
+ CreateMockRead(*body_no_fin), |
+ MockRead(ASYNC, ERR_IO_PENDING, 0), // Force a pause |
+ CreateMockRead(*body_fin), |
+ MockRead(ASYNC, ERR_IO_PENDING, 0), // Force a pause |
+ MockRead(ASYNC, 0, 0) // EOF |
}; |
- HttpResponseInfo response; |
- HttpResponseInfo response2; |
- std::string expected_push_result("pushed"); |
- DeterministicSocketData data(reads, arraysize(reads), |
- writes, arraysize(writes)); |
+ DelayedSocketData data(1, reads, arraysize(reads), |
+ vector_as_array(&writes), writes.size()); |
NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
BoundNetLog(), GetParam(), NULL); |
- helper.SetDeterministic(); |
- helper.AddDeterministicData(&data); |
+ helper.AddData(&data); |
helper.RunPreTestSetup(); |
- |
HttpNetworkTransaction* trans = helper.trans(); |
- // Run until we've received the primary SYN_STREAM, the pushed SYN_STREAM, |
- // and the body of the primary stream, but before we've received the HEADERS |
- // for the pushed stream. |
- data.SetStop(3); |
- |
- // Start the transaction. |
TestCompletionCallback callback; |
- int rv = trans->Start( |
- &CreateGetRequest(), callback.callback(), BoundNetLog()); |
- EXPECT_EQ(ERR_IO_PENDING, rv); |
- data.Run(); |
- rv = callback.WaitForResult(); |
- EXPECT_EQ(0, rv); |
+ int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); |
- // Request the pushed path. At this point, we've received the push, but the |
- // headers are not yet complete. |
- scoped_ptr<HttpNetworkTransaction> trans2( |
- new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); |
- rv = trans2->Start( |
- &CreateGetPushRequest(), callback.callback(), BoundNetLog()); |
EXPECT_EQ(ERR_IO_PENDING, rv); |
- data.RunFor(3); |
- base::MessageLoop::current()->RunUntilIdle(); |
+ rv = callback.WaitForResult(); |
+ EXPECT_EQ(OK, rv); |
- // Read the server push body. |
- std::string result2; |
- ReadResult(trans2.get(), &data, &result2); |
- // Read the response body. |
- std::string result; |
- ReadResult(trans, &data, &result); |
+ SpdyHttpStream* stream = |
+ static_cast<SpdyHttpStream*>(trans->stream_.get()); |
+ ASSERT_TRUE(stream != NULL); |
+ ASSERT_TRUE(stream->stream() != NULL); |
- // Verify that the received push data is same as the expected push data. |
- EXPECT_EQ(result2.compare(expected_push_result), 0) |
- << "Received data: " |
- << result2 |
- << "||||| Expected data: " |
- << expected_push_result; |
+ EXPECT_EQ( |
+ static_cast<int>(kSpdyStreamInitialWindowSize - body_data.size()), |
+ stream->stream()->recv_window_size()); |
- // Verify the SYN_REPLY. |
- // Copy the response info, because trans goes away. |
- response = *trans->GetResponseInfo(); |
- response2 = *trans2->GetResponseInfo(); |
+ const HttpResponseInfo* response = trans->GetResponseInfo(); |
+ ASSERT_TRUE(response != NULL); |
+ ASSERT_TRUE(response->headers.get() != NULL); |
+ EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); |
+ EXPECT_TRUE(response->was_fetched_via_spdy); |
- VerifyStreamsClosed(helper); |
+ // Issue a read which will cause a WINDOW_UPDATE to be sent and window |
+ // size increased to default. |
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(body_data.size())); |
+ rv = trans->Read(buf.get(), body_data.size(), CompletionCallback()); |
+ EXPECT_EQ(static_cast<int>(body_data.size()), rv); |
+ std::string content(buf->data(), buf->data() + body_data.size()); |
+ EXPECT_EQ(body_data, content); |
- // Verify the SYN_REPLY. |
- EXPECT_TRUE(response.headers.get() != NULL); |
- EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
+ // Schedule the reading of empty data frame with FIN |
+ data.CompleteRead(); |
- // Verify the pushed stream. |
- EXPECT_TRUE(response2.headers.get() != NULL); |
- EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); |
+ // Force write of WINDOW_UPDATE which was scheduled during the above |
+ // read. |
+ base::MessageLoop::current()->RunUntilIdle(); |
- // Read the final EOF (which will close the session) |
- data.RunFor(1); |
+ // Read EOF. |
+ data.CompleteRead(); |
- // Verify that we consumed all test data. |
- EXPECT_TRUE(data.at_read_eof()); |
- EXPECT_TRUE(data.at_write_eof()); |
+ helper.VerifyDataConsumed(); |
} |
-TEST_P(SpdyNetworkTransactionTest, ServerPushWithTwoHeaderFrames) { |
- // We push a stream and attempt to claim it before the headers come down. |
- scoped_ptr<SpdyFrame> stream1_syn( |
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- scoped_ptr<SpdyFrame> stream1_body( |
- spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- MockWrite writes[] = { |
- CreateMockWrite(*stream1_syn, 0, SYNCHRONOUS), |
- }; |
+// Test that WINDOW_UPDATE frame causing overflow is handled correctly. |
+TEST_P(SpdyNetworkTransactionTest, WindowUpdateOverflow) { |
+ if (GetParam().protocol < kProtoSPDY3) |
+ return; |
- scoped_ptr<SpdyHeaderBlock> initial_headers(new SpdyHeaderBlock()); |
- spdy_util_.AddUrlToHeaderBlock( |
- "http://www.google.com/foo.dat", initial_headers.get()); |
- scoped_ptr<SpdyFrame> stream2_syn( |
- spdy_util_.ConstructSpdyControlFrame(initial_headers.Pass(), |
- false, |
- 2, |
- LOWEST, |
- SYN_STREAM, |
- CONTROL_FLAG_NONE, |
- 1)); |
+ // Number of full frames we hope to write (but will not, used to |
+ // set content-length header correctly) |
+ static int kFrameCount = 3; |
- scoped_ptr<SpdyHeaderBlock> middle_headers(new SpdyHeaderBlock()); |
- (*middle_headers)["hello"] = "bye"; |
- scoped_ptr<SpdyFrame> stream2_headers1( |
- spdy_util_.ConstructSpdyControlFrame(middle_headers.Pass(), |
- false, |
- 2, |
- LOWEST, |
- HEADERS, |
- CONTROL_FLAG_NONE, |
- 0)); |
+ scoped_ptr<std::string> content( |
+ new std::string(kMaxSpdyFrameChunkSize, 'a')); |
+ scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyPost( |
+ kRequestUrl, 1, kMaxSpdyFrameChunkSize * kFrameCount, LOWEST, NULL, 0)); |
+ scoped_ptr<SpdyFrame> body( |
+ spdy_util_.ConstructSpdyBodyFrame( |
+ 1, content->c_str(), content->size(), false)); |
+ scoped_ptr<SpdyFrame> rst( |
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_FLOW_CONTROL_ERROR)); |
- scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock()); |
- (*late_headers)[spdy_util_.GetStatusKey()] = "200"; |
- (*late_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1"; |
- scoped_ptr<SpdyFrame> stream2_headers2( |
- spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(), |
- false, |
- 2, |
- LOWEST, |
- HEADERS, |
- CONTROL_FLAG_NONE, |
- 0)); |
+ // We're not going to write a data frame with FIN, we'll receive a bad |
+ // WINDOW_UPDATE while sending a request and will send a RST_STREAM frame. |
+ MockWrite writes[] = { |
+ CreateMockWrite(*req, 0), |
+ CreateMockWrite(*body, 2), |
+ CreateMockWrite(*rst, 3), |
+ }; |
- scoped_ptr<SpdyFrame> |
- stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- const char kPushedData[] = "pushed"; |
- scoped_ptr<SpdyFrame> stream2_body( |
- spdy_util_.ConstructSpdyBodyFrame( |
- 2, kPushedData, strlen(kPushedData), true)); |
+ static const int32 kDeltaWindowSize = 0x7fffffff; // cause an overflow |
+ scoped_ptr<SpdyFrame> window_update( |
+ spdy_util_.ConstructSpdyWindowUpdate(1, kDeltaWindowSize)); |
MockRead reads[] = { |
- CreateMockRead(*stream1_reply, 1), |
- CreateMockRead(*stream2_syn, 2), |
- CreateMockRead(*stream1_body, 3), |
- CreateMockRead(*stream2_headers1, 4), |
- CreateMockRead(*stream2_headers2, 5), |
- CreateMockRead(*stream2_body, 6), |
- MockRead(ASYNC, 0, 7), // EOF |
+ CreateMockRead(*window_update, 1), |
+ MockRead(ASYNC, 0, 4) // EOF |
}; |
- HttpResponseInfo response; |
- HttpResponseInfo response2; |
- std::string expected_push_result("pushed"); |
DeterministicSocketData data(reads, arraysize(reads), |
writes, arraysize(writes)); |
- NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
+ ScopedVector<UploadElementReader> element_readers; |
+ for (int i = 0; i < kFrameCount; ++i) { |
+ element_readers.push_back( |
+ new UploadBytesElementReader(content->c_str(), content->size())); |
+ } |
+ UploadDataStream upload_data_stream(&element_readers, 0); |
+ |
+ // Setup the request |
+ HttpRequestInfo request; |
+ request.method = "POST"; |
+ request.url = GURL("http://www.google.com/"); |
+ request.upload_data_stream = &upload_data_stream; |
+ |
+ NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY, |
BoundNetLog(), GetParam(), NULL); |
helper.SetDeterministic(); |
- helper.AddDeterministicData(&data); |
helper.RunPreTestSetup(); |
- |
+ helper.AddDeterministicData(&data); |
HttpNetworkTransaction* trans = helper.trans(); |
- // Run until we've received the primary SYN_STREAM, the pushed SYN_STREAM, |
- // the first HEADERS frame, and the body of the primary stream, but before |
- // we've received the final HEADERS for the pushed stream. |
- data.SetStop(4); |
- |
- // Start the transaction. |
TestCompletionCallback callback; |
- int rv = trans->Start( |
- &CreateGetRequest(), callback.callback(), BoundNetLog()); |
- EXPECT_EQ(ERR_IO_PENDING, rv); |
- data.Run(); |
- rv = callback.WaitForResult(); |
- EXPECT_EQ(0, rv); |
- |
- // Request the pushed path. At this point, we've received the push, but the |
- // headers are not yet complete. |
- scoped_ptr<HttpNetworkTransaction> trans2( |
- new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); |
- rv = trans2->Start( |
- &CreateGetPushRequest(), callback.callback(), BoundNetLog()); |
- EXPECT_EQ(ERR_IO_PENDING, rv); |
- data.RunFor(3); |
- base::MessageLoop::current()->RunUntilIdle(); |
- |
- // Read the server push body. |
- std::string result2; |
- ReadResult(trans2.get(), &data, &result2); |
- // Read the response body. |
- std::string result; |
- ReadResult(trans, &data, &result); |
+ int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); |
+ ASSERT_EQ(ERR_IO_PENDING, rv); |
- // Verify that the received push data is same as the expected push data. |
- EXPECT_EQ(expected_push_result, result2); |
+ data.RunFor(5); |
+ ASSERT_TRUE(callback.have_result()); |
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, callback.WaitForResult()); |
+ helper.VerifyDataConsumed(); |
+} |
- // Verify the SYN_REPLY. |
- // Copy the response info, because trans goes away. |
- response = *trans->GetResponseInfo(); |
- response2 = *trans2->GetResponseInfo(); |
+// Test that after hitting a send window size of 0, the write process |
+// stalls and upon receiving WINDOW_UPDATE frame write resumes. |
- VerifyStreamsClosed(helper); |
+// This test constructs a POST request followed by enough data frames |
+// containing 'a' that would make the window size 0, followed by another |
+// data frame containing default content (which is "hello!") and this frame |
+// also contains a FIN flag. DelayedSocketData is used to enforce all |
+// writes go through before a read could happen. However, the last frame |
+// ("hello!") is not supposed to go through since by the time its turn |
+// arrives, window size is 0. At this point MessageLoop::Run() called via |
+// callback would block. Therefore we call MessageLoop::RunUntilIdle() |
+// which returns after performing all possible writes. We use DCHECKS to |
+// ensure that last data frame is still there and stream has stalled. |
+// After that, next read is artifically enforced, which causes a |
+// WINDOW_UPDATE to be read and I/O process resumes. |
+TEST_P(SpdyNetworkTransactionTest, FlowControlStallResume) { |
+ if (GetParam().protocol < kProtoSPDY3) |
+ return; |
- // Verify the SYN_REPLY. |
- EXPECT_TRUE(response.headers.get() != NULL); |
- EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
+ // Number of frames we need to send to zero out the window size: data |
+ // frames plus SYN_STREAM plus the last data frame; also we need another |
+ // data frame that we will send once the WINDOW_UPDATE is received, |
+ // therefore +3. |
+ size_t num_writes = kSpdyStreamInitialWindowSize / kMaxSpdyFrameChunkSize + 3; |
- // Verify the pushed stream. |
- EXPECT_TRUE(response2.headers.get() != NULL); |
- EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); |
+ // Calculate last frame's size; 0 size data frame is legal. |
+ size_t last_frame_size = |
+ kSpdyStreamInitialWindowSize % kMaxSpdyFrameChunkSize; |
- // Verify we got all the headers |
- if (spdy_util_.spdy_version() < SPDY3) { |
- EXPECT_TRUE(response2.headers->HasHeaderValue( |
- "url", |
- "http://www.google.com/foo.dat")); |
- } else { |
- EXPECT_TRUE(response2.headers->HasHeaderValue( |
- "scheme", "http")); |
- EXPECT_TRUE(response2.headers->HasHeaderValue( |
- "host", "www.google.com")); |
- EXPECT_TRUE(response2.headers->HasHeaderValue( |
- "path", "/foo.dat")); |
- } |
- EXPECT_TRUE(response2.headers->HasHeaderValue("hello", "bye")); |
- EXPECT_TRUE(response2.headers->HasHeaderValue("status", "200")); |
- EXPECT_TRUE(response2.headers->HasHeaderValue("version", "HTTP/1.1")); |
+ // Construct content for a data frame of maximum size. |
+ std::string content(kMaxSpdyFrameChunkSize, 'a'); |
- // Read the final EOF (which will close the session) |
- data.RunFor(1); |
+ scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyPost( |
+ kRequestUrl, 1, kSpdyStreamInitialWindowSize + kUploadDataSize, |
+ LOWEST, NULL, 0)); |
- // Verify that we consumed all test data. |
- EXPECT_TRUE(data.at_read_eof()); |
- EXPECT_TRUE(data.at_write_eof()); |
-} |
+ // Full frames. |
+ scoped_ptr<SpdyFrame> body1( |
+ spdy_util_.ConstructSpdyBodyFrame( |
+ 1, content.c_str(), content.size(), false)); |
-TEST_P(SpdyNetworkTransactionTest, ServerPushWithNoStatusHeaderFrames) { |
- // We push a stream and attempt to claim it before the headers come down. |
- scoped_ptr<SpdyFrame> stream1_syn( |
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- scoped_ptr<SpdyFrame> stream1_body( |
- spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- MockWrite writes[] = { |
- CreateMockWrite(*stream1_syn, 0, SYNCHRONOUS), |
- }; |
+ // Last frame to zero out the window size. |
+ scoped_ptr<SpdyFrame> body2( |
+ spdy_util_.ConstructSpdyBodyFrame( |
+ 1, content.c_str(), last_frame_size, false)); |
- scoped_ptr<SpdyHeaderBlock> initial_headers(new SpdyHeaderBlock()); |
- spdy_util_.AddUrlToHeaderBlock( |
- "http://www.google.com/foo.dat", initial_headers.get()); |
- scoped_ptr<SpdyFrame> stream2_syn( |
- spdy_util_.ConstructSpdyControlFrame(initial_headers.Pass(), |
- false, |
- 2, |
- LOWEST, |
- SYN_STREAM, |
- CONTROL_FLAG_NONE, |
- 1)); |
+ // Data frame to be sent once WINDOW_UPDATE frame is received. |
+ scoped_ptr<SpdyFrame> body3(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- scoped_ptr<SpdyHeaderBlock> middle_headers(new SpdyHeaderBlock()); |
- (*middle_headers)["hello"] = "bye"; |
- scoped_ptr<SpdyFrame> stream2_headers1( |
- spdy_util_.ConstructSpdyControlFrame(middle_headers.Pass(), |
- false, |
- 2, |
- LOWEST, |
- HEADERS, |
- CONTROL_FLAG_NONE, |
- 0)); |
+ // Fill in mock writes. |
+ scoped_ptr<MockWrite[]> writes(new MockWrite[num_writes]); |
+ size_t i = 0; |
+ writes[i] = CreateMockWrite(*req); |
+ for (i = 1; i < num_writes - 2; i++) |
+ writes[i] = CreateMockWrite(*body1); |
+ writes[i++] = CreateMockWrite(*body2); |
+ writes[i] = CreateMockWrite(*body3); |
- scoped_ptr<SpdyFrame> |
- stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- const char kPushedData[] = "pushed"; |
- scoped_ptr<SpdyFrame> stream2_body( |
- spdy_util_.ConstructSpdyBodyFrame( |
- 2, kPushedData, strlen(kPushedData), true)); |
+ // Construct read frame, give enough space to upload the rest of the |
+ // data. |
+ scoped_ptr<SpdyFrame> session_window_update( |
+ spdy_util_.ConstructSpdyWindowUpdate(0, kUploadDataSize)); |
+ scoped_ptr<SpdyFrame> window_update( |
+ spdy_util_.ConstructSpdyWindowUpdate(1, kUploadDataSize)); |
+ scoped_ptr<SpdyFrame> reply(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); |
MockRead reads[] = { |
- CreateMockRead(*stream1_reply, 1), |
- CreateMockRead(*stream2_syn, 2), |
- CreateMockRead(*stream1_body, 3), |
- CreateMockRead(*stream2_headers1, 4), |
- CreateMockRead(*stream2_body, 5), |
- MockRead(ASYNC, 0, 6), // EOF |
+ CreateMockRead(*session_window_update), |
+ CreateMockRead(*session_window_update), |
+ CreateMockRead(*window_update), |
+ CreateMockRead(*window_update), |
+ CreateMockRead(*reply), |
+ CreateMockRead(*body2), |
+ CreateMockRead(*body3), |
+ MockRead(ASYNC, 0, 0) // EOF |
}; |
- DeterministicSocketData data(reads, arraysize(reads), |
- writes, arraysize(writes)); |
+ // Skip the session window updates unless we're using SPDY/3.1 and |
+ // above. |
+ size_t read_offset = (GetParam().protocol >= kProtoSPDY31) ? 0 : 2; |
+ size_t num_reads = arraysize(reads) - read_offset; |
- NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
+ // Force all writes to happen before any read, last write will not |
+ // actually queue a frame, due to window size being 0. |
+ DelayedSocketData data(num_writes, reads + read_offset, num_reads, |
+ writes.get(), num_writes); |
+ |
+ ScopedVector<UploadElementReader> element_readers; |
+ std::string upload_data_string(kSpdyStreamInitialWindowSize, 'a'); |
+ upload_data_string.append(kUploadData, kUploadDataSize); |
+ element_readers.push_back(new UploadBytesElementReader( |
+ upload_data_string.c_str(), upload_data_string.size())); |
+ UploadDataStream upload_data_stream(&element_readers, 0); |
+ |
+ HttpRequestInfo request; |
+ request.method = "POST"; |
+ request.url = GURL("http://www.google.com/"); |
+ request.upload_data_stream = &upload_data_stream; |
+ NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY, |
BoundNetLog(), GetParam(), NULL); |
- helper.SetDeterministic(); |
- helper.AddDeterministicData(&data); |
+ helper.AddData(&data); |
helper.RunPreTestSetup(); |
HttpNetworkTransaction* trans = helper.trans(); |
- // Run until we've received the primary SYN_STREAM, the pushed SYN_STREAM, |
- // the first HEADERS frame, and the body of the primary stream, but before |
- // we've received the final HEADERS for the pushed stream. |
- data.SetStop(4); |
- |
- // Start the transaction. |
TestCompletionCallback callback; |
- int rv = trans->Start( |
- &CreateGetRequest(), callback.callback(), BoundNetLog()); |
- EXPECT_EQ(ERR_IO_PENDING, rv); |
- data.Run(); |
- rv = callback.WaitForResult(); |
- EXPECT_EQ(0, rv); |
- |
- // Request the pushed path. At this point, we've received the push, but the |
- // headers are not yet complete. |
- scoped_ptr<HttpNetworkTransaction> trans2( |
- new HttpNetworkTransaction(DEFAULT_PRIORITY, helper.session().get())); |
- rv = trans2->Start( |
- &CreateGetPushRequest(), callback.callback(), BoundNetLog()); |
- EXPECT_EQ(ERR_IO_PENDING, rv); |
- data.RunFor(2); |
- base::MessageLoop::current()->RunUntilIdle(); |
- |
- // Read the server push body. |
- std::string result2; |
- ReadResult(trans2.get(), &data, &result2); |
- // Read the response body. |
- std::string result; |
- ReadResult(trans, &data, &result); |
- EXPECT_EQ("hello!", result); |
- |
- // Verify that we haven't received any push data. |
- EXPECT_EQ("", result2); |
- |
- // Verify the SYN_REPLY. |
- // Copy the response info, because trans goes away. |
- HttpResponseInfo response = *trans->GetResponseInfo(); |
- ASSERT_TRUE(trans2->GetResponseInfo() == NULL); |
- |
- VerifyStreamsClosed(helper); |
- |
- // Verify the SYN_REPLY. |
- EXPECT_TRUE(response.headers.get() != NULL); |
- EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
+ int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); |
+ EXPECT_EQ(ERR_IO_PENDING, rv); |
- // Read the final EOF (which will close the session). |
- data.RunFor(1); |
+ base::MessageLoop::current()->RunUntilIdle(); // Write as much as we can. |
- // Verify that we consumed all test data. |
- EXPECT_TRUE(data.at_read_eof()); |
- EXPECT_TRUE(data.at_write_eof()); |
+ SpdyHttpStream* stream = static_cast<SpdyHttpStream*>(trans->stream_.get()); |
+ ASSERT_TRUE(stream != NULL); |
+ ASSERT_TRUE(stream->stream() != NULL); |
+ EXPECT_EQ(0, stream->stream()->send_window_size()); |
+ // All the body data should have been read. |
+ // TODO(satorux): This is because of the weirdness in reading the request |
+ // body in OnSendBodyComplete(). See crbug.com/113107. |
+ EXPECT_TRUE(upload_data_stream.IsEOF()); |
+ // But the body is not yet fully sent (kUploadData is not yet sent) |
+ // since we're send-stalled. |
+ EXPECT_TRUE(stream->stream()->send_stalled_by_flow_control()); |
+ |
+ data.ForceNextRead(); // Read in WINDOW_UPDATE frame. |
+ rv = callback.WaitForResult(); |
+ helper.VerifyDataConsumed(); |
} |
-TEST_P(SpdyNetworkTransactionTest, SynReplyWithHeaders) { |
- scoped_ptr<SpdyFrame> req( |
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- scoped_ptr<SpdyFrame> rst( |
- spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR)); |
- MockWrite writes[] = { |
- CreateMockWrite(*req), |
- CreateMockWrite(*rst), |
- }; |
+// Test we correctly handle the case where the SETTINGS frame results in |
+// unstalling the send window. |
+TEST_P(SpdyNetworkTransactionTest, FlowControlStallResumeAfterSettings) { |
+ if (GetParam().protocol < kProtoSPDY3) |
+ return; |
- scoped_ptr<SpdyHeaderBlock> initial_headers(new SpdyHeaderBlock()); |
- (*initial_headers)[spdy_util_.GetStatusKey()] = "200 OK"; |
- (*initial_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1"; |
- scoped_ptr<SpdyFrame> stream1_reply( |
- spdy_util_.ConstructSpdyControlFrame(initial_headers.Pass(), |
- false, |
- 1, |
- LOWEST, |
- SYN_REPLY, |
- CONTROL_FLAG_NONE, |
- 0)); |
+ // Number of frames we need to send to zero out the window size: data |
+ // frames plus SYN_STREAM plus the last data frame; also we need another |
+ // data frame that we will send once the SETTING is received, therefore +3. |
+ size_t num_writes = kSpdyStreamInitialWindowSize / kMaxSpdyFrameChunkSize + 3; |
- scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock()); |
- (*late_headers)["hello"] = "bye"; |
- scoped_ptr<SpdyFrame> stream1_headers( |
- spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(), |
- false, |
- 1, |
- LOWEST, |
- HEADERS, |
- CONTROL_FLAG_NONE, |
- 0)); |
- scoped_ptr<SpdyFrame> stream1_body( |
- spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- MockRead reads[] = { |
- CreateMockRead(*stream1_reply), |
- CreateMockRead(*stream1_headers), |
- CreateMockRead(*stream1_body), |
- MockRead(ASYNC, 0, 0) // EOF |
- }; |
+ // Calculate last frame's size; 0 size data frame is legal. |
+ size_t last_frame_size = |
+ kSpdyStreamInitialWindowSize % kMaxSpdyFrameChunkSize; |
- DelayedSocketData data(1, reads, arraysize(reads), |
- writes, arraysize(writes)); |
- NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
- BoundNetLog(), GetParam(), NULL); |
- helper.RunToCompletion(&data); |
- TransactionHelperResult out = helper.output(); |
- EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); |
-} |
+ // Construct content for a data frame of maximum size. |
+ std::string content(kMaxSpdyFrameChunkSize, 'a'); |
-TEST_P(SpdyNetworkTransactionTest, SynReplyWithLateHeaders) { |
- scoped_ptr<SpdyFrame> req( |
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- scoped_ptr<SpdyFrame> rst( |
- spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR)); |
- MockWrite writes[] = { |
- CreateMockWrite(*req), |
- CreateMockWrite(*rst), |
- }; |
+ scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyPost( |
+ kRequestUrl, 1, kSpdyStreamInitialWindowSize + kUploadDataSize, |
+ LOWEST, NULL, 0)); |
- scoped_ptr<SpdyHeaderBlock> initial_headers(new SpdyHeaderBlock()); |
- (*initial_headers)[spdy_util_.GetStatusKey()] = "200 OK"; |
- (*initial_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1"; |
- scoped_ptr<SpdyFrame> stream1_reply( |
- spdy_util_.ConstructSpdyControlFrame(initial_headers.Pass(), |
- false, |
- 1, |
- LOWEST, |
- SYN_REPLY, |
- CONTROL_FLAG_NONE, |
- 0)); |
+ // Full frames. |
+ scoped_ptr<SpdyFrame> body1( |
+ spdy_util_.ConstructSpdyBodyFrame( |
+ 1, content.c_str(), content.size(), false)); |
- scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock()); |
- (*late_headers)["hello"] = "bye"; |
- scoped_ptr<SpdyFrame> stream1_headers( |
- spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(), |
- false, |
- 1, |
- LOWEST, |
- HEADERS, |
- CONTROL_FLAG_NONE, |
- 0)); |
- scoped_ptr<SpdyFrame> stream1_body( |
- spdy_util_.ConstructSpdyBodyFrame(1, false)); |
- scoped_ptr<SpdyFrame> stream1_body2( |
- spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- MockRead reads[] = { |
- CreateMockRead(*stream1_reply), |
- CreateMockRead(*stream1_body), |
- CreateMockRead(*stream1_headers), |
- CreateMockRead(*stream1_body2), |
- MockRead(ASYNC, 0, 0) // EOF |
- }; |
+ // Last frame to zero out the window size. |
+ scoped_ptr<SpdyFrame> body2( |
+ spdy_util_.ConstructSpdyBodyFrame( |
+ 1, content.c_str(), last_frame_size, false)); |
- DelayedSocketData data(1, reads, arraysize(reads), |
- writes, arraysize(writes)); |
- NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
- BoundNetLog(), GetParam(), NULL); |
- helper.RunToCompletion(&data); |
- TransactionHelperResult out = helper.output(); |
- EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); |
-} |
+ // Data frame to be sent once SETTINGS frame is received. |
+ scoped_ptr<SpdyFrame> body3(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
-TEST_P(SpdyNetworkTransactionTest, ServerPushCrossOriginCorrectness) { |
- // In this test we want to verify that we can't accidentally push content |
- // which can't be pushed by this content server. |
- // This test assumes that: |
- // - if we're requesting http://www.foo.com/barbaz |
- // - the browser has made a connection to "www.foo.com". |
+ // Fill in mock reads/writes. |
+ std::vector<MockRead> reads; |
+ std::vector<MockWrite> writes; |
+ size_t i = 0; |
+ writes.push_back(CreateMockWrite(*req, i++)); |
+ while (i < num_writes - 2) |
+ writes.push_back(CreateMockWrite(*body1, i++)); |
+ writes.push_back(CreateMockWrite(*body2, i++)); |
- // A list of the URL to fetch, followed by the URL being pushed. |
- static const char* const kTestCases[] = { |
- "http://www.google.com/foo.html", |
- "http://www.google.com:81/foo.js", // Bad port |
+ // Construct read frame for SETTINGS that gives enough space to upload the |
+ // rest of the data. |
+ SettingsMap settings; |
+ settings[SETTINGS_INITIAL_WINDOW_SIZE] = |
+ SettingsFlagsAndValue( |
+ SETTINGS_FLAG_NONE, kSpdyStreamInitialWindowSize * 2); |
+ scoped_ptr<SpdyFrame> settings_frame_large( |
+ spdy_util_.ConstructSpdySettings(settings)); |
- "http://www.google.com/foo.html", |
- "https://www.google.com/foo.js", // Bad protocol |
+ reads.push_back(CreateMockRead(*settings_frame_large, i++)); |
- "http://www.google.com/foo.html", |
- "ftp://www.google.com/foo.js", // Invalid Protocol |
+ scoped_ptr<SpdyFrame> session_window_update( |
+ spdy_util_.ConstructSpdyWindowUpdate(0, kUploadDataSize)); |
+ if (GetParam().protocol >= kProtoSPDY31) |
+ reads.push_back(CreateMockRead(*session_window_update, i++)); |
- "http://www.google.com/foo.html", |
- "http://blat.www.google.com/foo.js", // Cross subdomain |
+ writes.push_back(CreateMockWrite(*body3, i++)); |
- "http://www.google.com/foo.html", |
- "http://www.foo.com/foo.js", // Cross domain |
- }; |
+ scoped_ptr<SpdyFrame> reply(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); |
+ reads.push_back(CreateMockRead(*reply, i++)); |
+ reads.push_back(CreateMockRead(*body2, i++)); |
+ reads.push_back(CreateMockRead(*body3, i++)); |
+ reads.push_back(MockRead(ASYNC, 0, i++)); // EOF |
- for (size_t index = 0; index < arraysize(kTestCases); index += 2) { |
- const char* url_to_fetch = kTestCases[index]; |
- const char* url_to_push = kTestCases[index + 1]; |
+ // Force all writes to happen before any read, last write will not |
+ // actually queue a frame, due to window size being 0. |
+ DeterministicSocketData data(vector_as_array(&reads), reads.size(), |
+ vector_as_array(&writes), writes.size()); |
- scoped_ptr<SpdyFrame> stream1_syn( |
- spdy_util_.ConstructSpdyGet(url_to_fetch, false, 1, LOWEST)); |
- scoped_ptr<SpdyFrame> stream1_body( |
- spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- scoped_ptr<SpdyFrame> push_rst( |
- spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_REFUSED_STREAM)); |
- MockWrite writes[] = { |
- CreateMockWrite(*stream1_syn, 1), |
- CreateMockWrite(*push_rst, 4), |
- }; |
+ ScopedVector<UploadElementReader> element_readers; |
+ std::string upload_data_string(kSpdyStreamInitialWindowSize, 'a'); |
+ upload_data_string.append(kUploadData, kUploadDataSize); |
+ element_readers.push_back(new UploadBytesElementReader( |
+ upload_data_string.c_str(), upload_data_string.size())); |
+ UploadDataStream upload_data_stream(&element_readers, 0); |
- scoped_ptr<SpdyFrame> |
- stream1_reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- scoped_ptr<SpdyFrame> |
- stream2_syn(spdy_util_.ConstructSpdyPush(NULL, |
- 0, |
- 2, |
- 1, |
- url_to_push)); |
- const char kPushedData[] = "pushed"; |
- scoped_ptr<SpdyFrame> stream2_body( |
- spdy_util_.ConstructSpdyBodyFrame( |
- 2, kPushedData, strlen(kPushedData), true)); |
- scoped_ptr<SpdyFrame> rst( |
- spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_CANCEL)); |
+ HttpRequestInfo request; |
+ request.method = "POST"; |
+ request.url = GURL("http://www.google.com/"); |
+ request.upload_data_stream = &upload_data_stream; |
+ NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY, |
+ BoundNetLog(), GetParam(), NULL); |
+ helper.SetDeterministic(); |
+ helper.RunPreTestSetup(); |
+ helper.AddDeterministicData(&data); |
- MockRead reads[] = { |
- CreateMockRead(*stream1_reply, 2), |
- CreateMockRead(*stream2_syn, 3), |
- CreateMockRead(*stream1_body, 5, SYNCHRONOUS), |
- CreateMockRead(*stream2_body, 6), |
- MockRead(ASYNC, ERR_IO_PENDING, 7), // Force a pause |
- }; |
+ HttpNetworkTransaction* trans = helper.trans(); |
- HttpResponseInfo response; |
- OrderedSocketData data(reads, arraysize(reads), |
- writes, arraysize(writes)); |
+ TestCompletionCallback callback; |
+ int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); |
+ EXPECT_EQ(ERR_IO_PENDING, rv); |
- HttpRequestInfo request; |
- request.method = "GET"; |
- request.url = GURL(url_to_fetch); |
- request.load_flags = 0; |
+ data.RunFor(num_writes - 1); // Write as much as we can. |
+ |
+ SpdyHttpStream* stream = static_cast<SpdyHttpStream*>(trans->stream_.get()); |
+ ASSERT_TRUE(stream != NULL); |
+ ASSERT_TRUE(stream->stream() != NULL); |
+ EXPECT_EQ(0, stream->stream()->send_window_size()); |
- // Enable cross-origin push. Since we are not using a proxy, this should |
- // not actually enable cross-origin SPDY push. |
- scoped_ptr<SpdySessionDependencies> session_deps( |
- CreateSpdySessionDependencies(GetParam())); |
- session_deps->trusted_spdy_proxy = "123.45.67.89:8080"; |
- NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY, |
- BoundNetLog(), GetParam(), |
- session_deps.release()); |
- helper.RunPreTestSetup(); |
- helper.AddData(&data); |
+ // All the body data should have been read. |
+ // TODO(satorux): This is because of the weirdness in reading the request |
+ // body in OnSendBodyComplete(). See crbug.com/113107. |
+ EXPECT_TRUE(upload_data_stream.IsEOF()); |
+ // But the body is not yet fully sent (kUploadData is not yet sent) |
+ // since we're send-stalled. |
+ EXPECT_TRUE(stream->stream()->send_stalled_by_flow_control()); |
- HttpNetworkTransaction* trans = helper.trans(); |
+ data.RunFor(6); // Read in SETTINGS frame to unstall. |
+ rv = callback.WaitForResult(); |
+ helper.VerifyDataConsumed(); |
+ // If stream is NULL, that means it was unstalled and closed. |
+ EXPECT_TRUE(stream->stream() == NULL); |
+} |
- // Start the transaction with basic parameters. |
- TestCompletionCallback callback; |
+// Test we correctly handle the case where the SETTINGS frame results in a |
+// negative send window size. |
+TEST_P(SpdyNetworkTransactionTest, FlowControlNegativeSendWindowSize) { |
+ if (GetParam().protocol < kProtoSPDY3) |
+ return; |
- int rv = trans->Start(&request, callback.callback(), BoundNetLog()); |
- EXPECT_EQ(ERR_IO_PENDING, rv); |
- rv = callback.WaitForResult(); |
+ // Number of frames we need to send to zero out the window size: data |
+ // frames plus SYN_STREAM plus the last data frame; also we need another |
+ // data frame that we will send once the SETTING is received, therefore +3. |
+ size_t num_writes = kSpdyStreamInitialWindowSize / kMaxSpdyFrameChunkSize + 3; |
- // Read the response body. |
- std::string result; |
- ReadResult(trans, &data, &result); |
+ // Calculate last frame's size; 0 size data frame is legal. |
+ size_t last_frame_size = |
+ kSpdyStreamInitialWindowSize % kMaxSpdyFrameChunkSize; |
- // Verify that we consumed all test data. |
- EXPECT_TRUE(data.at_read_eof()); |
- EXPECT_TRUE(data.at_write_eof()); |
+ // Construct content for a data frame of maximum size. |
+ std::string content(kMaxSpdyFrameChunkSize, 'a'); |
- // Verify the SYN_REPLY. |
- // Copy the response info, because trans goes away. |
- response = *trans->GetResponseInfo(); |
+ scoped_ptr<SpdyFrame> req(spdy_util_.ConstructSpdyPost( |
+ kRequestUrl, 1, kSpdyStreamInitialWindowSize + kUploadDataSize, |
+ LOWEST, NULL, 0)); |
- VerifyStreamsClosed(helper); |
+ // Full frames. |
+ scoped_ptr<SpdyFrame> body1( |
+ spdy_util_.ConstructSpdyBodyFrame( |
+ 1, content.c_str(), content.size(), false)); |
- // Verify the SYN_REPLY. |
- EXPECT_TRUE(response.headers.get() != NULL); |
- EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
- } |
-} |
+ // Last frame to zero out the window size. |
+ scoped_ptr<SpdyFrame> body2( |
+ spdy_util_.ConstructSpdyBodyFrame( |
+ 1, content.c_str(), last_frame_size, false)); |
-TEST_P(SpdyNetworkTransactionTest, RetryAfterRefused) { |
- // Construct the request. |
- scoped_ptr<SpdyFrame> req( |
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- scoped_ptr<SpdyFrame> req2( |
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, LOWEST, true)); |
- MockWrite writes[] = { |
- CreateMockWrite(*req, 1), |
- CreateMockWrite(*req2, 3), |
- }; |
+ // Data frame to be sent once SETTINGS frame is received. |
+ scoped_ptr<SpdyFrame> body3(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- scoped_ptr<SpdyFrame> refused( |
- spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_REFUSED_STREAM)); |
- scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3)); |
- scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(3, true)); |
- MockRead reads[] = { |
- CreateMockRead(*refused, 2), |
- CreateMockRead(*resp, 4), |
- CreateMockRead(*body, 5), |
- MockRead(ASYNC, 0, 6) // EOF |
- }; |
+ // Fill in mock reads/writes. |
+ std::vector<MockRead> reads; |
+ std::vector<MockWrite> writes; |
+ size_t i = 0; |
+ writes.push_back(CreateMockWrite(*req, i++)); |
+ while (i < num_writes - 2) |
+ writes.push_back(CreateMockWrite(*body1, i++)); |
+ writes.push_back(CreateMockWrite(*body2, i++)); |
- OrderedSocketData data(reads, arraysize(reads), |
- writes, arraysize(writes)); |
- NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY, |
- BoundNetLog(), GetParam(), NULL); |
+ // Construct read frame for SETTINGS that makes the send_window_size |
+ // negative. |
+ SettingsMap new_settings; |
+ new_settings[SETTINGS_INITIAL_WINDOW_SIZE] = |
+ SettingsFlagsAndValue( |
+ SETTINGS_FLAG_NONE, kSpdyStreamInitialWindowSize / 2); |
+ scoped_ptr<SpdyFrame> settings_frame_small( |
+ spdy_util_.ConstructSpdySettings(new_settings)); |
+ // Construct read frames for WINDOW_UPDATE that makes the send_window_size |
+ // positive. |
+ scoped_ptr<SpdyFrame> session_window_update_init_size( |
+ spdy_util_.ConstructSpdyWindowUpdate(0, kSpdyStreamInitialWindowSize)); |
+ scoped_ptr<SpdyFrame> window_update_init_size( |
+ spdy_util_.ConstructSpdyWindowUpdate(1, kSpdyStreamInitialWindowSize)); |
- helper.RunPreTestSetup(); |
- helper.AddData(&data); |
+ reads.push_back(CreateMockRead(*settings_frame_small, i++)); |
- HttpNetworkTransaction* trans = helper.trans(); |
+ if (GetParam().protocol >= kProtoSPDY3) |
+ reads.push_back(CreateMockRead(*session_window_update_init_size, i++)); |
- // Start the transaction with basic parameters. |
- TestCompletionCallback callback; |
- int rv = trans->Start( |
- &CreateGetRequest(), callback.callback(), BoundNetLog()); |
- EXPECT_EQ(ERR_IO_PENDING, rv); |
- rv = callback.WaitForResult(); |
- EXPECT_EQ(OK, rv); |
+ reads.push_back(CreateMockRead(*window_update_init_size, i++)); |
- // Verify that we consumed all test data. |
- EXPECT_TRUE(data.at_read_eof()) << "Read count: " |
- << data.read_count() |
- << " Read index: " |
- << data.read_index(); |
- EXPECT_TRUE(data.at_write_eof()) << "Write count: " |
- << data.write_count() |
- << " Write index: " |
- << data.write_index(); |
+ writes.push_back(CreateMockWrite(*body3, i++)); |
- // Verify the SYN_REPLY. |
- HttpResponseInfo response = *trans->GetResponseInfo(); |
- EXPECT_TRUE(response.headers.get() != NULL); |
- EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); |
-} |
+ scoped_ptr<SpdyFrame> reply(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); |
+ reads.push_back(CreateMockRead(*reply, i++)); |
+ reads.push_back(CreateMockRead(*body2, i++)); |
+ reads.push_back(CreateMockRead(*body3, i++)); |
+ reads.push_back(MockRead(ASYNC, 0, i++)); // EOF |
-TEST_P(SpdyNetworkTransactionTest, OutOfOrderSynStream) { |
- // This first request will start to establish the SpdySession. |
- // Then we will start the second (MEDIUM priority) and then third |
- // (HIGHEST priority) request in such a way that the third will actually |
- // start before the second, causing the second to be numbered differently |
- // than the order they were created. |
- scoped_ptr<SpdyFrame> req1( |
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); |
- scoped_ptr<SpdyFrame> req2( |
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, HIGHEST, true)); |
- scoped_ptr<SpdyFrame> req3( |
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 5, MEDIUM, true)); |
- MockWrite writes[] = { |
- CreateMockWrite(*req1, 0), |
- CreateMockWrite(*req2, 3), |
- CreateMockWrite(*req3, 4), |
- }; |
+ // Force all writes to happen before any read, last write will not |
+ // actually queue a frame, due to window size being 0. |
+ DeterministicSocketData data(vector_as_array(&reads), reads.size(), |
+ vector_as_array(&writes), writes.size()); |
- scoped_ptr<SpdyFrame> resp1(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); |
- scoped_ptr<SpdyFrame> body1(spdy_util_.ConstructSpdyBodyFrame(1, true)); |
- scoped_ptr<SpdyFrame> resp2(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3)); |
- scoped_ptr<SpdyFrame> body2(spdy_util_.ConstructSpdyBodyFrame(3, true)); |
- scoped_ptr<SpdyFrame> resp3(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 5)); |
- scoped_ptr<SpdyFrame> body3(spdy_util_.ConstructSpdyBodyFrame(5, true)); |
- MockRead reads[] = { |
- CreateMockRead(*resp1, 1), |
- CreateMockRead(*body1, 2), |
- CreateMockRead(*resp2, 5), |
- CreateMockRead(*body2, 6), |
- CreateMockRead(*resp3, 7), |
- CreateMockRead(*body3, 8), |
- MockRead(ASYNC, 0, 9) // EOF |
- }; |
+ ScopedVector<UploadElementReader> element_readers; |
+ std::string upload_data_string(kSpdyStreamInitialWindowSize, 'a'); |
+ upload_data_string.append(kUploadData, kUploadDataSize); |
+ element_readers.push_back(new UploadBytesElementReader( |
+ upload_data_string.c_str(), upload_data_string.size())); |
+ UploadDataStream upload_data_stream(&element_readers, 0); |
- DeterministicSocketData data(reads, arraysize(reads), |
- writes, arraysize(writes)); |
- NormalSpdyTransactionHelper helper(CreateGetRequest(), LOWEST, |
+ HttpRequestInfo request; |
+ request.method = "POST"; |
+ request.url = GURL("http://www.google.com/"); |
+ request.upload_data_stream = &upload_data_stream; |
+ NormalSpdyTransactionHelper helper(request, DEFAULT_PRIORITY, |
BoundNetLog(), GetParam(), NULL); |
helper.SetDeterministic(); |
helper.RunPreTestSetup(); |
helper.AddDeterministicData(&data); |
- // Start the first transaction to set up the SpdySession |
HttpNetworkTransaction* trans = helper.trans(); |
- TestCompletionCallback callback; |
- HttpRequestInfo info1 = CreateGetRequest(); |
- int rv = trans->Start(&info1, callback.callback(), BoundNetLog()); |
- EXPECT_EQ(ERR_IO_PENDING, rv); |
- |
- // Run the message loop, but do not allow the write to complete. |
- // This leaves the SpdySession with a write pending, which prevents |
- // SpdySession from attempting subsequent writes until this write completes. |
- base::MessageLoop::current()->RunUntilIdle(); |
- // Now, start both new transactions |
- HttpRequestInfo info2 = CreateGetRequest(); |
- TestCompletionCallback callback2; |
- scoped_ptr<HttpNetworkTransaction> trans2( |
- new HttpNetworkTransaction(MEDIUM, helper.session().get())); |
- rv = trans2->Start(&info2, callback2.callback(), BoundNetLog()); |
+ TestCompletionCallback callback; |
+ int rv = trans->Start(&helper.request(), callback.callback(), BoundNetLog()); |
EXPECT_EQ(ERR_IO_PENDING, rv); |
- base::MessageLoop::current()->RunUntilIdle(); |
- HttpRequestInfo info3 = CreateGetRequest(); |
- TestCompletionCallback callback3; |
- scoped_ptr<HttpNetworkTransaction> trans3( |
- new HttpNetworkTransaction(HIGHEST, helper.session().get())); |
- rv = trans3->Start(&info3, callback3.callback(), BoundNetLog()); |
- EXPECT_EQ(ERR_IO_PENDING, rv); |
- base::MessageLoop::current()->RunUntilIdle(); |
+ data.RunFor(num_writes - 1); // Write as much as we can. |
- // We now have two SYN_STREAM frames queued up which will be |
- // dequeued only once the first write completes, which we |
- // now allow to happen. |
- data.RunFor(2); |
- EXPECT_EQ(OK, callback.WaitForResult()); |
+ SpdyHttpStream* stream = static_cast<SpdyHttpStream*>(trans->stream_.get()); |
+ ASSERT_TRUE(stream != NULL); |
+ ASSERT_TRUE(stream->stream() != NULL); |
+ EXPECT_EQ(0, stream->stream()->send_window_size()); |
- // And now we can allow everything else to run to completion. |
- data.SetStop(10); |
- data.Run(); |
- EXPECT_EQ(OK, callback2.WaitForResult()); |
- EXPECT_EQ(OK, callback3.WaitForResult()); |
+ // All the body data should have been read. |
+ // TODO(satorux): This is because of the weirdness in reading the request |
+ // body in OnSendBodyComplete(). See crbug.com/113107. |
+ EXPECT_TRUE(upload_data_stream.IsEOF()); |
+ // But the body is not yet fully sent (kUploadData is not yet sent) |
+ // since we're send-stalled. |
+ EXPECT_TRUE(stream->stream()->send_stalled_by_flow_control()); |
+ // Read in WINDOW_UPDATE or SETTINGS frame. |
+ data.RunFor((GetParam().protocol >= kProtoSPDY31) ? 8 : 7); |
+ rv = callback.WaitForResult(); |
helper.VerifyDataConsumed(); |
} |