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"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/message_loop/message_loop.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/chunked_upload_data_stream.h"
17 #include "net/base/load_timing_info.h"
18 #include "net/base/load_timing_info_test_util.h"
19 #include "net/base/test_completion_callback.h"
20 #include "net/cert/asn1_util.h"
21 #include "net/http/http_request_info.h"
22 #include "net/http/http_response_headers.h"
23 #include "net/http/http_response_info.h"
24 #include "net/log/test_net_log.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"
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
));
43 EXPECT_TRUE(load_timing_info
.socket_reused
);
44 EXPECT_NE(NetLog::Source::kInvalidId
, load_timing_info
.socket_log_id
);
46 ExpectConnectTimingHasNoTimes(load_timing_info
.connect_timing
);
47 ExpectLoadTimingHasOnlyConnectionTimes(load_timing_info
);
50 // Tests the load timing of a stream that's connected and using a fresh
52 void TestLoadTimingNotReused(const HttpStream
& stream
) {
53 LoadTimingInfo load_timing_info
;
54 EXPECT_TRUE(stream
.GetLoadTimingInfo(&load_timing_info
));
56 EXPECT_FALSE(load_timing_info
.socket_reused
);
57 EXPECT_NE(NetLog::Source::kInvalidId
, load_timing_info
.socket_log_id
);
59 ExpectConnectTimingHasTimes(load_timing_info
.connect_timing
,
60 CONNECT_TIMING_HAS_DNS_TIMES
);
61 ExpectLoadTimingHasOnlyConnectionTimes(load_timing_info
);
66 class SpdyHttpStreamTest
: public testing::Test
,
67 public testing::WithParamInterface
<NextProto
> {
70 : spdy_util_(GetParam()),
71 session_deps_(GetParam()) {
72 session_deps_
.net_log
= &net_log_
;
76 void TearDown() override
{
77 crypto::ECSignatureCreator::SetFactoryForTesting(NULL
);
78 base::MessageLoop::current()->RunUntilIdle();
79 EXPECT_TRUE(sequenced_data_
->AllReadDataConsumed());
80 EXPECT_TRUE(sequenced_data_
->AllWriteDataConsumed());
83 // Initializes the session using SequencedSocketData.
84 void InitSession(MockRead
* reads
,
88 const SpdySessionKey
& key
) {
89 sequenced_data_
.reset(
90 new SequencedSocketData(reads
, reads_count
, writes
, writes_count
));
91 session_deps_
.socket_factory
->AddSocketDataProvider(sequenced_data_
.get());
92 http_session_
= SpdySessionDependencies::SpdyCreateSession(&session_deps_
);
93 session_
= CreateInsecureSpdySession(http_session_
, key
, BoundNetLog());
96 void TestSendCredentials(
97 ChannelIDService
* channel_id_service
,
98 const std::string
& cert
,
99 const std::string
& proof
);
101 SpdyTestUtil spdy_util_
;
103 SpdySessionDependencies session_deps_
;
104 scoped_ptr
<SequencedSocketData
> sequenced_data_
;
105 scoped_refptr
<HttpNetworkSession
> http_session_
;
106 base::WeakPtr
<SpdySession
> session_
;
109 MockECSignatureCreatorFactory ec_signature_creator_factory_
;
112 INSTANTIATE_TEST_CASE_P(NextProto
,
114 testing::Values(kProtoSPDY31
,
117 // SpdyHttpStream::GetUploadProgress() should still work even before the
118 // stream is initialized.
119 TEST_P(SpdyHttpStreamTest
, GetUploadProgressBeforeInitialization
) {
121 MockRead(ASYNC
, 0, 0) // EOF
124 HostPortPair
host_port_pair("www.example.org", 80);
125 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
126 PRIVACY_MODE_DISABLED
);
127 InitSession(reads
, arraysize(reads
), NULL
, 0, key
);
129 SpdyHttpStream
stream(session_
, false);
130 UploadProgress progress
= stream
.GetUploadProgress();
131 EXPECT_EQ(0u, progress
.size());
132 EXPECT_EQ(0u, progress
.position());
134 // Pump the event loop so |reads| is consumed before the function returns.
135 base::RunLoop().RunUntilIdle();
138 TEST_P(SpdyHttpStreamTest
, SendRequest
) {
139 scoped_ptr
<SpdyFrame
> req(
140 spdy_util_
.ConstructSpdyGet(NULL
, 0, false, 1, LOWEST
, true));
141 MockWrite writes
[] = {
142 CreateMockWrite(*req
.get(), 0),
144 scoped_ptr
<SpdyFrame
> resp(spdy_util_
.ConstructSpdyGetSynReply(NULL
, 0, 1));
146 CreateMockRead(*resp
, 1), MockRead(SYNCHRONOUS
, 0, 2) // EOF
149 HostPortPair
host_port_pair("www.example.org", 80);
150 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
151 PRIVACY_MODE_DISABLED
);
152 InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
), key
);
154 HttpRequestInfo request
;
155 request
.method
= "GET";
156 request
.url
= GURL("http://www.example.org/");
157 TestCompletionCallback callback
;
158 HttpResponseInfo response
;
159 HttpRequestHeaders headers
;
161 scoped_ptr
<SpdyHttpStream
> http_stream(new SpdyHttpStream(session_
, true));
162 // Make sure getting load timing information the stream early does not crash.
163 LoadTimingInfo load_timing_info
;
164 EXPECT_FALSE(http_stream
->GetLoadTimingInfo(&load_timing_info
));
168 http_stream
->InitializeStream(&request
, DEFAULT_PRIORITY
,
169 net_log
, CompletionCallback()));
170 EXPECT_FALSE(http_stream
->GetLoadTimingInfo(&load_timing_info
));
172 EXPECT_EQ(ERR_IO_PENDING
, http_stream
->SendRequest(headers
, &response
,
173 callback
.callback()));
174 EXPECT_TRUE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
175 EXPECT_FALSE(http_stream
->GetLoadTimingInfo(&load_timing_info
));
177 callback
.WaitForResult();
179 // Can get timing information once the stream connects.
180 TestLoadTimingNotReused(*http_stream
);
182 // Because we abandoned the stream, we don't expect to find a session in the
184 EXPECT_FALSE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
186 TestLoadTimingNotReused(*http_stream
);
187 http_stream
->Close(true);
188 // Test that there's no crash when trying to get the load timing after the
189 // stream has been closed.
190 TestLoadTimingNotReused(*http_stream
);
192 EXPECT_EQ(static_cast<int64_t>(req
->size()),
193 http_stream
->GetTotalSentBytes());
194 EXPECT_EQ(static_cast<int64_t>(resp
->size()),
195 http_stream
->GetTotalReceivedBytes());
198 TEST_P(SpdyHttpStreamTest
, LoadTimingTwoRequests
) {
199 scoped_ptr
<SpdyFrame
> req1(
200 spdy_util_
.ConstructSpdyGet(NULL
, 0, false, 1, LOWEST
, true));
201 scoped_ptr
<SpdyFrame
> req2(
202 spdy_util_
.ConstructSpdyGet(NULL
, 0, false, 3, LOWEST
, true));
203 MockWrite writes
[] = {
204 CreateMockWrite(*req1
, 0),
205 CreateMockWrite(*req2
, 1),
207 scoped_ptr
<SpdyFrame
> resp1(
208 spdy_util_
.ConstructSpdyGetSynReply(NULL
, 0, 1));
209 scoped_ptr
<SpdyFrame
> body1(
210 spdy_util_
.ConstructSpdyBodyFrame(1, "", 0, true));
211 scoped_ptr
<SpdyFrame
> resp2(
212 spdy_util_
.ConstructSpdyGetSynReply(NULL
, 0, 3));
213 scoped_ptr
<SpdyFrame
> body2(
214 spdy_util_
.ConstructSpdyBodyFrame(3, "", 0, true));
216 CreateMockRead(*resp1
, 2),
217 CreateMockRead(*body1
, 3),
218 CreateMockRead(*resp2
, 4),
219 CreateMockRead(*body2
, 5),
220 MockRead(ASYNC
, 0, 6) // EOF
223 HostPortPair
host_port_pair("www.example.org", 80);
224 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
225 PRIVACY_MODE_DISABLED
);
226 InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
), key
);
228 HttpRequestInfo request1
;
229 request1
.method
= "GET";
230 request1
.url
= GURL("http://www.example.org/");
231 TestCompletionCallback callback1
;
232 HttpResponseInfo response1
;
233 HttpRequestHeaders headers1
;
234 scoped_ptr
<SpdyHttpStream
> http_stream1(new SpdyHttpStream(session_
, true));
236 HttpRequestInfo request2
;
237 request2
.method
= "GET";
238 request2
.url
= GURL("http://www.example.org/");
239 TestCompletionCallback callback2
;
240 HttpResponseInfo response2
;
241 HttpRequestHeaders headers2
;
242 scoped_ptr
<SpdyHttpStream
> http_stream2(new SpdyHttpStream(session_
, true));
246 http_stream1
->InitializeStream(&request1
, DEFAULT_PRIORITY
,
248 CompletionCallback()));
249 EXPECT_EQ(ERR_IO_PENDING
, http_stream1
->SendRequest(headers1
, &response1
,
250 callback1
.callback()));
251 EXPECT_TRUE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
253 EXPECT_LE(0, callback1
.WaitForResult());
255 TestLoadTimingNotReused(*http_stream1
);
256 LoadTimingInfo load_timing_info1
;
257 LoadTimingInfo load_timing_info2
;
258 EXPECT_TRUE(http_stream1
->GetLoadTimingInfo(&load_timing_info1
));
259 EXPECT_FALSE(http_stream2
->GetLoadTimingInfo(&load_timing_info2
));
263 http_stream2
->InitializeStream(&request2
, DEFAULT_PRIORITY
,
265 CompletionCallback()));
266 EXPECT_EQ(ERR_IO_PENDING
, http_stream2
->SendRequest(headers2
, &response2
,
267 callback2
.callback()));
268 EXPECT_TRUE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
270 EXPECT_LE(0, callback2
.WaitForResult());
271 TestLoadTimingReused(*http_stream2
);
272 EXPECT_TRUE(http_stream2
->GetLoadTimingInfo(&load_timing_info2
));
273 EXPECT_EQ(load_timing_info1
.socket_log_id
, load_timing_info2
.socket_log_id
);
275 // Read stream 1 to completion, before making sure we can still read load
276 // timing from both streams.
277 scoped_refptr
<IOBuffer
> buf1(new IOBuffer(1));
279 0, http_stream1
->ReadResponseBody(buf1
.get(), 1, callback1
.callback()));
281 // Stream 1 has been read to completion.
282 TestLoadTimingNotReused(*http_stream1
);
284 EXPECT_EQ(static_cast<int64_t>(req1
->size()),
285 http_stream1
->GetTotalSentBytes());
286 EXPECT_EQ(static_cast<int64_t>(resp1
->size() + body1
->size()),
287 http_stream1
->GetTotalReceivedBytes());
289 // Stream 2 still has queued body data.
290 TestLoadTimingReused(*http_stream2
);
292 EXPECT_EQ(static_cast<int64_t>(req2
->size()),
293 http_stream2
->GetTotalSentBytes());
294 EXPECT_EQ(static_cast<int64_t>(resp2
->size() + body2
->size()),
295 http_stream2
->GetTotalReceivedBytes());
298 TEST_P(SpdyHttpStreamTest
, SendChunkedPost
) {
299 BufferedSpdyFramer
framer(spdy_util_
.spdy_version(), false);
301 scoped_ptr
<SpdyFrame
> req(
302 spdy_util_
.ConstructChunkedSpdyPost(NULL
, 0));
303 scoped_ptr
<SpdyFrame
> body(
304 framer
.CreateDataFrame(1, kUploadData
, kUploadDataSize
, DATA_FLAG_FIN
));
305 MockWrite writes
[] = {
306 CreateMockWrite(*req
, 0), // request
307 CreateMockWrite(*body
, 1) // POST upload frame
310 scoped_ptr
<SpdyFrame
> resp(spdy_util_
.ConstructSpdyPostSynReply(NULL
, 0));
312 CreateMockRead(*resp
, 2),
313 CreateMockRead(*body
, 3),
314 MockRead(SYNCHRONOUS
, 0, 4) // EOF
317 HostPortPair
host_port_pair("www.example.org", 80);
318 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
319 PRIVACY_MODE_DISABLED
);
320 InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
), key
);
321 EXPECT_EQ(spdy_util_
.spdy_version(), session_
->GetProtocolVersion());
323 ChunkedUploadDataStream
upload_stream(0);
324 const int kFirstChunkSize
= kUploadDataSize
/2;
325 upload_stream
.AppendData(kUploadData
, kFirstChunkSize
, false);
326 upload_stream
.AppendData(kUploadData
+ kFirstChunkSize
,
327 kUploadDataSize
- kFirstChunkSize
, true);
329 HttpRequestInfo request
;
330 request
.method
= "POST";
331 request
.url
= GURL("http://www.example.org/");
332 request
.upload_data_stream
= &upload_stream
;
334 ASSERT_EQ(OK
, upload_stream
.Init(TestCompletionCallback().callback()));
336 TestCompletionCallback callback
;
337 HttpResponseInfo response
;
338 HttpRequestHeaders headers
;
340 SpdyHttpStream
http_stream(session_
, true);
343 http_stream
.InitializeStream(&request
, DEFAULT_PRIORITY
,
344 net_log
, CompletionCallback()));
346 EXPECT_EQ(ERR_IO_PENDING
, http_stream
.SendRequest(
347 headers
, &response
, callback
.callback()));
348 EXPECT_TRUE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
350 EXPECT_EQ(OK
, callback
.WaitForResult());
352 EXPECT_EQ(static_cast<int64_t>(req
->size() + body
->size()),
353 http_stream
.GetTotalSentBytes());
354 EXPECT_EQ(static_cast<int64_t>(resp
->size() + body
->size()),
355 http_stream
.GetTotalReceivedBytes());
357 // Because the server closed the connection, we there shouldn't be a session
358 // in the pool anymore.
359 EXPECT_FALSE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
362 TEST_P(SpdyHttpStreamTest
, ConnectionClosedDuringChunkedPost
) {
363 BufferedSpdyFramer
framer(spdy_util_
.spdy_version(), false);
365 scoped_ptr
<SpdyFrame
> req(spdy_util_
.ConstructChunkedSpdyPost(NULL
, 0));
366 scoped_ptr
<SpdyFrame
> body(
367 framer
.CreateDataFrame(1, kUploadData
, kUploadDataSize
, DATA_FLAG_NONE
));
368 MockWrite writes
[] = {
369 CreateMockWrite(*req
, 0), // Request
370 CreateMockWrite(*body
, 1) // First POST upload frame
373 scoped_ptr
<SpdyFrame
> resp(spdy_util_
.ConstructSpdyPostSynReply(NULL
, 0));
375 MockRead(ASYNC
, ERR_CONNECTION_CLOSED
, 2) // Server hangs up early.
378 HostPortPair
host_port_pair("www.example.org", 80);
379 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
380 PRIVACY_MODE_DISABLED
);
381 InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
), key
);
382 EXPECT_EQ(spdy_util_
.spdy_version(), session_
->GetProtocolVersion());
384 ChunkedUploadDataStream
upload_stream(0);
385 // Append first chunk.
386 upload_stream
.AppendData(kUploadData
, kUploadDataSize
, false);
388 HttpRequestInfo request
;
389 request
.method
= "POST";
390 request
.url
= GURL("http://www.example.org/");
391 request
.upload_data_stream
= &upload_stream
;
393 ASSERT_EQ(OK
, upload_stream
.Init(TestCompletionCallback().callback()));
395 TestCompletionCallback callback
;
396 HttpResponseInfo response
;
397 HttpRequestHeaders headers
;
399 SpdyHttpStream
http_stream(session_
, true);
400 ASSERT_EQ(OK
, http_stream
.InitializeStream(&request
, DEFAULT_PRIORITY
,
401 net_log
, CompletionCallback()));
403 EXPECT_EQ(ERR_IO_PENDING
,
404 http_stream
.SendRequest(headers
, &response
, callback
.callback()));
405 EXPECT_TRUE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
407 EXPECT_EQ(OK
, callback
.WaitForResult());
409 EXPECT_EQ(static_cast<int64_t>(req
->size() + body
->size()),
410 http_stream
.GetTotalSentBytes());
411 EXPECT_EQ(0, http_stream
.GetTotalReceivedBytes());
413 // Because the server closed the connection, we there shouldn't be a session
414 // in the pool anymore.
415 EXPECT_FALSE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
417 // Appending a second chunk now should not result in a crash.
418 upload_stream
.AppendData(kUploadData
, kUploadDataSize
, true);
419 // Appending data is currently done synchronously, but seems best to be
421 base::RunLoop().RunUntilIdle();
423 // The total sent and received bytes should be unchanged.
424 EXPECT_EQ(static_cast<int64_t>(req
->size() + body
->size()),
425 http_stream
.GetTotalSentBytes());
426 EXPECT_EQ(0, http_stream
.GetTotalReceivedBytes());
429 // Test to ensure the SpdyStream state machine does not get confused when a
430 // chunk becomes available while a write is pending.
431 TEST_P(SpdyHttpStreamTest
, DelayedSendChunkedPost
) {
432 const char kUploadData1
[] = "12345678";
433 const int kUploadData1Size
= arraysize(kUploadData1
)-1;
434 scoped_ptr
<SpdyFrame
> req(spdy_util_
.ConstructChunkedSpdyPost(NULL
, 0));
435 scoped_ptr
<SpdyFrame
> chunk1(spdy_util_
.ConstructSpdyBodyFrame(1, false));
436 scoped_ptr
<SpdyFrame
> chunk2(
437 spdy_util_
.ConstructSpdyBodyFrame(
438 1, kUploadData1
, kUploadData1Size
, false));
439 scoped_ptr
<SpdyFrame
> chunk3(spdy_util_
.ConstructSpdyBodyFrame(1, true));
440 MockWrite writes
[] = {
441 CreateMockWrite(*req
.get(), 0),
442 CreateMockWrite(*chunk1
, 1), // POST upload frames
443 CreateMockWrite(*chunk2
, 2),
444 CreateMockWrite(*chunk3
, 3),
446 scoped_ptr
<SpdyFrame
> resp(spdy_util_
.ConstructSpdyPostSynReply(NULL
, 0));
448 CreateMockRead(*resp
, 4),
449 CreateMockRead(*chunk1
, 5),
450 CreateMockRead(*chunk2
, 6),
451 CreateMockRead(*chunk3
, 7),
452 MockRead(ASYNC
, 0, 8) // EOF
455 HostPortPair
host_port_pair("www.example.org", 80);
456 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
457 PRIVACY_MODE_DISABLED
);
458 InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
), key
);
460 ChunkedUploadDataStream
upload_stream(0);
462 HttpRequestInfo request
;
463 request
.method
= "POST";
464 request
.url
= GURL("http://www.example.org/");
465 request
.upload_data_stream
= &upload_stream
;
467 ASSERT_EQ(OK
, upload_stream
.Init(TestCompletionCallback().callback()));
468 upload_stream
.AppendData(kUploadData
, kUploadDataSize
, false);
471 scoped_ptr
<SpdyHttpStream
> http_stream(new SpdyHttpStream(session_
, true));
472 ASSERT_EQ(OK
, http_stream
->InitializeStream(&request
, DEFAULT_PRIORITY
,
473 net_log
, CompletionCallback()));
475 TestCompletionCallback callback
;
476 HttpRequestHeaders headers
;
477 HttpResponseInfo response
;
478 // This will attempt to Write() the initial request and headers, which will
479 // complete asynchronously.
480 EXPECT_EQ(ERR_IO_PENDING
, http_stream
->SendRequest(headers
, &response
,
481 callback
.callback()));
482 EXPECT_TRUE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
484 // Complete the initial request write and the first chunk.
485 base::RunLoop().RunUntilIdle();
486 ASSERT_TRUE(callback
.have_result());
487 EXPECT_EQ(OK
, callback
.WaitForResult());
489 // Now append the final two chunks which will enqueue two more writes.
490 upload_stream
.AppendData(kUploadData1
, kUploadData1Size
, false);
491 upload_stream
.AppendData(kUploadData
, kUploadDataSize
, true);
493 // Finish writing all the chunks and do all reads.
494 base::RunLoop().RunUntilIdle();
496 EXPECT_EQ(static_cast<int64_t>(req
->size() + chunk1
->size() + chunk2
->size() +
498 http_stream
->GetTotalSentBytes());
499 EXPECT_EQ(static_cast<int64_t>(resp
->size() + chunk1
->size() +
500 chunk2
->size() + chunk3
->size()),
501 http_stream
->GetTotalReceivedBytes());
503 // Check response headers.
504 ASSERT_EQ(OK
, http_stream
->ReadResponseHeaders(callback
.callback()));
506 // Check |chunk1| response.
507 scoped_refptr
<IOBuffer
> buf1(new IOBuffer(kUploadDataSize
));
508 ASSERT_EQ(kUploadDataSize
,
509 http_stream
->ReadResponseBody(
510 buf1
.get(), kUploadDataSize
, callback
.callback()));
511 EXPECT_EQ(kUploadData
, std::string(buf1
->data(), kUploadDataSize
));
513 // Check |chunk2| response.
514 scoped_refptr
<IOBuffer
> buf2(new IOBuffer(kUploadData1Size
));
515 ASSERT_EQ(kUploadData1Size
,
516 http_stream
->ReadResponseBody(
517 buf2
.get(), kUploadData1Size
, callback
.callback()));
518 EXPECT_EQ(kUploadData1
, std::string(buf2
->data(), kUploadData1Size
));
520 // Check |chunk3| response.
521 scoped_refptr
<IOBuffer
> buf3(new IOBuffer(kUploadDataSize
));
522 ASSERT_EQ(kUploadDataSize
,
523 http_stream
->ReadResponseBody(
524 buf3
.get(), kUploadDataSize
, callback
.callback()));
525 EXPECT_EQ(kUploadData
, std::string(buf3
->data(), kUploadDataSize
));
527 ASSERT_TRUE(response
.headers
.get());
528 ASSERT_EQ(200, response
.headers
->response_code());
531 // Test that the SpdyStream state machine can handle sending a final empty data
532 // frame when uploading a chunked data stream.
533 TEST_P(SpdyHttpStreamTest
, DelayedSendChunkedPostWithEmptyFinalDataFrame
) {
534 scoped_ptr
<SpdyFrame
> req(spdy_util_
.ConstructChunkedSpdyPost(NULL
, 0));
535 scoped_ptr
<SpdyFrame
> chunk1(spdy_util_
.ConstructSpdyBodyFrame(1, false));
536 scoped_ptr
<SpdyFrame
> chunk2(
537 spdy_util_
.ConstructSpdyBodyFrame(1, "", 0, true));
538 MockWrite writes
[] = {
539 CreateMockWrite(*req
.get(), 0),
540 CreateMockWrite(*chunk1
, 1), // POST upload frames
541 CreateMockWrite(*chunk2
, 2),
543 scoped_ptr
<SpdyFrame
> resp(spdy_util_
.ConstructSpdyPostSynReply(NULL
, 0));
545 CreateMockRead(*resp
, 3),
546 CreateMockRead(*chunk1
, 4),
547 CreateMockRead(*chunk2
, 5),
548 MockRead(ASYNC
, 0, 6) // EOF
551 HostPortPair
host_port_pair("www.example.org", 80);
552 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
553 PRIVACY_MODE_DISABLED
);
554 InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
), key
);
556 ChunkedUploadDataStream
upload_stream(0);
558 HttpRequestInfo request
;
559 request
.method
= "POST";
560 request
.url
= GURL("http://www.example.org/");
561 request
.upload_data_stream
= &upload_stream
;
563 ASSERT_EQ(OK
, upload_stream
.Init(TestCompletionCallback().callback()));
564 upload_stream
.AppendData(kUploadData
, kUploadDataSize
, false);
567 scoped_ptr
<SpdyHttpStream
> http_stream(new SpdyHttpStream(session_
, true));
568 ASSERT_EQ(OK
, http_stream
->InitializeStream(&request
, DEFAULT_PRIORITY
,
569 net_log
, CompletionCallback()));
571 TestCompletionCallback callback
;
572 HttpRequestHeaders headers
;
573 HttpResponseInfo response
;
574 // This will attempt to Write() the initial request and headers, which will
575 // complete asynchronously.
576 EXPECT_EQ(ERR_IO_PENDING
, http_stream
->SendRequest(headers
, &response
,
577 callback
.callback()));
578 EXPECT_TRUE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
580 // Complete the initial request write and the first chunk.
581 base::RunLoop().RunUntilIdle();
582 ASSERT_TRUE(callback
.have_result());
583 EXPECT_EQ(OK
, callback
.WaitForResult());
585 EXPECT_EQ(static_cast<int64_t>(req
->size() + chunk1
->size()),
586 http_stream
->GetTotalSentBytes());
587 EXPECT_EQ(0, http_stream
->GetTotalReceivedBytes());
589 // Now end the stream with an empty data frame and the FIN set.
590 upload_stream
.AppendData(NULL
, 0, true);
592 // Finish writing the final frame, and perform all reads.
593 base::RunLoop().RunUntilIdle();
595 // Check response headers.
596 ASSERT_EQ(OK
, http_stream
->ReadResponseHeaders(callback
.callback()));
598 EXPECT_EQ(static_cast<int64_t>(req
->size() + chunk1
->size() + chunk2
->size()),
599 http_stream
->GetTotalSentBytes());
601 static_cast<int64_t>(resp
->size() + chunk1
->size() + chunk2
->size()),
602 http_stream
->GetTotalReceivedBytes());
604 // Check |chunk1| response.
605 scoped_refptr
<IOBuffer
> buf1(new IOBuffer(kUploadDataSize
));
606 ASSERT_EQ(kUploadDataSize
,
607 http_stream
->ReadResponseBody(
608 buf1
.get(), kUploadDataSize
, callback
.callback()));
609 EXPECT_EQ(kUploadData
, std::string(buf1
->data(), kUploadDataSize
));
611 // Check |chunk2| response.
613 http_stream
->ReadResponseBody(
614 buf1
.get(), kUploadDataSize
, callback
.callback()));
616 ASSERT_TRUE(response
.headers
.get());
617 ASSERT_EQ(200, response
.headers
->response_code());
620 // Test that the SpdyStream state machine handles a chunked upload with no
621 // payload. Unclear if this is a case worth supporting.
622 TEST_P(SpdyHttpStreamTest
, ChunkedPostWithEmptyPayload
) {
623 scoped_ptr
<SpdyFrame
> req(spdy_util_
.ConstructChunkedSpdyPost(NULL
, 0));
624 scoped_ptr
<SpdyFrame
> chunk(
625 spdy_util_
.ConstructSpdyBodyFrame(1, "", 0, true));
626 MockWrite writes
[] = {
627 CreateMockWrite(*req
.get(), 0),
628 CreateMockWrite(*chunk
, 1),
630 scoped_ptr
<SpdyFrame
> resp(spdy_util_
.ConstructSpdyPostSynReply(NULL
, 0));
632 CreateMockRead(*resp
, 2),
633 CreateMockRead(*chunk
, 3),
634 MockRead(ASYNC
, 0, 4) // EOF
637 HostPortPair
host_port_pair("www.example.org", 80);
638 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
639 PRIVACY_MODE_DISABLED
);
640 InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
), key
);
642 ChunkedUploadDataStream
upload_stream(0);
644 HttpRequestInfo request
;
645 request
.method
= "POST";
646 request
.url
= GURL("http://www.example.org/");
647 request
.upload_data_stream
= &upload_stream
;
649 ASSERT_EQ(OK
, upload_stream
.Init(TestCompletionCallback().callback()));
650 upload_stream
.AppendData("", 0, true);
653 scoped_ptr
<SpdyHttpStream
> http_stream(new SpdyHttpStream(session_
, true));
654 ASSERT_EQ(OK
, http_stream
->InitializeStream(&request
, DEFAULT_PRIORITY
,
655 net_log
, CompletionCallback()));
657 TestCompletionCallback callback
;
658 HttpRequestHeaders headers
;
659 HttpResponseInfo response
;
660 // This will attempt to Write() the initial request and headers, which will
661 // complete asynchronously.
662 EXPECT_EQ(ERR_IO_PENDING
, http_stream
->SendRequest(headers
, &response
,
663 callback
.callback()));
664 EXPECT_TRUE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
666 // Complete writing request, followed by a FIN.
667 base::RunLoop().RunUntilIdle();
668 ASSERT_TRUE(callback
.have_result());
669 EXPECT_EQ(OK
, callback
.WaitForResult());
671 EXPECT_EQ(static_cast<int64_t>(req
->size() + chunk
->size()),
672 http_stream
->GetTotalSentBytes());
673 EXPECT_EQ(static_cast<int64_t>(resp
->size() + chunk
->size()),
674 http_stream
->GetTotalReceivedBytes());
676 // Check response headers.
677 ASSERT_EQ(OK
, http_stream
->ReadResponseHeaders(callback
.callback()));
679 // Check |chunk| response.
680 scoped_refptr
<IOBuffer
> buf(new IOBuffer(1));
682 http_stream
->ReadResponseBody(
683 buf
.get(), 1, callback
.callback()));
685 ASSERT_TRUE(response
.headers
.get());
686 ASSERT_EQ(200, response
.headers
->response_code());
689 // Test case for bug: http://code.google.com/p/chromium/issues/detail?id=50058
690 TEST_P(SpdyHttpStreamTest
, SpdyURLTest
) {
691 const char* const full_url
= "http://www.example.org/foo?query=what#anchor";
692 const char* const base_url
= "http://www.example.org/foo?query=what";
693 scoped_ptr
<SpdyFrame
> req(
694 spdy_util_
.ConstructSpdyGet(base_url
, false, 1, LOWEST
));
695 MockWrite writes
[] = {
696 CreateMockWrite(*req
.get(), 0),
698 scoped_ptr
<SpdyFrame
> resp(spdy_util_
.ConstructSpdyGetSynReply(NULL
, 0, 1));
700 CreateMockRead(*resp
, 1), MockRead(SYNCHRONOUS
, 0, 2) // EOF
703 HostPortPair
host_port_pair("www.example.org", 80);
704 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
705 PRIVACY_MODE_DISABLED
);
706 InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
), key
);
708 HttpRequestInfo request
;
709 request
.method
= "GET";
710 request
.url
= GURL(full_url
);
711 TestCompletionCallback callback
;
712 HttpResponseInfo response
;
713 HttpRequestHeaders headers
;
715 scoped_ptr
<SpdyHttpStream
> http_stream(new SpdyHttpStream(session_
, true));
717 http_stream
->InitializeStream(
718 &request
, DEFAULT_PRIORITY
, net_log
, CompletionCallback()));
720 EXPECT_EQ(ERR_IO_PENDING
, http_stream
->SendRequest(headers
, &response
,
721 callback
.callback()));
723 EXPECT_EQ(base_url
, http_stream
->stream()->GetUrlFromHeaders().spec());
725 callback
.WaitForResult();
727 EXPECT_EQ(static_cast<int64_t>(req
->size()),
728 http_stream
->GetTotalSentBytes());
729 EXPECT_EQ(static_cast<int64_t>(resp
->size()),
730 http_stream
->GetTotalReceivedBytes());
732 // Because we abandoned the stream, we don't expect to find a session in the
734 EXPECT_FALSE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
737 // Test the receipt of a WINDOW_UPDATE frame while waiting for a chunk to be
738 // made available is handled correctly.
739 TEST_P(SpdyHttpStreamTest
, DelayedSendChunkedPostWithWindowUpdate
) {
740 scoped_ptr
<SpdyFrame
> req(spdy_util_
.ConstructChunkedSpdyPost(NULL
, 0));
741 scoped_ptr
<SpdyFrame
> chunk1(spdy_util_
.ConstructSpdyBodyFrame(1, true));
742 MockWrite writes
[] = {
743 CreateMockWrite(*req
.get(), 0),
744 CreateMockWrite(*chunk1
, 1),
746 scoped_ptr
<SpdyFrame
> resp(spdy_util_
.ConstructSpdyPostSynReply(NULL
, 0));
747 scoped_ptr
<SpdyFrame
> window_update(
748 spdy_util_
.ConstructSpdyWindowUpdate(1, kUploadDataSize
));
750 CreateMockRead(*window_update
, 2),
751 MockRead(ASYNC
, ERR_IO_PENDING
, 3),
752 CreateMockRead(*resp
, 4),
753 CreateMockRead(*chunk1
, 5),
754 MockRead(ASYNC
, 0, 6) // EOF
757 HostPortPair
host_port_pair("www.example.org", 80);
758 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
759 PRIVACY_MODE_DISABLED
);
761 InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
), key
);
763 ChunkedUploadDataStream
upload_stream(0);
765 HttpRequestInfo request
;
766 request
.method
= "POST";
767 request
.url
= GURL("http://www.example.org/");
768 request
.upload_data_stream
= &upload_stream
;
770 ASSERT_EQ(OK
, upload_stream
.Init(TestCompletionCallback().callback()));
773 scoped_ptr
<SpdyHttpStream
> http_stream(new SpdyHttpStream(session_
, true));
774 ASSERT_EQ(OK
, http_stream
->InitializeStream(&request
, DEFAULT_PRIORITY
,
775 net_log
, CompletionCallback()));
777 HttpRequestHeaders headers
;
778 HttpResponseInfo response
;
779 // This will attempt to Write() the initial request and headers, which will
780 // complete asynchronously.
781 TestCompletionCallback callback
;
782 EXPECT_EQ(ERR_IO_PENDING
, http_stream
->SendRequest(headers
, &response
,
783 callback
.callback()));
784 EXPECT_TRUE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
786 // Complete the initial request write and first chunk.
787 base::RunLoop().RunUntilIdle();
788 ASSERT_TRUE(callback
.have_result());
789 EXPECT_EQ(OK
, callback
.WaitForResult());
791 EXPECT_EQ(static_cast<int64_t>(req
->size()),
792 http_stream
->GetTotalSentBytes());
793 EXPECT_EQ(0, http_stream
->GetTotalReceivedBytes());
795 upload_stream
.AppendData(kUploadData
, kUploadDataSize
, true);
797 // Verify that the window size has decreased.
798 ASSERT_TRUE(http_stream
->stream() != NULL
);
799 EXPECT_NE(static_cast<int>(
800 SpdySession::GetDefaultInitialWindowSize(session_
->protocol())),
801 http_stream
->stream()->send_window_size());
803 // Read window update.
804 base::RunLoop().RunUntilIdle();
806 EXPECT_EQ(static_cast<int64_t>(req
->size() + chunk1
->size()),
807 http_stream
->GetTotalSentBytes());
808 // The window update is not counted in the total received bytes.
809 EXPECT_EQ(0, http_stream
->GetTotalReceivedBytes());
811 // Verify the window update.
812 ASSERT_TRUE(http_stream
->stream() != NULL
);
813 EXPECT_EQ(static_cast<int>(
814 SpdySession::GetDefaultInitialWindowSize(session_
->protocol())),
815 http_stream
->stream()->send_window_size());
817 // Read rest of data.
818 sequenced_data_
->CompleteRead();
819 base::RunLoop().RunUntilIdle();
821 EXPECT_EQ(static_cast<int64_t>(req
->size() + chunk1
->size()),
822 http_stream
->GetTotalSentBytes());
823 EXPECT_EQ(static_cast<int64_t>(resp
->size() + chunk1
->size()),
824 http_stream
->GetTotalReceivedBytes());
826 // Check response headers.
827 ASSERT_EQ(OK
, http_stream
->ReadResponseHeaders(callback
.callback()));
829 // Check |chunk1| response.
830 scoped_refptr
<IOBuffer
> buf1(new IOBuffer(kUploadDataSize
));
831 ASSERT_EQ(kUploadDataSize
,
832 http_stream
->ReadResponseBody(
833 buf1
.get(), kUploadDataSize
, callback
.callback()));
834 EXPECT_EQ(kUploadData
, std::string(buf1
->data(), kUploadDataSize
));
836 ASSERT_TRUE(response
.headers
.get());
837 ASSERT_EQ(200, response
.headers
->response_code());
840 // TODO(willchan): Write a longer test for SpdyStream that exercises all