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.
5 #include "net/spdy/spdy_http_stream.h"
7 #include "base/memory/scoped_ptr.h"
8 #include "base/message_loop/message_loop.h"
9 #include "base/run_loop.h"
10 #include "base/stl_util.h"
11 #include "crypto/ec_private_key.h"
12 #include "crypto/ec_signature_creator.h"
13 #include "crypto/signature_creator.h"
14 #include "net/base/chunked_upload_data_stream.h"
15 #include "net/base/load_timing_info.h"
16 #include "net/base/load_timing_info_test_util.h"
17 #include "net/base/test_completion_callback.h"
18 #include "net/cert/asn1_util.h"
19 #include "net/http/http_request_info.h"
20 #include "net/http/http_response_headers.h"
21 #include "net/http/http_response_info.h"
22 #include "net/log/test_net_log.h"
23 #include "net/socket/next_proto.h"
24 #include "net/socket/socket_test_util.h"
25 #include "net/spdy/spdy_http_utils.h"
26 #include "net/spdy/spdy_session.h"
27 #include "net/spdy/spdy_test_util_common.h"
28 #include "net/ssl/default_channel_id_store.h"
29 #include "testing/gtest/include/gtest/gtest.h"
35 // Tests the load timing of a stream that's connected and is not the first
36 // request sent on a connection.
37 void TestLoadTimingReused(const HttpStream
& stream
) {
38 LoadTimingInfo load_timing_info
;
39 EXPECT_TRUE(stream
.GetLoadTimingInfo(&load_timing_info
));
41 EXPECT_TRUE(load_timing_info
.socket_reused
);
42 EXPECT_NE(NetLog::Source::kInvalidId
, load_timing_info
.socket_log_id
);
44 ExpectConnectTimingHasNoTimes(load_timing_info
.connect_timing
);
45 ExpectLoadTimingHasOnlyConnectionTimes(load_timing_info
);
48 // Tests the load timing of a stream that's connected and using a fresh
50 void TestLoadTimingNotReused(const HttpStream
& stream
) {
51 LoadTimingInfo load_timing_info
;
52 EXPECT_TRUE(stream
.GetLoadTimingInfo(&load_timing_info
));
54 EXPECT_FALSE(load_timing_info
.socket_reused
);
55 EXPECT_NE(NetLog::Source::kInvalidId
, load_timing_info
.socket_log_id
);
57 ExpectConnectTimingHasTimes(load_timing_info
.connect_timing
,
58 CONNECT_TIMING_HAS_DNS_TIMES
);
59 ExpectLoadTimingHasOnlyConnectionTimes(load_timing_info
);
64 class SpdyHttpStreamTest
: public testing::Test
,
65 public testing::WithParamInterface
<NextProto
> {
68 : spdy_util_(GetParam()),
69 session_deps_(GetParam()) {
70 session_deps_
.net_log
= &net_log_
;
74 void TearDown() override
{
75 crypto::ECSignatureCreator::SetFactoryForTesting(NULL
);
76 base::MessageLoop::current()->RunUntilIdle();
77 EXPECT_TRUE(sequenced_data_
->AllReadDataConsumed());
78 EXPECT_TRUE(sequenced_data_
->AllWriteDataConsumed());
81 // Initializes the session using SequencedSocketData.
82 void InitSession(MockRead
* reads
,
86 const SpdySessionKey
& key
) {
87 sequenced_data_
.reset(
88 new SequencedSocketData(reads
, reads_count
, writes
, writes_count
));
89 session_deps_
.socket_factory
->AddSocketDataProvider(sequenced_data_
.get());
90 http_session_
= SpdySessionDependencies::SpdyCreateSession(&session_deps_
);
91 session_
= CreateInsecureSpdySession(http_session_
, key
, BoundNetLog());
94 void TestSendCredentials(
95 ChannelIDService
* channel_id_service
,
96 const std::string
& cert
,
97 const std::string
& proof
);
99 SpdyTestUtil spdy_util_
;
101 SpdySessionDependencies session_deps_
;
102 scoped_ptr
<SequencedSocketData
> sequenced_data_
;
103 scoped_refptr
<HttpNetworkSession
> http_session_
;
104 base::WeakPtr
<SpdySession
> session_
;
107 MockECSignatureCreatorFactory ec_signature_creator_factory_
;
110 INSTANTIATE_TEST_CASE_P(NextProto
,
112 testing::Values(kProtoSPDY31
,
115 // SpdyHttpStream::GetUploadProgress() should still work even before the
116 // stream is initialized.
117 TEST_P(SpdyHttpStreamTest
, GetUploadProgressBeforeInitialization
) {
119 MockRead(ASYNC
, 0, 0) // EOF
122 HostPortPair
host_port_pair("www.example.org", 80);
123 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
124 PRIVACY_MODE_DISABLED
);
125 InitSession(reads
, arraysize(reads
), NULL
, 0, key
);
127 SpdyHttpStream
stream(session_
, false);
128 UploadProgress progress
= stream
.GetUploadProgress();
129 EXPECT_EQ(0u, progress
.size());
130 EXPECT_EQ(0u, progress
.position());
132 // Pump the event loop so |reads| is consumed before the function returns.
133 base::RunLoop().RunUntilIdle();
136 TEST_P(SpdyHttpStreamTest
, SendRequest
) {
137 scoped_ptr
<SpdyFrame
> req(
138 spdy_util_
.ConstructSpdyGet(NULL
, 0, false, 1, LOWEST
, true));
139 MockWrite writes
[] = {
140 CreateMockWrite(*req
.get(), 0),
142 scoped_ptr
<SpdyFrame
> resp(spdy_util_
.ConstructSpdyGetSynReply(NULL
, 0, 1));
144 CreateMockRead(*resp
, 1), MockRead(SYNCHRONOUS
, 0, 2) // EOF
147 HostPortPair
host_port_pair("www.example.org", 80);
148 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
149 PRIVACY_MODE_DISABLED
);
150 InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
), key
);
152 HttpRequestInfo request
;
153 request
.method
= "GET";
154 request
.url
= GURL("http://www.example.org/");
155 TestCompletionCallback callback
;
156 HttpResponseInfo response
;
157 HttpRequestHeaders headers
;
159 scoped_ptr
<SpdyHttpStream
> http_stream(new SpdyHttpStream(session_
, true));
160 // Make sure getting load timing information the stream early does not crash.
161 LoadTimingInfo load_timing_info
;
162 EXPECT_FALSE(http_stream
->GetLoadTimingInfo(&load_timing_info
));
166 http_stream
->InitializeStream(&request
, DEFAULT_PRIORITY
,
167 net_log
, CompletionCallback()));
168 EXPECT_FALSE(http_stream
->GetLoadTimingInfo(&load_timing_info
));
170 EXPECT_EQ(ERR_IO_PENDING
, http_stream
->SendRequest(headers
, &response
,
171 callback
.callback()));
172 EXPECT_TRUE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
173 EXPECT_FALSE(http_stream
->GetLoadTimingInfo(&load_timing_info
));
175 callback
.WaitForResult();
177 // Can get timing information once the stream connects.
178 TestLoadTimingNotReused(*http_stream
);
180 // Because we abandoned the stream, we don't expect to find a session in the
182 EXPECT_FALSE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
184 TestLoadTimingNotReused(*http_stream
);
185 http_stream
->Close(true);
186 // Test that there's no crash when trying to get the load timing after the
187 // stream has been closed.
188 TestLoadTimingNotReused(*http_stream
);
191 TEST_P(SpdyHttpStreamTest
, LoadTimingTwoRequests
) {
192 scoped_ptr
<SpdyFrame
> req1(
193 spdy_util_
.ConstructSpdyGet(NULL
, 0, false, 1, LOWEST
, true));
194 scoped_ptr
<SpdyFrame
> req2(
195 spdy_util_
.ConstructSpdyGet(NULL
, 0, false, 3, LOWEST
, true));
196 MockWrite writes
[] = {
197 CreateMockWrite(*req1
, 0),
198 CreateMockWrite(*req2
, 1),
200 scoped_ptr
<SpdyFrame
> resp1(
201 spdy_util_
.ConstructSpdyGetSynReply(NULL
, 0, 1));
202 scoped_ptr
<SpdyFrame
> body1(
203 spdy_util_
.ConstructSpdyBodyFrame(1, "", 0, true));
204 scoped_ptr
<SpdyFrame
> resp2(
205 spdy_util_
.ConstructSpdyGetSynReply(NULL
, 0, 3));
206 scoped_ptr
<SpdyFrame
> body2(
207 spdy_util_
.ConstructSpdyBodyFrame(3, "", 0, true));
209 CreateMockRead(*resp1
, 2),
210 CreateMockRead(*body1
, 3),
211 CreateMockRead(*resp2
, 4),
212 CreateMockRead(*body2
, 5),
213 MockRead(ASYNC
, 0, 6) // EOF
216 HostPortPair
host_port_pair("www.example.org", 80);
217 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
218 PRIVACY_MODE_DISABLED
);
219 InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
), key
);
221 HttpRequestInfo request1
;
222 request1
.method
= "GET";
223 request1
.url
= GURL("http://www.example.org/");
224 TestCompletionCallback callback1
;
225 HttpResponseInfo response1
;
226 HttpRequestHeaders headers1
;
227 scoped_ptr
<SpdyHttpStream
> http_stream1(new SpdyHttpStream(session_
, true));
229 HttpRequestInfo request2
;
230 request2
.method
= "GET";
231 request2
.url
= GURL("http://www.example.org/");
232 TestCompletionCallback callback2
;
233 HttpResponseInfo response2
;
234 HttpRequestHeaders headers2
;
235 scoped_ptr
<SpdyHttpStream
> http_stream2(new SpdyHttpStream(session_
, true));
239 http_stream1
->InitializeStream(&request1
, DEFAULT_PRIORITY
,
241 CompletionCallback()));
242 EXPECT_EQ(ERR_IO_PENDING
, http_stream1
->SendRequest(headers1
, &response1
,
243 callback1
.callback()));
244 EXPECT_TRUE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
246 EXPECT_LE(0, callback1
.WaitForResult());
248 TestLoadTimingNotReused(*http_stream1
);
249 LoadTimingInfo load_timing_info1
;
250 LoadTimingInfo load_timing_info2
;
251 EXPECT_TRUE(http_stream1
->GetLoadTimingInfo(&load_timing_info1
));
252 EXPECT_FALSE(http_stream2
->GetLoadTimingInfo(&load_timing_info2
));
256 http_stream2
->InitializeStream(&request2
, DEFAULT_PRIORITY
,
258 CompletionCallback()));
259 EXPECT_EQ(ERR_IO_PENDING
, http_stream2
->SendRequest(headers2
, &response2
,
260 callback2
.callback()));
261 EXPECT_TRUE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
263 EXPECT_LE(0, callback2
.WaitForResult());
264 TestLoadTimingReused(*http_stream2
);
265 EXPECT_TRUE(http_stream2
->GetLoadTimingInfo(&load_timing_info2
));
266 EXPECT_EQ(load_timing_info1
.socket_log_id
, load_timing_info2
.socket_log_id
);
268 // Read stream 1 to completion, before making sure we can still read load
269 // timing from both streams.
270 scoped_refptr
<IOBuffer
> buf1(new IOBuffer(1));
272 0, http_stream1
->ReadResponseBody(buf1
.get(), 1, callback1
.callback()));
274 // Stream 1 has been read to completion.
275 TestLoadTimingNotReused(*http_stream1
);
276 // Stream 2 still has queued body data.
277 TestLoadTimingReused(*http_stream2
);
280 TEST_P(SpdyHttpStreamTest
, SendChunkedPost
) {
281 BufferedSpdyFramer
framer(spdy_util_
.spdy_version(), false);
283 scoped_ptr
<SpdyFrame
> req(
284 spdy_util_
.ConstructChunkedSpdyPost(NULL
, 0));
285 scoped_ptr
<SpdyFrame
> body(
286 framer
.CreateDataFrame(1, kUploadData
, kUploadDataSize
, DATA_FLAG_FIN
));
287 MockWrite writes
[] = {
288 CreateMockWrite(*req
, 0), // request
289 CreateMockWrite(*body
, 1) // POST upload frame
292 scoped_ptr
<SpdyFrame
> resp(spdy_util_
.ConstructSpdyPostSynReply(NULL
, 0));
294 CreateMockRead(*resp
, 2),
295 CreateMockRead(*body
, 3),
296 MockRead(SYNCHRONOUS
, 0, 4) // EOF
299 HostPortPair
host_port_pair("www.example.org", 80);
300 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
301 PRIVACY_MODE_DISABLED
);
302 InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
), key
);
303 EXPECT_EQ(spdy_util_
.spdy_version(), session_
->GetProtocolVersion());
305 ChunkedUploadDataStream
upload_stream(0);
306 const int kFirstChunkSize
= kUploadDataSize
/2;
307 upload_stream
.AppendData(kUploadData
, kFirstChunkSize
, false);
308 upload_stream
.AppendData(kUploadData
+ kFirstChunkSize
,
309 kUploadDataSize
- kFirstChunkSize
, true);
311 HttpRequestInfo request
;
312 request
.method
= "POST";
313 request
.url
= GURL("http://www.example.org/");
314 request
.upload_data_stream
= &upload_stream
;
316 ASSERT_EQ(OK
, upload_stream
.Init(TestCompletionCallback().callback()));
318 TestCompletionCallback callback
;
319 HttpResponseInfo response
;
320 HttpRequestHeaders headers
;
322 SpdyHttpStream
http_stream(session_
, true);
325 http_stream
.InitializeStream(&request
, DEFAULT_PRIORITY
,
326 net_log
, CompletionCallback()));
328 EXPECT_EQ(ERR_IO_PENDING
, http_stream
.SendRequest(
329 headers
, &response
, callback
.callback()));
330 EXPECT_TRUE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
332 EXPECT_EQ(OK
, callback
.WaitForResult());
334 // Because the server closed the connection, we there shouldn't be a session
335 // in the pool anymore.
336 EXPECT_FALSE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
339 TEST_P(SpdyHttpStreamTest
, ConnectionClosedDuringChunkedPost
) {
340 BufferedSpdyFramer
framer(spdy_util_
.spdy_version(), false);
342 scoped_ptr
<SpdyFrame
> req(spdy_util_
.ConstructChunkedSpdyPost(NULL
, 0));
343 scoped_ptr
<SpdyFrame
> body(
344 framer
.CreateDataFrame(1, kUploadData
, kUploadDataSize
, DATA_FLAG_NONE
));
345 MockWrite writes
[] = {
346 CreateMockWrite(*req
, 0), // Request
347 CreateMockWrite(*body
, 1) // First POST upload frame
350 scoped_ptr
<SpdyFrame
> resp(spdy_util_
.ConstructSpdyPostSynReply(NULL
, 0));
352 MockRead(ASYNC
, ERR_CONNECTION_CLOSED
, 2) // Server hangs up early.
355 HostPortPair
host_port_pair("www.example.org", 80);
356 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
357 PRIVACY_MODE_DISABLED
);
358 InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
), key
);
359 EXPECT_EQ(spdy_util_
.spdy_version(), session_
->GetProtocolVersion());
361 ChunkedUploadDataStream
upload_stream(0);
362 // Append first chunk.
363 upload_stream
.AppendData(kUploadData
, kUploadDataSize
, false);
365 HttpRequestInfo request
;
366 request
.method
= "POST";
367 request
.url
= GURL("http://www.example.org/");
368 request
.upload_data_stream
= &upload_stream
;
370 ASSERT_EQ(OK
, upload_stream
.Init(TestCompletionCallback().callback()));
372 TestCompletionCallback callback
;
373 HttpResponseInfo response
;
374 HttpRequestHeaders headers
;
376 SpdyHttpStream
http_stream(session_
, true);
377 ASSERT_EQ(OK
, http_stream
.InitializeStream(&request
, DEFAULT_PRIORITY
,
378 net_log
, CompletionCallback()));
380 EXPECT_EQ(ERR_IO_PENDING
,
381 http_stream
.SendRequest(headers
, &response
, callback
.callback()));
382 EXPECT_TRUE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
384 EXPECT_EQ(OK
, callback
.WaitForResult());
386 // Because the server closed the connection, we there shouldn't be a session
387 // in the pool anymore.
388 EXPECT_FALSE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
390 // Appending a second chunk now should not result in a crash.
391 upload_stream
.AppendData(kUploadData
, kUploadDataSize
, true);
392 // Appending data is currently done synchronously, but seems best to be
394 base::RunLoop().RunUntilIdle();
397 // Test to ensure the SpdyStream state machine does not get confused when a
398 // chunk becomes available while a write is pending.
399 TEST_P(SpdyHttpStreamTest
, DelayedSendChunkedPost
) {
400 const char kUploadData1
[] = "12345678";
401 const int kUploadData1Size
= arraysize(kUploadData1
)-1;
402 scoped_ptr
<SpdyFrame
> req(spdy_util_
.ConstructChunkedSpdyPost(NULL
, 0));
403 scoped_ptr
<SpdyFrame
> chunk1(spdy_util_
.ConstructSpdyBodyFrame(1, false));
404 scoped_ptr
<SpdyFrame
> chunk2(
405 spdy_util_
.ConstructSpdyBodyFrame(
406 1, kUploadData1
, kUploadData1Size
, false));
407 scoped_ptr
<SpdyFrame
> chunk3(spdy_util_
.ConstructSpdyBodyFrame(1, true));
408 MockWrite writes
[] = {
409 CreateMockWrite(*req
.get(), 0),
410 CreateMockWrite(*chunk1
, 1), // POST upload frames
411 CreateMockWrite(*chunk2
, 2),
412 CreateMockWrite(*chunk3
, 3),
414 scoped_ptr
<SpdyFrame
> resp(spdy_util_
.ConstructSpdyPostSynReply(NULL
, 0));
416 CreateMockRead(*resp
, 4),
417 CreateMockRead(*chunk1
, 5),
418 CreateMockRead(*chunk2
, 6),
419 CreateMockRead(*chunk3
, 7),
420 MockRead(ASYNC
, 0, 8) // EOF
423 HostPortPair
host_port_pair("www.example.org", 80);
424 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
425 PRIVACY_MODE_DISABLED
);
426 InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
), key
);
428 ChunkedUploadDataStream
upload_stream(0);
430 HttpRequestInfo request
;
431 request
.method
= "POST";
432 request
.url
= GURL("http://www.example.org/");
433 request
.upload_data_stream
= &upload_stream
;
435 ASSERT_EQ(OK
, upload_stream
.Init(TestCompletionCallback().callback()));
436 upload_stream
.AppendData(kUploadData
, kUploadDataSize
, false);
439 scoped_ptr
<SpdyHttpStream
> http_stream(new SpdyHttpStream(session_
, true));
440 ASSERT_EQ(OK
, http_stream
->InitializeStream(&request
, DEFAULT_PRIORITY
,
441 net_log
, CompletionCallback()));
443 TestCompletionCallback callback
;
444 HttpRequestHeaders headers
;
445 HttpResponseInfo response
;
446 // This will attempt to Write() the initial request and headers, which will
447 // complete asynchronously.
448 EXPECT_EQ(ERR_IO_PENDING
, http_stream
->SendRequest(headers
, &response
,
449 callback
.callback()));
450 EXPECT_TRUE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
452 // Complete the initial request write and the first chunk.
453 base::RunLoop().RunUntilIdle();
454 ASSERT_TRUE(callback
.have_result());
455 EXPECT_EQ(OK
, callback
.WaitForResult());
457 // Now append the final two chunks which will enqueue two more writes.
458 upload_stream
.AppendData(kUploadData1
, kUploadData1Size
, false);
459 upload_stream
.AppendData(kUploadData
, kUploadDataSize
, true);
461 // Finish writing all the chunks and do all reads.
462 base::RunLoop().RunUntilIdle();
464 // Check response headers.
465 ASSERT_EQ(OK
, http_stream
->ReadResponseHeaders(callback
.callback()));
467 // Check |chunk1| response.
468 scoped_refptr
<IOBuffer
> buf1(new IOBuffer(kUploadDataSize
));
469 ASSERT_EQ(kUploadDataSize
,
470 http_stream
->ReadResponseBody(
471 buf1
.get(), kUploadDataSize
, callback
.callback()));
472 EXPECT_EQ(kUploadData
, std::string(buf1
->data(), kUploadDataSize
));
474 // Check |chunk2| response.
475 scoped_refptr
<IOBuffer
> buf2(new IOBuffer(kUploadData1Size
));
476 ASSERT_EQ(kUploadData1Size
,
477 http_stream
->ReadResponseBody(
478 buf2
.get(), kUploadData1Size
, callback
.callback()));
479 EXPECT_EQ(kUploadData1
, std::string(buf2
->data(), kUploadData1Size
));
481 // Check |chunk3| response.
482 scoped_refptr
<IOBuffer
> buf3(new IOBuffer(kUploadDataSize
));
483 ASSERT_EQ(kUploadDataSize
,
484 http_stream
->ReadResponseBody(
485 buf3
.get(), kUploadDataSize
, callback
.callback()));
486 EXPECT_EQ(kUploadData
, std::string(buf3
->data(), kUploadDataSize
));
488 ASSERT_TRUE(response
.headers
.get());
489 ASSERT_EQ(200, response
.headers
->response_code());
492 // Test that the SpdyStream state machine can handle sending a final empty data
493 // frame when uploading a chunked data stream.
494 TEST_P(SpdyHttpStreamTest
, DelayedSendChunkedPostWithEmptyFinalDataFrame
) {
495 scoped_ptr
<SpdyFrame
> req(spdy_util_
.ConstructChunkedSpdyPost(NULL
, 0));
496 scoped_ptr
<SpdyFrame
> chunk1(spdy_util_
.ConstructSpdyBodyFrame(1, false));
497 scoped_ptr
<SpdyFrame
> chunk2(
498 spdy_util_
.ConstructSpdyBodyFrame(1, "", 0, true));
499 MockWrite writes
[] = {
500 CreateMockWrite(*req
.get(), 0),
501 CreateMockWrite(*chunk1
, 1), // POST upload frames
502 CreateMockWrite(*chunk2
, 2),
504 scoped_ptr
<SpdyFrame
> resp(spdy_util_
.ConstructSpdyPostSynReply(NULL
, 0));
506 CreateMockRead(*resp
, 3),
507 CreateMockRead(*chunk1
, 4),
508 CreateMockRead(*chunk2
, 5),
509 MockRead(ASYNC
, 0, 6) // EOF
512 HostPortPair
host_port_pair("www.example.org", 80);
513 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
514 PRIVACY_MODE_DISABLED
);
515 InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
), key
);
517 ChunkedUploadDataStream
upload_stream(0);
519 HttpRequestInfo request
;
520 request
.method
= "POST";
521 request
.url
= GURL("http://www.example.org/");
522 request
.upload_data_stream
= &upload_stream
;
524 ASSERT_EQ(OK
, upload_stream
.Init(TestCompletionCallback().callback()));
525 upload_stream
.AppendData(kUploadData
, kUploadDataSize
, false);
528 scoped_ptr
<SpdyHttpStream
> http_stream(new SpdyHttpStream(session_
, true));
529 ASSERT_EQ(OK
, http_stream
->InitializeStream(&request
, DEFAULT_PRIORITY
,
530 net_log
, CompletionCallback()));
532 TestCompletionCallback callback
;
533 HttpRequestHeaders headers
;
534 HttpResponseInfo response
;
535 // This will attempt to Write() the initial request and headers, which will
536 // complete asynchronously.
537 EXPECT_EQ(ERR_IO_PENDING
, http_stream
->SendRequest(headers
, &response
,
538 callback
.callback()));
539 EXPECT_TRUE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
541 // Complete the initial request write and the first chunk.
542 base::RunLoop().RunUntilIdle();
543 ASSERT_TRUE(callback
.have_result());
544 EXPECT_EQ(OK
, callback
.WaitForResult());
546 // Now end the stream with an empty data frame and the FIN set.
547 upload_stream
.AppendData(NULL
, 0, true);
549 // Finish writing the final frame, and perform all reads.
550 base::RunLoop().RunUntilIdle();
552 // Check response headers.
553 ASSERT_EQ(OK
, http_stream
->ReadResponseHeaders(callback
.callback()));
555 // Check |chunk1| response.
556 scoped_refptr
<IOBuffer
> buf1(new IOBuffer(kUploadDataSize
));
557 ASSERT_EQ(kUploadDataSize
,
558 http_stream
->ReadResponseBody(
559 buf1
.get(), kUploadDataSize
, callback
.callback()));
560 EXPECT_EQ(kUploadData
, std::string(buf1
->data(), kUploadDataSize
));
562 // Check |chunk2| response.
564 http_stream
->ReadResponseBody(
565 buf1
.get(), kUploadDataSize
, callback
.callback()));
567 ASSERT_TRUE(response
.headers
.get());
568 ASSERT_EQ(200, response
.headers
->response_code());
571 // Test that the SpdyStream state machine handles a chunked upload with no
572 // payload. Unclear if this is a case worth supporting.
573 TEST_P(SpdyHttpStreamTest
, ChunkedPostWithEmptyPayload
) {
574 scoped_ptr
<SpdyFrame
> req(spdy_util_
.ConstructChunkedSpdyPost(NULL
, 0));
575 scoped_ptr
<SpdyFrame
> chunk(
576 spdy_util_
.ConstructSpdyBodyFrame(1, "", 0, true));
577 MockWrite writes
[] = {
578 CreateMockWrite(*req
.get(), 0),
579 CreateMockWrite(*chunk
, 1),
581 scoped_ptr
<SpdyFrame
> resp(spdy_util_
.ConstructSpdyPostSynReply(NULL
, 0));
583 CreateMockRead(*resp
, 2),
584 CreateMockRead(*chunk
, 3),
585 MockRead(ASYNC
, 0, 4) // EOF
588 HostPortPair
host_port_pair("www.example.org", 80);
589 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
590 PRIVACY_MODE_DISABLED
);
591 InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
), key
);
593 ChunkedUploadDataStream
upload_stream(0);
595 HttpRequestInfo request
;
596 request
.method
= "POST";
597 request
.url
= GURL("http://www.example.org/");
598 request
.upload_data_stream
= &upload_stream
;
600 ASSERT_EQ(OK
, upload_stream
.Init(TestCompletionCallback().callback()));
601 upload_stream
.AppendData("", 0, true);
604 scoped_ptr
<SpdyHttpStream
> http_stream(new SpdyHttpStream(session_
, true));
605 ASSERT_EQ(OK
, http_stream
->InitializeStream(&request
, DEFAULT_PRIORITY
,
606 net_log
, CompletionCallback()));
608 TestCompletionCallback callback
;
609 HttpRequestHeaders headers
;
610 HttpResponseInfo response
;
611 // This will attempt to Write() the initial request and headers, which will
612 // complete asynchronously.
613 EXPECT_EQ(ERR_IO_PENDING
, http_stream
->SendRequest(headers
, &response
,
614 callback
.callback()));
615 EXPECT_TRUE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
617 // Complete writing request, followed by a FIN.
618 base::RunLoop().RunUntilIdle();
619 ASSERT_TRUE(callback
.have_result());
620 EXPECT_EQ(OK
, callback
.WaitForResult());
622 // Check response headers.
623 ASSERT_EQ(OK
, http_stream
->ReadResponseHeaders(callback
.callback()));
625 // Check |chunk| response.
626 scoped_refptr
<IOBuffer
> buf(new IOBuffer(1));
628 http_stream
->ReadResponseBody(
629 buf
.get(), 1, callback
.callback()));
631 ASSERT_TRUE(response
.headers
.get());
632 ASSERT_EQ(200, response
.headers
->response_code());
635 // Test case for bug: http://code.google.com/p/chromium/issues/detail?id=50058
636 TEST_P(SpdyHttpStreamTest
, SpdyURLTest
) {
637 const char* const full_url
= "http://www.example.org/foo?query=what#anchor";
638 const char* const base_url
= "http://www.example.org/foo?query=what";
639 scoped_ptr
<SpdyFrame
> req(
640 spdy_util_
.ConstructSpdyGet(base_url
, false, 1, LOWEST
));
641 MockWrite writes
[] = {
642 CreateMockWrite(*req
.get(), 0),
644 scoped_ptr
<SpdyFrame
> resp(spdy_util_
.ConstructSpdyGetSynReply(NULL
, 0, 1));
646 CreateMockRead(*resp
, 1), MockRead(SYNCHRONOUS
, 0, 2) // EOF
649 HostPortPair
host_port_pair("www.example.org", 80);
650 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
651 PRIVACY_MODE_DISABLED
);
652 InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
), key
);
654 HttpRequestInfo request
;
655 request
.method
= "GET";
656 request
.url
= GURL(full_url
);
657 TestCompletionCallback callback
;
658 HttpResponseInfo response
;
659 HttpRequestHeaders headers
;
661 scoped_ptr
<SpdyHttpStream
> http_stream(new SpdyHttpStream(session_
, true));
663 http_stream
->InitializeStream(
664 &request
, DEFAULT_PRIORITY
, net_log
, CompletionCallback()));
666 EXPECT_EQ(ERR_IO_PENDING
, http_stream
->SendRequest(headers
, &response
,
667 callback
.callback()));
669 EXPECT_EQ(base_url
, http_stream
->stream()->GetUrlFromHeaders().spec());
671 callback
.WaitForResult();
673 // Because we abandoned the stream, we don't expect to find a session in the
675 EXPECT_FALSE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
678 // Test the receipt of a WINDOW_UPDATE frame while waiting for a chunk to be
679 // made available is handled correctly.
680 TEST_P(SpdyHttpStreamTest
, DelayedSendChunkedPostWithWindowUpdate
) {
681 scoped_ptr
<SpdyFrame
> req(spdy_util_
.ConstructChunkedSpdyPost(NULL
, 0));
682 scoped_ptr
<SpdyFrame
> chunk1(spdy_util_
.ConstructSpdyBodyFrame(1, true));
683 MockWrite writes
[] = {
684 CreateMockWrite(*req
.get(), 0),
685 CreateMockWrite(*chunk1
, 1),
687 scoped_ptr
<SpdyFrame
> resp(spdy_util_
.ConstructSpdyPostSynReply(NULL
, 0));
688 scoped_ptr
<SpdyFrame
> window_update(
689 spdy_util_
.ConstructSpdyWindowUpdate(1, kUploadDataSize
));
691 CreateMockRead(*window_update
, 2),
692 MockRead(ASYNC
, ERR_IO_PENDING
, 3),
693 CreateMockRead(*resp
, 4),
694 CreateMockRead(*chunk1
, 5),
695 MockRead(ASYNC
, 0, 6) // EOF
698 HostPortPair
host_port_pair("www.example.org", 80);
699 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
700 PRIVACY_MODE_DISABLED
);
702 InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
), key
);
704 ChunkedUploadDataStream
upload_stream(0);
706 HttpRequestInfo request
;
707 request
.method
= "POST";
708 request
.url
= GURL("http://www.example.org/");
709 request
.upload_data_stream
= &upload_stream
;
711 ASSERT_EQ(OK
, upload_stream
.Init(TestCompletionCallback().callback()));
714 scoped_ptr
<SpdyHttpStream
> http_stream(new SpdyHttpStream(session_
, true));
715 ASSERT_EQ(OK
, http_stream
->InitializeStream(&request
, DEFAULT_PRIORITY
,
716 net_log
, CompletionCallback()));
718 HttpRequestHeaders headers
;
719 HttpResponseInfo response
;
720 // This will attempt to Write() the initial request and headers, which will
721 // complete asynchronously.
722 TestCompletionCallback callback
;
723 EXPECT_EQ(ERR_IO_PENDING
, http_stream
->SendRequest(headers
, &response
,
724 callback
.callback()));
725 EXPECT_TRUE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
727 // Complete the initial request write and first chunk.
728 base::RunLoop().RunUntilIdle();
729 ASSERT_TRUE(callback
.have_result());
730 EXPECT_EQ(OK
, callback
.WaitForResult());
732 upload_stream
.AppendData(kUploadData
, kUploadDataSize
, true);
734 // Verify that the window size has decreased.
735 ASSERT_TRUE(http_stream
->stream() != NULL
);
736 EXPECT_NE(static_cast<int>(
737 SpdySession::GetDefaultInitialWindowSize(session_
->protocol())),
738 http_stream
->stream()->send_window_size());
740 // Read window update.
741 base::RunLoop().RunUntilIdle();
743 // Verify the window update.
744 ASSERT_TRUE(http_stream
->stream() != NULL
);
745 EXPECT_EQ(static_cast<int>(
746 SpdySession::GetDefaultInitialWindowSize(session_
->protocol())),
747 http_stream
->stream()->send_window_size());
749 // Read rest of data.
750 sequenced_data_
->CompleteRead();
751 base::RunLoop().RunUntilIdle();
753 // Check response headers.
754 ASSERT_EQ(OK
, http_stream
->ReadResponseHeaders(callback
.callback()));
756 // Check |chunk1| response.
757 scoped_refptr
<IOBuffer
> buf1(new IOBuffer(kUploadDataSize
));
758 ASSERT_EQ(kUploadDataSize
,
759 http_stream
->ReadResponseBody(
760 buf1
.get(), kUploadDataSize
, callback
.callback()));
761 EXPECT_EQ(kUploadData
, std::string(buf1
->data(), kUploadDataSize
));
763 ASSERT_TRUE(response
.headers
.get());
764 ASSERT_EQ(200, response
.headers
->response_code());
767 // TODO(willchan): Write a longer test for SpdyStream that exercises all