| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "net/spdy/spdy_http_stream.h" | |
| 6 | |
| 7 #include <vector> | |
| 8 | |
| 9 #include "base/memory/scoped_ptr.h" | |
| 10 #include "base/message_loop/message_loop_proxy.h" | |
| 11 #include "base/run_loop.h" | |
| 12 #include "base/stl_util.h" | |
| 13 #include "crypto/ec_private_key.h" | |
| 14 #include "crypto/ec_signature_creator.h" | |
| 15 #include "crypto/signature_creator.h" | |
| 16 #include "net/base/capturing_net_log.h" | |
| 17 #include "net/base/chunked_upload_data_stream.h" | |
| 18 #include "net/base/load_timing_info.h" | |
| 19 #include "net/base/load_timing_info_test_util.h" | |
| 20 #include "net/base/test_completion_callback.h" | |
| 21 #include "net/cert/asn1_util.h" | |
| 22 #include "net/http/http_request_info.h" | |
| 23 #include "net/http/http_response_headers.h" | |
| 24 #include "net/http/http_response_info.h" | |
| 25 #include "net/socket/next_proto.h" | |
| 26 #include "net/socket/socket_test_util.h" | |
| 27 #include "net/spdy/spdy_http_utils.h" | |
| 28 #include "net/spdy/spdy_session.h" | |
| 29 #include "net/spdy/spdy_test_util_common.h" | |
| 30 #include "net/ssl/default_channel_id_store.h" | |
| 31 #include "testing/gtest/include/gtest/gtest.h" | |
| 32 | |
| 33 namespace net { | |
| 34 | |
| 35 namespace { | |
| 36 | |
| 37 // Tests the load timing of a stream that's connected and is not the first | |
| 38 // request sent on a connection. | |
| 39 void TestLoadTimingReused(const HttpStream& stream) { | |
| 40 LoadTimingInfo load_timing_info; | |
| 41 EXPECT_TRUE(stream.GetLoadTimingInfo(&load_timing_info)); | |
| 42 | |
| 43 EXPECT_TRUE(load_timing_info.socket_reused); | |
| 44 EXPECT_NE(NetLog::Source::kInvalidId, load_timing_info.socket_log_id); | |
| 45 | |
| 46 ExpectConnectTimingHasNoTimes(load_timing_info.connect_timing); | |
| 47 ExpectLoadTimingHasOnlyConnectionTimes(load_timing_info); | |
| 48 } | |
| 49 | |
| 50 // Tests the load timing of a stream that's connected and using a fresh | |
| 51 // connection. | |
| 52 void TestLoadTimingNotReused(const HttpStream& stream) { | |
| 53 LoadTimingInfo load_timing_info; | |
| 54 EXPECT_TRUE(stream.GetLoadTimingInfo(&load_timing_info)); | |
| 55 | |
| 56 EXPECT_FALSE(load_timing_info.socket_reused); | |
| 57 EXPECT_NE(NetLog::Source::kInvalidId, load_timing_info.socket_log_id); | |
| 58 | |
| 59 ExpectConnectTimingHasTimes(load_timing_info.connect_timing, | |
| 60 CONNECT_TIMING_HAS_DNS_TIMES); | |
| 61 ExpectLoadTimingHasOnlyConnectionTimes(load_timing_info); | |
| 62 } | |
| 63 | |
| 64 } // namespace | |
| 65 | |
| 66 class SpdyHttpStreamTest : public testing::Test, | |
| 67 public testing::WithParamInterface<NextProto> { | |
| 68 public: | |
| 69 SpdyHttpStreamTest() | |
| 70 : spdy_util_(GetParam()), | |
| 71 session_deps_(GetParam()) { | |
| 72 session_deps_.net_log = &net_log_; | |
| 73 } | |
| 74 | |
| 75 DeterministicSocketData* deterministic_data() { | |
| 76 return deterministic_data_.get(); | |
| 77 } | |
| 78 | |
| 79 OrderedSocketData* data() { return data_.get(); } | |
| 80 | |
| 81 protected: | |
| 82 void TearDown() override { | |
| 83 crypto::ECSignatureCreator::SetFactoryForTesting(NULL); | |
| 84 base::MessageLoop::current()->RunUntilIdle(); | |
| 85 } | |
| 86 | |
| 87 // Initializes the session using DeterministicSocketData. It's advisable | |
| 88 // to use this function rather than the OrderedSocketData, since the | |
| 89 // DeterministicSocketData behaves in a reasonable manner. | |
| 90 void InitSessionDeterministic(MockRead* reads, size_t reads_count, | |
| 91 MockWrite* writes, size_t writes_count, | |
| 92 const SpdySessionKey& key) { | |
| 93 deterministic_data_.reset( | |
| 94 new DeterministicSocketData(reads, reads_count, writes, writes_count)); | |
| 95 session_deps_.deterministic_socket_factory->AddSocketDataProvider( | |
| 96 deterministic_data_.get()); | |
| 97 http_session_ = | |
| 98 SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_); | |
| 99 session_ = CreateInsecureSpdySession(http_session_, key, BoundNetLog()); | |
| 100 } | |
| 101 | |
| 102 // Initializes the session using the finicky OrderedSocketData class. | |
| 103 void InitSession(MockRead* reads, size_t reads_count, | |
| 104 MockWrite* writes, size_t writes_count, | |
| 105 const SpdySessionKey& key) { | |
| 106 data_.reset(new OrderedSocketData(reads, reads_count, | |
| 107 writes, writes_count)); | |
| 108 session_deps_.socket_factory->AddSocketDataProvider(data_.get()); | |
| 109 http_session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_); | |
| 110 session_ = CreateInsecureSpdySession(http_session_, key, BoundNetLog()); | |
| 111 } | |
| 112 | |
| 113 void TestSendCredentials( | |
| 114 ChannelIDService* channel_id_service, | |
| 115 const std::string& cert, | |
| 116 const std::string& proof); | |
| 117 | |
| 118 SpdyTestUtil spdy_util_; | |
| 119 CapturingNetLog net_log_; | |
| 120 SpdySessionDependencies session_deps_; | |
| 121 scoped_ptr<OrderedSocketData> data_; | |
| 122 scoped_ptr<DeterministicSocketData> deterministic_data_; | |
| 123 scoped_refptr<HttpNetworkSession> http_session_; | |
| 124 base::WeakPtr<SpdySession> session_; | |
| 125 | |
| 126 private: | |
| 127 MockECSignatureCreatorFactory ec_signature_creator_factory_; | |
| 128 }; | |
| 129 | |
| 130 INSTANTIATE_TEST_CASE_P( | |
| 131 NextProto, | |
| 132 SpdyHttpStreamTest, | |
| 133 testing::Values(kProtoSPDY31, kProtoSPDY4_14, kProtoSPDY4_15)); | |
| 134 | |
| 135 // SpdyHttpStream::GetUploadProgress() should still work even before the | |
| 136 // stream is initialized. | |
| 137 TEST_P(SpdyHttpStreamTest, GetUploadProgressBeforeInitialization) { | |
| 138 MockRead reads[] = { | |
| 139 MockRead(ASYNC, 0, 0) // EOF | |
| 140 }; | |
| 141 | |
| 142 HostPortPair host_port_pair("www.google.com", 80); | |
| 143 SpdySessionKey key(host_port_pair, ProxyServer::Direct(), | |
| 144 PRIVACY_MODE_DISABLED); | |
| 145 InitSession(reads, arraysize(reads), NULL, 0, key); | |
| 146 | |
| 147 SpdyHttpStream stream(session_, false); | |
| 148 UploadProgress progress = stream.GetUploadProgress(); | |
| 149 EXPECT_EQ(0u, progress.size()); | |
| 150 EXPECT_EQ(0u, progress.position()); | |
| 151 | |
| 152 // Pump the event loop so |reads| is consumed before the function returns. | |
| 153 base::RunLoop().RunUntilIdle(); | |
| 154 } | |
| 155 | |
| 156 TEST_P(SpdyHttpStreamTest, SendRequest) { | |
| 157 scoped_ptr<SpdyFrame> req( | |
| 158 spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); | |
| 159 MockWrite writes[] = { | |
| 160 CreateMockWrite(*req.get(), 1), | |
| 161 }; | |
| 162 scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); | |
| 163 MockRead reads[] = { | |
| 164 CreateMockRead(*resp, 2), | |
| 165 MockRead(SYNCHRONOUS, 0, 3) // EOF | |
| 166 }; | |
| 167 | |
| 168 HostPortPair host_port_pair("www.google.com", 80); | |
| 169 SpdySessionKey key(host_port_pair, ProxyServer::Direct(), | |
| 170 PRIVACY_MODE_DISABLED); | |
| 171 InitSession(reads, arraysize(reads), writes, arraysize(writes), key); | |
| 172 | |
| 173 HttpRequestInfo request; | |
| 174 request.method = "GET"; | |
| 175 request.url = GURL("http://www.google.com/"); | |
| 176 TestCompletionCallback callback; | |
| 177 HttpResponseInfo response; | |
| 178 HttpRequestHeaders headers; | |
| 179 BoundNetLog net_log; | |
| 180 scoped_ptr<SpdyHttpStream> http_stream(new SpdyHttpStream(session_, true)); | |
| 181 // Make sure getting load timing information the stream early does not crash. | |
| 182 LoadTimingInfo load_timing_info; | |
| 183 EXPECT_FALSE(http_stream->GetLoadTimingInfo(&load_timing_info)); | |
| 184 | |
| 185 ASSERT_EQ( | |
| 186 OK, | |
| 187 http_stream->InitializeStream(&request, DEFAULT_PRIORITY, | |
| 188 net_log, CompletionCallback())); | |
| 189 EXPECT_FALSE(http_stream->GetLoadTimingInfo(&load_timing_info)); | |
| 190 | |
| 191 EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response, | |
| 192 callback.callback())); | |
| 193 EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key)); | |
| 194 EXPECT_FALSE(http_stream->GetLoadTimingInfo(&load_timing_info)); | |
| 195 | |
| 196 // This triggers the MockWrite and read 2 | |
| 197 callback.WaitForResult(); | |
| 198 | |
| 199 // Can get timing information once the stream connects. | |
| 200 TestLoadTimingNotReused(*http_stream); | |
| 201 | |
| 202 // This triggers read 3. The empty read causes the session to shut down. | |
| 203 data()->CompleteRead(); | |
| 204 | |
| 205 // Because we abandoned the stream, we don't expect to find a session in the | |
| 206 // pool anymore. | |
| 207 EXPECT_FALSE(HasSpdySession(http_session_->spdy_session_pool(), key)); | |
| 208 EXPECT_TRUE(data()->at_read_eof()); | |
| 209 EXPECT_TRUE(data()->at_write_eof()); | |
| 210 | |
| 211 TestLoadTimingNotReused(*http_stream); | |
| 212 http_stream->Close(true); | |
| 213 // Test that there's no crash when trying to get the load timing after the | |
| 214 // stream has been closed. | |
| 215 TestLoadTimingNotReused(*http_stream); | |
| 216 } | |
| 217 | |
| 218 TEST_P(SpdyHttpStreamTest, LoadTimingTwoRequests) { | |
| 219 scoped_ptr<SpdyFrame> req1( | |
| 220 spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true)); | |
| 221 scoped_ptr<SpdyFrame> req2( | |
| 222 spdy_util_.ConstructSpdyGet(NULL, 0, false, 3, LOWEST, true)); | |
| 223 MockWrite writes[] = { | |
| 224 CreateMockWrite(*req1, 0), | |
| 225 CreateMockWrite(*req2, 1), | |
| 226 }; | |
| 227 scoped_ptr<SpdyFrame> resp1( | |
| 228 spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); | |
| 229 scoped_ptr<SpdyFrame> body1( | |
| 230 spdy_util_.ConstructSpdyBodyFrame(1, "", 0, true)); | |
| 231 scoped_ptr<SpdyFrame> resp2( | |
| 232 spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 3)); | |
| 233 scoped_ptr<SpdyFrame> body2( | |
| 234 spdy_util_.ConstructSpdyBodyFrame(3, "", 0, true)); | |
| 235 MockRead reads[] = { | |
| 236 CreateMockRead(*resp1, 2), | |
| 237 CreateMockRead(*body1, 3), | |
| 238 CreateMockRead(*resp2, 4), | |
| 239 CreateMockRead(*body2, 5), | |
| 240 MockRead(ASYNC, 0, 6) // EOF | |
| 241 }; | |
| 242 | |
| 243 HostPortPair host_port_pair("www.google.com", 80); | |
| 244 SpdySessionKey key(host_port_pair, ProxyServer::Direct(), | |
| 245 PRIVACY_MODE_DISABLED); | |
| 246 InitSessionDeterministic(reads, arraysize(reads), | |
| 247 writes, arraysize(writes), | |
| 248 key); | |
| 249 | |
| 250 HttpRequestInfo request1; | |
| 251 request1.method = "GET"; | |
| 252 request1.url = GURL("http://www.google.com/"); | |
| 253 TestCompletionCallback callback1; | |
| 254 HttpResponseInfo response1; | |
| 255 HttpRequestHeaders headers1; | |
| 256 scoped_ptr<SpdyHttpStream> http_stream1(new SpdyHttpStream(session_, true)); | |
| 257 | |
| 258 HttpRequestInfo request2; | |
| 259 request2.method = "GET"; | |
| 260 request2.url = GURL("http://www.google.com/"); | |
| 261 TestCompletionCallback callback2; | |
| 262 HttpResponseInfo response2; | |
| 263 HttpRequestHeaders headers2; | |
| 264 scoped_ptr<SpdyHttpStream> http_stream2(new SpdyHttpStream(session_, true)); | |
| 265 | |
| 266 // First write. | |
| 267 ASSERT_EQ(OK, | |
| 268 http_stream1->InitializeStream(&request1, DEFAULT_PRIORITY, | |
| 269 BoundNetLog(), | |
| 270 CompletionCallback())); | |
| 271 EXPECT_EQ(ERR_IO_PENDING, http_stream1->SendRequest(headers1, &response1, | |
| 272 callback1.callback())); | |
| 273 EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key)); | |
| 274 | |
| 275 deterministic_data()->RunFor(1); | |
| 276 EXPECT_LE(0, callback1.WaitForResult()); | |
| 277 | |
| 278 TestLoadTimingNotReused(*http_stream1); | |
| 279 LoadTimingInfo load_timing_info1; | |
| 280 LoadTimingInfo load_timing_info2; | |
| 281 EXPECT_TRUE(http_stream1->GetLoadTimingInfo(&load_timing_info1)); | |
| 282 EXPECT_FALSE(http_stream2->GetLoadTimingInfo(&load_timing_info2)); | |
| 283 | |
| 284 // Second write. | |
| 285 ASSERT_EQ(OK, | |
| 286 http_stream2->InitializeStream(&request2, DEFAULT_PRIORITY, | |
| 287 BoundNetLog(), | |
| 288 CompletionCallback())); | |
| 289 EXPECT_EQ(ERR_IO_PENDING, http_stream2->SendRequest(headers2, &response2, | |
| 290 callback2.callback())); | |
| 291 EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key)); | |
| 292 | |
| 293 deterministic_data()->RunFor(1); | |
| 294 EXPECT_LE(0, callback2.WaitForResult()); | |
| 295 TestLoadTimingReused(*http_stream2); | |
| 296 EXPECT_TRUE(http_stream2->GetLoadTimingInfo(&load_timing_info2)); | |
| 297 EXPECT_EQ(load_timing_info1.socket_log_id, load_timing_info2.socket_log_id); | |
| 298 | |
| 299 // All the reads. | |
| 300 deterministic_data()->RunFor(6); | |
| 301 | |
| 302 // Read stream 1 to completion, before making sure we can still read load | |
| 303 // timing from both streams. | |
| 304 scoped_refptr<IOBuffer> buf1(new IOBuffer(1)); | |
| 305 ASSERT_EQ( | |
| 306 0, http_stream1->ReadResponseBody(buf1.get(), 1, callback1.callback())); | |
| 307 | |
| 308 // Stream 1 has been read to completion. | |
| 309 TestLoadTimingNotReused(*http_stream1); | |
| 310 // Stream 2 still has queued body data. | |
| 311 TestLoadTimingReused(*http_stream2); | |
| 312 } | |
| 313 | |
| 314 TEST_P(SpdyHttpStreamTest, SendChunkedPost) { | |
| 315 BufferedSpdyFramer framer(spdy_util_.spdy_version(), false); | |
| 316 | |
| 317 scoped_ptr<SpdyFrame> req( | |
| 318 spdy_util_.ConstructChunkedSpdyPost(NULL, 0)); | |
| 319 scoped_ptr<SpdyFrame> body( | |
| 320 framer.CreateDataFrame(1, kUploadData, kUploadDataSize, DATA_FLAG_FIN)); | |
| 321 std::vector<MockWrite> writes; | |
| 322 int seq = 0; | |
| 323 writes.push_back(CreateMockWrite(*req, seq++)); | |
| 324 writes.push_back(CreateMockWrite(*body, seq++)); // POST upload frame | |
| 325 | |
| 326 scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); | |
| 327 std::vector<MockRead> reads; | |
| 328 reads.push_back(CreateMockRead(*resp, seq++)); | |
| 329 reads.push_back(CreateMockRead(*body, seq++)); | |
| 330 reads.push_back(MockRead(SYNCHRONOUS, 0, seq++)); // EOF | |
| 331 | |
| 332 HostPortPair host_port_pair("www.google.com", 80); | |
| 333 SpdySessionKey key(host_port_pair, ProxyServer::Direct(), | |
| 334 PRIVACY_MODE_DISABLED); | |
| 335 InitSession(vector_as_array(&reads), reads.size(), | |
| 336 vector_as_array(&writes), writes.size(), | |
| 337 key); | |
| 338 EXPECT_EQ(spdy_util_.spdy_version(), session_->GetProtocolVersion()); | |
| 339 | |
| 340 ChunkedUploadDataStream upload_stream(0); | |
| 341 const int kFirstChunkSize = kUploadDataSize/2; | |
| 342 upload_stream.AppendData(kUploadData, kFirstChunkSize, false); | |
| 343 upload_stream.AppendData(kUploadData + kFirstChunkSize, | |
| 344 kUploadDataSize - kFirstChunkSize, true); | |
| 345 | |
| 346 HttpRequestInfo request; | |
| 347 request.method = "POST"; | |
| 348 request.url = GURL("http://www.google.com/"); | |
| 349 request.upload_data_stream = &upload_stream; | |
| 350 | |
| 351 ASSERT_EQ(OK, upload_stream.Init(TestCompletionCallback().callback())); | |
| 352 | |
| 353 TestCompletionCallback callback; | |
| 354 HttpResponseInfo response; | |
| 355 HttpRequestHeaders headers; | |
| 356 BoundNetLog net_log; | |
| 357 SpdyHttpStream http_stream(session_, true); | |
| 358 ASSERT_EQ( | |
| 359 OK, | |
| 360 http_stream.InitializeStream(&request, DEFAULT_PRIORITY, | |
| 361 net_log, CompletionCallback())); | |
| 362 | |
| 363 EXPECT_EQ(ERR_IO_PENDING, http_stream.SendRequest( | |
| 364 headers, &response, callback.callback())); | |
| 365 EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key)); | |
| 366 | |
| 367 // This results in writing the post body and reading the response headers. | |
| 368 callback.WaitForResult(); | |
| 369 | |
| 370 // This triggers reading the body and the EOF, causing the session to shut | |
| 371 // down. | |
| 372 data()->CompleteRead(); | |
| 373 base::MessageLoop::current()->RunUntilIdle(); | |
| 374 | |
| 375 // Because we abandoned the stream, we don't expect to find a session in the | |
| 376 // pool anymore. | |
| 377 EXPECT_FALSE(HasSpdySession(http_session_->spdy_session_pool(), key)); | |
| 378 EXPECT_TRUE(data()->at_read_eof()); | |
| 379 EXPECT_TRUE(data()->at_write_eof()); | |
| 380 } | |
| 381 | |
| 382 // Test to ensure the SpdyStream state machine does not get confused when a | |
| 383 // chunk becomes available while a write is pending. | |
| 384 TEST_P(SpdyHttpStreamTest, DelayedSendChunkedPost) { | |
| 385 const char kUploadData1[] = "12345678"; | |
| 386 const int kUploadData1Size = arraysize(kUploadData1)-1; | |
| 387 scoped_ptr<SpdyFrame> req(spdy_util_.ConstructChunkedSpdyPost(NULL, 0)); | |
| 388 scoped_ptr<SpdyFrame> chunk1(spdy_util_.ConstructSpdyBodyFrame(1, false)); | |
| 389 scoped_ptr<SpdyFrame> chunk2( | |
| 390 spdy_util_.ConstructSpdyBodyFrame( | |
| 391 1, kUploadData1, kUploadData1Size, false)); | |
| 392 scoped_ptr<SpdyFrame> chunk3(spdy_util_.ConstructSpdyBodyFrame(1, true)); | |
| 393 MockWrite writes[] = { | |
| 394 CreateMockWrite(*req.get(), 0), | |
| 395 CreateMockWrite(*chunk1, 1), // POST upload frames | |
| 396 CreateMockWrite(*chunk2, 2), | |
| 397 CreateMockWrite(*chunk3, 3), | |
| 398 }; | |
| 399 scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); | |
| 400 MockRead reads[] = { | |
| 401 CreateMockRead(*resp, 4), | |
| 402 CreateMockRead(*chunk1, 5), | |
| 403 CreateMockRead(*chunk2, 6), | |
| 404 CreateMockRead(*chunk3, 7), | |
| 405 MockRead(ASYNC, 0, 8) // EOF | |
| 406 }; | |
| 407 | |
| 408 HostPortPair host_port_pair("www.google.com", 80); | |
| 409 SpdySessionKey key(host_port_pair, ProxyServer::Direct(), | |
| 410 PRIVACY_MODE_DISABLED); | |
| 411 InitSessionDeterministic(reads, arraysize(reads), | |
| 412 writes, arraysize(writes), | |
| 413 key); | |
| 414 | |
| 415 ChunkedUploadDataStream upload_stream(0); | |
| 416 | |
| 417 HttpRequestInfo request; | |
| 418 request.method = "POST"; | |
| 419 request.url = GURL("http://www.google.com/"); | |
| 420 request.upload_data_stream = &upload_stream; | |
| 421 | |
| 422 ASSERT_EQ(OK, upload_stream.Init(TestCompletionCallback().callback())); | |
| 423 upload_stream.AppendData(kUploadData, kUploadDataSize, false); | |
| 424 | |
| 425 BoundNetLog net_log; | |
| 426 scoped_ptr<SpdyHttpStream> http_stream(new SpdyHttpStream(session_, true)); | |
| 427 ASSERT_EQ(OK, http_stream->InitializeStream(&request, DEFAULT_PRIORITY, | |
| 428 net_log, CompletionCallback())); | |
| 429 | |
| 430 TestCompletionCallback callback; | |
| 431 HttpRequestHeaders headers; | |
| 432 HttpResponseInfo response; | |
| 433 // This will attempt to Write() the initial request and headers, which will | |
| 434 // complete asynchronously. | |
| 435 EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response, | |
| 436 callback.callback())); | |
| 437 EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key)); | |
| 438 | |
| 439 // Complete the initial request write and the first chunk. | |
| 440 deterministic_data()->RunFor(2); | |
| 441 ASSERT_TRUE(callback.have_result()); | |
| 442 EXPECT_EQ(OK, callback.WaitForResult()); | |
| 443 | |
| 444 // Now append the final two chunks which will enqueue two more writes. | |
| 445 upload_stream.AppendData(kUploadData1, kUploadData1Size, false); | |
| 446 upload_stream.AppendData(kUploadData, kUploadDataSize, true); | |
| 447 | |
| 448 // Finish writing all the chunks. | |
| 449 deterministic_data()->RunFor(2); | |
| 450 | |
| 451 // Read response headers. | |
| 452 deterministic_data()->RunFor(1); | |
| 453 ASSERT_EQ(OK, http_stream->ReadResponseHeaders(callback.callback())); | |
| 454 | |
| 455 // Read and check |chunk1| response. | |
| 456 deterministic_data()->RunFor(1); | |
| 457 scoped_refptr<IOBuffer> buf1(new IOBuffer(kUploadDataSize)); | |
| 458 ASSERT_EQ(kUploadDataSize, | |
| 459 http_stream->ReadResponseBody( | |
| 460 buf1.get(), kUploadDataSize, callback.callback())); | |
| 461 EXPECT_EQ(kUploadData, std::string(buf1->data(), kUploadDataSize)); | |
| 462 | |
| 463 // Read and check |chunk2| response. | |
| 464 deterministic_data()->RunFor(1); | |
| 465 scoped_refptr<IOBuffer> buf2(new IOBuffer(kUploadData1Size)); | |
| 466 ASSERT_EQ(kUploadData1Size, | |
| 467 http_stream->ReadResponseBody( | |
| 468 buf2.get(), kUploadData1Size, callback.callback())); | |
| 469 EXPECT_EQ(kUploadData1, std::string(buf2->data(), kUploadData1Size)); | |
| 470 | |
| 471 // Read and check |chunk3| response. | |
| 472 deterministic_data()->RunFor(1); | |
| 473 scoped_refptr<IOBuffer> buf3(new IOBuffer(kUploadDataSize)); | |
| 474 ASSERT_EQ(kUploadDataSize, | |
| 475 http_stream->ReadResponseBody( | |
| 476 buf3.get(), kUploadDataSize, callback.callback())); | |
| 477 EXPECT_EQ(kUploadData, std::string(buf3->data(), kUploadDataSize)); | |
| 478 | |
| 479 // Finish reading the |EOF|. | |
| 480 deterministic_data()->RunFor(1); | |
| 481 ASSERT_TRUE(response.headers.get()); | |
| 482 ASSERT_EQ(200, response.headers->response_code()); | |
| 483 EXPECT_TRUE(deterministic_data()->at_read_eof()); | |
| 484 EXPECT_TRUE(deterministic_data()->at_write_eof()); | |
| 485 } | |
| 486 | |
| 487 // Test that the SpdyStream state machine can handle sending a final empty data | |
| 488 // frame when uploading a chunked data stream. | |
| 489 TEST_P(SpdyHttpStreamTest, DelayedSendChunkedPostWithEmptyFinalDataFrame) { | |
| 490 scoped_ptr<SpdyFrame> req(spdy_util_.ConstructChunkedSpdyPost(NULL, 0)); | |
| 491 scoped_ptr<SpdyFrame> chunk1(spdy_util_.ConstructSpdyBodyFrame(1, false)); | |
| 492 scoped_ptr<SpdyFrame> chunk2( | |
| 493 spdy_util_.ConstructSpdyBodyFrame(1, "", 0, true)); | |
| 494 MockWrite writes[] = { | |
| 495 CreateMockWrite(*req.get(), 0), | |
| 496 CreateMockWrite(*chunk1, 1), // POST upload frames | |
| 497 CreateMockWrite(*chunk2, 2), | |
| 498 }; | |
| 499 scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); | |
| 500 MockRead reads[] = { | |
| 501 CreateMockRead(*resp, 3), | |
| 502 CreateMockRead(*chunk1, 4), | |
| 503 CreateMockRead(*chunk2, 5), | |
| 504 MockRead(ASYNC, 0, 6) // EOF | |
| 505 }; | |
| 506 | |
| 507 HostPortPair host_port_pair("www.google.com", 80); | |
| 508 SpdySessionKey key(host_port_pair, ProxyServer::Direct(), | |
| 509 PRIVACY_MODE_DISABLED); | |
| 510 InitSessionDeterministic(reads, arraysize(reads), | |
| 511 writes, arraysize(writes), | |
| 512 key); | |
| 513 | |
| 514 ChunkedUploadDataStream upload_stream(0); | |
| 515 | |
| 516 HttpRequestInfo request; | |
| 517 request.method = "POST"; | |
| 518 request.url = GURL("http://www.google.com/"); | |
| 519 request.upload_data_stream = &upload_stream; | |
| 520 | |
| 521 ASSERT_EQ(OK, upload_stream.Init(TestCompletionCallback().callback())); | |
| 522 upload_stream.AppendData(kUploadData, kUploadDataSize, false); | |
| 523 | |
| 524 BoundNetLog net_log; | |
| 525 scoped_ptr<SpdyHttpStream> http_stream(new SpdyHttpStream(session_, true)); | |
| 526 ASSERT_EQ(OK, http_stream->InitializeStream(&request, DEFAULT_PRIORITY, | |
| 527 net_log, CompletionCallback())); | |
| 528 | |
| 529 TestCompletionCallback callback; | |
| 530 HttpRequestHeaders headers; | |
| 531 HttpResponseInfo response; | |
| 532 // This will attempt to Write() the initial request and headers, which will | |
| 533 // complete asynchronously. | |
| 534 EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response, | |
| 535 callback.callback())); | |
| 536 EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key)); | |
| 537 | |
| 538 // Complete the initial request write and the first chunk. | |
| 539 deterministic_data()->RunFor(2); | |
| 540 ASSERT_TRUE(callback.have_result()); | |
| 541 EXPECT_EQ(OK, callback.WaitForResult()); | |
| 542 | |
| 543 // Now end the stream with an empty data frame and the FIN set. | |
| 544 upload_stream.AppendData(NULL, 0, true); | |
| 545 | |
| 546 // Finish writing the final frame. | |
| 547 deterministic_data()->RunFor(1); | |
| 548 | |
| 549 // Read response headers. | |
| 550 deterministic_data()->RunFor(1); | |
| 551 ASSERT_EQ(OK, http_stream->ReadResponseHeaders(callback.callback())); | |
| 552 | |
| 553 // Read and check |chunk1| response. | |
| 554 deterministic_data()->RunFor(1); | |
| 555 scoped_refptr<IOBuffer> buf1(new IOBuffer(kUploadDataSize)); | |
| 556 ASSERT_EQ(kUploadDataSize, | |
| 557 http_stream->ReadResponseBody( | |
| 558 buf1.get(), kUploadDataSize, callback.callback())); | |
| 559 EXPECT_EQ(kUploadData, std::string(buf1->data(), kUploadDataSize)); | |
| 560 | |
| 561 // Read and check |chunk2| response. | |
| 562 deterministic_data()->RunFor(1); | |
| 563 ASSERT_EQ(0, | |
| 564 http_stream->ReadResponseBody( | |
| 565 buf1.get(), kUploadDataSize, callback.callback())); | |
| 566 | |
| 567 // Finish reading the |EOF|. | |
| 568 deterministic_data()->RunFor(1); | |
| 569 ASSERT_TRUE(response.headers.get()); | |
| 570 ASSERT_EQ(200, response.headers->response_code()); | |
| 571 EXPECT_TRUE(deterministic_data()->at_read_eof()); | |
| 572 EXPECT_TRUE(deterministic_data()->at_write_eof()); | |
| 573 } | |
| 574 | |
| 575 // Test that the SpdyStream state machine handles a chunked upload with no | |
| 576 // payload. Unclear if this is a case worth supporting. | |
| 577 TEST_P(SpdyHttpStreamTest, ChunkedPostWithEmptyPayload) { | |
| 578 scoped_ptr<SpdyFrame> req(spdy_util_.ConstructChunkedSpdyPost(NULL, 0)); | |
| 579 scoped_ptr<SpdyFrame> chunk( | |
| 580 spdy_util_.ConstructSpdyBodyFrame(1, "", 0, true)); | |
| 581 MockWrite writes[] = { | |
| 582 CreateMockWrite(*req.get(), 0), | |
| 583 CreateMockWrite(*chunk, 1), | |
| 584 }; | |
| 585 scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); | |
| 586 MockRead reads[] = { | |
| 587 CreateMockRead(*resp, 2), | |
| 588 CreateMockRead(*chunk, 3), | |
| 589 MockRead(ASYNC, 0, 4) // EOF | |
| 590 }; | |
| 591 | |
| 592 HostPortPair host_port_pair("www.google.com", 80); | |
| 593 SpdySessionKey key(host_port_pair, ProxyServer::Direct(), | |
| 594 PRIVACY_MODE_DISABLED); | |
| 595 InitSessionDeterministic(reads, arraysize(reads), | |
| 596 writes, arraysize(writes), | |
| 597 key); | |
| 598 | |
| 599 ChunkedUploadDataStream upload_stream(0); | |
| 600 | |
| 601 HttpRequestInfo request; | |
| 602 request.method = "POST"; | |
| 603 request.url = GURL("http://www.google.com/"); | |
| 604 request.upload_data_stream = &upload_stream; | |
| 605 | |
| 606 ASSERT_EQ(OK, upload_stream.Init(TestCompletionCallback().callback())); | |
| 607 upload_stream.AppendData("", 0, true); | |
| 608 | |
| 609 BoundNetLog net_log; | |
| 610 scoped_ptr<SpdyHttpStream> http_stream(new SpdyHttpStream(session_, true)); | |
| 611 ASSERT_EQ(OK, http_stream->InitializeStream(&request, DEFAULT_PRIORITY, | |
| 612 net_log, CompletionCallback())); | |
| 613 | |
| 614 TestCompletionCallback callback; | |
| 615 HttpRequestHeaders headers; | |
| 616 HttpResponseInfo response; | |
| 617 // This will attempt to Write() the initial request and headers, which will | |
| 618 // complete asynchronously. | |
| 619 EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response, | |
| 620 callback.callback())); | |
| 621 EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key)); | |
| 622 | |
| 623 // Complete writing request, followed by a FIN. | |
| 624 deterministic_data()->RunFor(2); | |
| 625 ASSERT_TRUE(callback.have_result()); | |
| 626 EXPECT_EQ(OK, callback.WaitForResult()); | |
| 627 | |
| 628 // Read response headers. | |
| 629 deterministic_data()->RunFor(1); | |
| 630 ASSERT_EQ(OK, http_stream->ReadResponseHeaders(callback.callback())); | |
| 631 | |
| 632 // Read and check |chunk| response. | |
| 633 deterministic_data()->RunFor(1); | |
| 634 scoped_refptr<IOBuffer> buf(new IOBuffer(1)); | |
| 635 ASSERT_EQ(0, | |
| 636 http_stream->ReadResponseBody( | |
| 637 buf.get(), 1, callback.callback())); | |
| 638 | |
| 639 // Finish reading the |EOF|. | |
| 640 deterministic_data()->RunFor(1); | |
| 641 ASSERT_TRUE(response.headers.get()); | |
| 642 ASSERT_EQ(200, response.headers->response_code()); | |
| 643 EXPECT_TRUE(deterministic_data()->at_read_eof()); | |
| 644 EXPECT_TRUE(deterministic_data()->at_write_eof()); | |
| 645 } | |
| 646 | |
| 647 // Test case for bug: http://code.google.com/p/chromium/issues/detail?id=50058 | |
| 648 TEST_P(SpdyHttpStreamTest, SpdyURLTest) { | |
| 649 const char * const full_url = "http://www.google.com/foo?query=what#anchor"; | |
| 650 const char * const base_url = "http://www.google.com/foo?query=what"; | |
| 651 scoped_ptr<SpdyFrame> req( | |
| 652 spdy_util_.ConstructSpdyGet(base_url, false, 1, LOWEST)); | |
| 653 MockWrite writes[] = { | |
| 654 CreateMockWrite(*req.get(), 1), | |
| 655 }; | |
| 656 scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1)); | |
| 657 MockRead reads[] = { | |
| 658 CreateMockRead(*resp, 2), | |
| 659 MockRead(SYNCHRONOUS, 0, 3) // EOF | |
| 660 }; | |
| 661 | |
| 662 HostPortPair host_port_pair("www.google.com", 80); | |
| 663 SpdySessionKey key(host_port_pair, ProxyServer::Direct(), | |
| 664 PRIVACY_MODE_DISABLED); | |
| 665 InitSession(reads, arraysize(reads), writes, arraysize(writes), key); | |
| 666 | |
| 667 HttpRequestInfo request; | |
| 668 request.method = "GET"; | |
| 669 request.url = GURL(full_url); | |
| 670 TestCompletionCallback callback; | |
| 671 HttpResponseInfo response; | |
| 672 HttpRequestHeaders headers; | |
| 673 BoundNetLog net_log; | |
| 674 scoped_ptr<SpdyHttpStream> http_stream(new SpdyHttpStream(session_, true)); | |
| 675 ASSERT_EQ(OK, | |
| 676 http_stream->InitializeStream( | |
| 677 &request, DEFAULT_PRIORITY, net_log, CompletionCallback())); | |
| 678 | |
| 679 EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response, | |
| 680 callback.callback())); | |
| 681 | |
| 682 EXPECT_EQ(base_url, http_stream->stream()->GetUrlFromHeaders().spec()); | |
| 683 | |
| 684 // This triggers the MockWrite and read 2 | |
| 685 callback.WaitForResult(); | |
| 686 | |
| 687 // This triggers read 3. The empty read causes the session to shut down. | |
| 688 data()->CompleteRead(); | |
| 689 | |
| 690 // Because we abandoned the stream, we don't expect to find a session in the | |
| 691 // pool anymore. | |
| 692 EXPECT_FALSE(HasSpdySession(http_session_->spdy_session_pool(), key)); | |
| 693 EXPECT_TRUE(data()->at_read_eof()); | |
| 694 EXPECT_TRUE(data()->at_write_eof()); | |
| 695 } | |
| 696 | |
| 697 // The tests below are only for SPDY/3 and above. | |
| 698 | |
| 699 // Test the receipt of a WINDOW_UPDATE frame while waiting for a chunk to be | |
| 700 // made available is handled correctly. | |
| 701 TEST_P(SpdyHttpStreamTest, DelayedSendChunkedPostWithWindowUpdate) { | |
| 702 scoped_ptr<SpdyFrame> req(spdy_util_.ConstructChunkedSpdyPost(NULL, 0)); | |
| 703 scoped_ptr<SpdyFrame> chunk1(spdy_util_.ConstructSpdyBodyFrame(1, true)); | |
| 704 MockWrite writes[] = { | |
| 705 CreateMockWrite(*req.get(), 0), | |
| 706 CreateMockWrite(*chunk1, 1), | |
| 707 }; | |
| 708 scoped_ptr<SpdyFrame> resp(spdy_util_.ConstructSpdyPostSynReply(NULL, 0)); | |
| 709 scoped_ptr<SpdyFrame> window_update( | |
| 710 spdy_util_.ConstructSpdyWindowUpdate(1, kUploadDataSize)); | |
| 711 MockRead reads[] = { | |
| 712 CreateMockRead(*window_update, 2), | |
| 713 CreateMockRead(*resp, 3), | |
| 714 CreateMockRead(*chunk1, 4), | |
| 715 MockRead(ASYNC, 0, 5) // EOF | |
| 716 }; | |
| 717 | |
| 718 HostPortPair host_port_pair("www.google.com", 80); | |
| 719 SpdySessionKey key(host_port_pair, ProxyServer::Direct(), | |
| 720 PRIVACY_MODE_DISABLED); | |
| 721 | |
| 722 InitSessionDeterministic(reads, arraysize(reads), | |
| 723 writes, arraysize(writes), | |
| 724 key); | |
| 725 | |
| 726 ChunkedUploadDataStream upload_stream(0); | |
| 727 | |
| 728 HttpRequestInfo request; | |
| 729 request.method = "POST"; | |
| 730 request.url = GURL("http://www.google.com/"); | |
| 731 request.upload_data_stream = &upload_stream; | |
| 732 | |
| 733 ASSERT_EQ(OK, upload_stream.Init(TestCompletionCallback().callback())); | |
| 734 upload_stream.AppendData(kUploadData, kUploadDataSize, true); | |
| 735 | |
| 736 BoundNetLog net_log; | |
| 737 scoped_ptr<SpdyHttpStream> http_stream(new SpdyHttpStream(session_, true)); | |
| 738 ASSERT_EQ(OK, http_stream->InitializeStream(&request, DEFAULT_PRIORITY, | |
| 739 net_log, CompletionCallback())); | |
| 740 | |
| 741 HttpRequestHeaders headers; | |
| 742 HttpResponseInfo response; | |
| 743 // This will attempt to Write() the initial request and headers, which will | |
| 744 // complete asynchronously. | |
| 745 TestCompletionCallback callback; | |
| 746 EXPECT_EQ(ERR_IO_PENDING, http_stream->SendRequest(headers, &response, | |
| 747 callback.callback())); | |
| 748 EXPECT_TRUE(HasSpdySession(http_session_->spdy_session_pool(), key)); | |
| 749 | |
| 750 // Complete the initial request write and first chunk. | |
| 751 deterministic_data_->RunFor(2); | |
| 752 ASSERT_TRUE(callback.have_result()); | |
| 753 EXPECT_EQ(OK, callback.WaitForResult()); | |
| 754 | |
| 755 // Verify that the window size has decreased. | |
| 756 ASSERT_TRUE(http_stream->stream() != NULL); | |
| 757 EXPECT_NE( | |
| 758 static_cast<int>(SpdySession::GetInitialWindowSize(session_->protocol())), | |
| 759 http_stream->stream()->send_window_size()); | |
| 760 | |
| 761 // Read window update. | |
| 762 deterministic_data_->RunFor(1); | |
| 763 | |
| 764 // Verify the window update. | |
| 765 ASSERT_TRUE(http_stream->stream() != NULL); | |
| 766 EXPECT_EQ( | |
| 767 static_cast<int>(SpdySession::GetInitialWindowSize(session_->protocol())), | |
| 768 http_stream->stream()->send_window_size()); | |
| 769 | |
| 770 // Read response headers. | |
| 771 deterministic_data_->RunFor(1); | |
| 772 ASSERT_EQ(OK, http_stream->ReadResponseHeaders(callback.callback())); | |
| 773 | |
| 774 // Read and check |chunk1| response. | |
| 775 deterministic_data_->RunFor(1); | |
| 776 scoped_refptr<IOBuffer> buf1(new IOBuffer(kUploadDataSize)); | |
| 777 ASSERT_EQ(kUploadDataSize, | |
| 778 http_stream->ReadResponseBody( | |
| 779 buf1.get(), kUploadDataSize, callback.callback())); | |
| 780 EXPECT_EQ(kUploadData, std::string(buf1->data(), kUploadDataSize)); | |
| 781 | |
| 782 // Finish reading the |EOF|. | |
| 783 deterministic_data_->RunFor(1); | |
| 784 ASSERT_TRUE(response.headers.get()); | |
| 785 ASSERT_EQ(200, response.headers->response_code()); | |
| 786 EXPECT_TRUE(deterministic_data_->at_read_eof()); | |
| 787 EXPECT_TRUE(deterministic_data_->at_write_eof()); | |
| 788 } | |
| 789 | |
| 790 // TODO(willchan): Write a longer test for SpdyStream that exercises all | |
| 791 // methods. | |
| 792 | |
| 793 } // namespace net | |
| OLD | NEW |