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
,
116 // SpdyHttpStream::GetUploadProgress() should still work even before the
117 // stream is initialized.
118 TEST_P(SpdyHttpStreamTest
, GetUploadProgressBeforeInitialization
) {
120 MockRead(ASYNC
, 0, 0) // EOF
123 HostPortPair
host_port_pair("www.example.org", 80);
124 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
125 PRIVACY_MODE_DISABLED
);
126 InitSession(reads
, arraysize(reads
), NULL
, 0, key
);
128 SpdyHttpStream
stream(session_
, false);
129 UploadProgress progress
= stream
.GetUploadProgress();
130 EXPECT_EQ(0u, progress
.size());
131 EXPECT_EQ(0u, progress
.position());
133 // Pump the event loop so |reads| is consumed before the function returns.
134 base::RunLoop().RunUntilIdle();
137 TEST_P(SpdyHttpStreamTest
, SendRequest
) {
138 scoped_ptr
<SpdyFrame
> req(
139 spdy_util_
.ConstructSpdyGet(NULL
, 0, false, 1, LOWEST
, true));
140 MockWrite writes
[] = {
141 CreateMockWrite(*req
.get(), 0),
143 scoped_ptr
<SpdyFrame
> resp(spdy_util_
.ConstructSpdyGetSynReply(NULL
, 0, 1));
145 CreateMockRead(*resp
, 1), MockRead(SYNCHRONOUS
, 0, 2) // EOF
148 HostPortPair
host_port_pair("www.example.org", 80);
149 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
150 PRIVACY_MODE_DISABLED
);
151 InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
), key
);
153 HttpRequestInfo request
;
154 request
.method
= "GET";
155 request
.url
= GURL("http://www.example.org/");
156 TestCompletionCallback callback
;
157 HttpResponseInfo response
;
158 HttpRequestHeaders headers
;
160 scoped_ptr
<SpdyHttpStream
> http_stream(new SpdyHttpStream(session_
, true));
161 // Make sure getting load timing information the stream early does not crash.
162 LoadTimingInfo load_timing_info
;
163 EXPECT_FALSE(http_stream
->GetLoadTimingInfo(&load_timing_info
));
167 http_stream
->InitializeStream(&request
, DEFAULT_PRIORITY
,
168 net_log
, CompletionCallback()));
169 EXPECT_FALSE(http_stream
->GetLoadTimingInfo(&load_timing_info
));
171 EXPECT_EQ(ERR_IO_PENDING
, http_stream
->SendRequest(headers
, &response
,
172 callback
.callback()));
173 EXPECT_TRUE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
174 EXPECT_FALSE(http_stream
->GetLoadTimingInfo(&load_timing_info
));
176 callback
.WaitForResult();
178 // Can get timing information once the stream connects.
179 TestLoadTimingNotReused(*http_stream
);
181 // Because we abandoned the stream, we don't expect to find a session in the
183 EXPECT_FALSE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
185 TestLoadTimingNotReused(*http_stream
);
186 http_stream
->Close(true);
187 // Test that there's no crash when trying to get the load timing after the
188 // stream has been closed.
189 TestLoadTimingNotReused(*http_stream
);
192 TEST_P(SpdyHttpStreamTest
, LoadTimingTwoRequests
) {
193 scoped_ptr
<SpdyFrame
> req1(
194 spdy_util_
.ConstructSpdyGet(NULL
, 0, false, 1, LOWEST
, true));
195 scoped_ptr
<SpdyFrame
> req2(
196 spdy_util_
.ConstructSpdyGet(NULL
, 0, false, 3, LOWEST
, true));
197 MockWrite writes
[] = {
198 CreateMockWrite(*req1
, 0),
199 CreateMockWrite(*req2
, 1),
201 scoped_ptr
<SpdyFrame
> resp1(
202 spdy_util_
.ConstructSpdyGetSynReply(NULL
, 0, 1));
203 scoped_ptr
<SpdyFrame
> body1(
204 spdy_util_
.ConstructSpdyBodyFrame(1, "", 0, true));
205 scoped_ptr
<SpdyFrame
> resp2(
206 spdy_util_
.ConstructSpdyGetSynReply(NULL
, 0, 3));
207 scoped_ptr
<SpdyFrame
> body2(
208 spdy_util_
.ConstructSpdyBodyFrame(3, "", 0, true));
210 CreateMockRead(*resp1
, 2),
211 CreateMockRead(*body1
, 3),
212 CreateMockRead(*resp2
, 4),
213 CreateMockRead(*body2
, 5),
214 MockRead(ASYNC
, 0, 6) // EOF
217 HostPortPair
host_port_pair("www.example.org", 80);
218 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
219 PRIVACY_MODE_DISABLED
);
220 InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
), key
);
222 HttpRequestInfo request1
;
223 request1
.method
= "GET";
224 request1
.url
= GURL("http://www.example.org/");
225 TestCompletionCallback callback1
;
226 HttpResponseInfo response1
;
227 HttpRequestHeaders headers1
;
228 scoped_ptr
<SpdyHttpStream
> http_stream1(new SpdyHttpStream(session_
, true));
230 HttpRequestInfo request2
;
231 request2
.method
= "GET";
232 request2
.url
= GURL("http://www.example.org/");
233 TestCompletionCallback callback2
;
234 HttpResponseInfo response2
;
235 HttpRequestHeaders headers2
;
236 scoped_ptr
<SpdyHttpStream
> http_stream2(new SpdyHttpStream(session_
, true));
240 http_stream1
->InitializeStream(&request1
, DEFAULT_PRIORITY
,
242 CompletionCallback()));
243 EXPECT_EQ(ERR_IO_PENDING
, http_stream1
->SendRequest(headers1
, &response1
,
244 callback1
.callback()));
245 EXPECT_TRUE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
247 EXPECT_LE(0, callback1
.WaitForResult());
249 TestLoadTimingNotReused(*http_stream1
);
250 LoadTimingInfo load_timing_info1
;
251 LoadTimingInfo load_timing_info2
;
252 EXPECT_TRUE(http_stream1
->GetLoadTimingInfo(&load_timing_info1
));
253 EXPECT_FALSE(http_stream2
->GetLoadTimingInfo(&load_timing_info2
));
257 http_stream2
->InitializeStream(&request2
, DEFAULT_PRIORITY
,
259 CompletionCallback()));
260 EXPECT_EQ(ERR_IO_PENDING
, http_stream2
->SendRequest(headers2
, &response2
,
261 callback2
.callback()));
262 EXPECT_TRUE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
264 EXPECT_LE(0, callback2
.WaitForResult());
265 TestLoadTimingReused(*http_stream2
);
266 EXPECT_TRUE(http_stream2
->GetLoadTimingInfo(&load_timing_info2
));
267 EXPECT_EQ(load_timing_info1
.socket_log_id
, load_timing_info2
.socket_log_id
);
269 // Read stream 1 to completion, before making sure we can still read load
270 // timing from both streams.
271 scoped_refptr
<IOBuffer
> buf1(new IOBuffer(1));
273 0, http_stream1
->ReadResponseBody(buf1
.get(), 1, callback1
.callback()));
275 // Stream 1 has been read to completion.
276 TestLoadTimingNotReused(*http_stream1
);
277 // Stream 2 still has queued body data.
278 TestLoadTimingReused(*http_stream2
);
281 TEST_P(SpdyHttpStreamTest
, SendChunkedPost
) {
282 BufferedSpdyFramer
framer(spdy_util_
.spdy_version(), false);
284 scoped_ptr
<SpdyFrame
> req(
285 spdy_util_
.ConstructChunkedSpdyPost(NULL
, 0));
286 scoped_ptr
<SpdyFrame
> body(
287 framer
.CreateDataFrame(1, kUploadData
, kUploadDataSize
, DATA_FLAG_FIN
));
288 MockWrite writes
[] = {
289 CreateMockWrite(*req
, 0), // request
290 CreateMockWrite(*body
, 1) // POST upload frame
293 scoped_ptr
<SpdyFrame
> resp(spdy_util_
.ConstructSpdyPostSynReply(NULL
, 0));
295 CreateMockRead(*resp
, 2),
296 CreateMockRead(*body
, 3),
297 MockRead(SYNCHRONOUS
, 0, 4) // EOF
300 HostPortPair
host_port_pair("www.example.org", 80);
301 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
302 PRIVACY_MODE_DISABLED
);
303 InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
), key
);
304 EXPECT_EQ(spdy_util_
.spdy_version(), session_
->GetProtocolVersion());
306 ChunkedUploadDataStream
upload_stream(0);
307 const int kFirstChunkSize
= kUploadDataSize
/2;
308 upload_stream
.AppendData(kUploadData
, kFirstChunkSize
, false);
309 upload_stream
.AppendData(kUploadData
+ kFirstChunkSize
,
310 kUploadDataSize
- kFirstChunkSize
, true);
312 HttpRequestInfo request
;
313 request
.method
= "POST";
314 request
.url
= GURL("http://www.example.org/");
315 request
.upload_data_stream
= &upload_stream
;
317 ASSERT_EQ(OK
, upload_stream
.Init(TestCompletionCallback().callback()));
319 TestCompletionCallback callback
;
320 HttpResponseInfo response
;
321 HttpRequestHeaders headers
;
323 SpdyHttpStream
http_stream(session_
, true);
326 http_stream
.InitializeStream(&request
, DEFAULT_PRIORITY
,
327 net_log
, CompletionCallback()));
329 EXPECT_EQ(ERR_IO_PENDING
, http_stream
.SendRequest(
330 headers
, &response
, callback
.callback()));
331 EXPECT_TRUE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
333 EXPECT_EQ(OK
, callback
.WaitForResult());
335 // Because the server closed the connection, we there shouldn't be a session
336 // in the pool anymore.
337 EXPECT_FALSE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
340 TEST_P(SpdyHttpStreamTest
, ConnectionClosedDuringChunkedPost
) {
341 BufferedSpdyFramer
framer(spdy_util_
.spdy_version(), false);
343 scoped_ptr
<SpdyFrame
> req(spdy_util_
.ConstructChunkedSpdyPost(NULL
, 0));
344 scoped_ptr
<SpdyFrame
> body(
345 framer
.CreateDataFrame(1, kUploadData
, kUploadDataSize
, DATA_FLAG_NONE
));
346 MockWrite writes
[] = {
347 CreateMockWrite(*req
, 0), // Request
348 CreateMockWrite(*body
, 1) // First POST upload frame
351 scoped_ptr
<SpdyFrame
> resp(spdy_util_
.ConstructSpdyPostSynReply(NULL
, 0));
353 MockRead(ASYNC
, ERR_CONNECTION_CLOSED
, 2) // Server hangs up early.
356 HostPortPair
host_port_pair("www.example.org", 80);
357 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
358 PRIVACY_MODE_DISABLED
);
359 InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
), key
);
360 EXPECT_EQ(spdy_util_
.spdy_version(), session_
->GetProtocolVersion());
362 ChunkedUploadDataStream
upload_stream(0);
363 // Append first chunk.
364 upload_stream
.AppendData(kUploadData
, kUploadDataSize
, false);
366 HttpRequestInfo request
;
367 request
.method
= "POST";
368 request
.url
= GURL("http://www.example.org/");
369 request
.upload_data_stream
= &upload_stream
;
371 ASSERT_EQ(OK
, upload_stream
.Init(TestCompletionCallback().callback()));
373 TestCompletionCallback callback
;
374 HttpResponseInfo response
;
375 HttpRequestHeaders headers
;
377 SpdyHttpStream
http_stream(session_
, true);
378 ASSERT_EQ(OK
, http_stream
.InitializeStream(&request
, DEFAULT_PRIORITY
,
379 net_log
, CompletionCallback()));
381 EXPECT_EQ(ERR_IO_PENDING
,
382 http_stream
.SendRequest(headers
, &response
, callback
.callback()));
383 EXPECT_TRUE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
385 EXPECT_EQ(OK
, callback
.WaitForResult());
387 // Because the server closed the connection, we there shouldn't be a session
388 // in the pool anymore.
389 EXPECT_FALSE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
391 // Appending a second chunk now should not result in a crash.
392 upload_stream
.AppendData(kUploadData
, kUploadDataSize
, true);
393 // Appending data is currently done synchronously, but seems best to be
395 base::RunLoop().RunUntilIdle();
398 // Test to ensure the SpdyStream state machine does not get confused when a
399 // chunk becomes available while a write is pending.
400 TEST_P(SpdyHttpStreamTest
, DelayedSendChunkedPost
) {
401 const char kUploadData1
[] = "12345678";
402 const int kUploadData1Size
= arraysize(kUploadData1
)-1;
403 scoped_ptr
<SpdyFrame
> req(spdy_util_
.ConstructChunkedSpdyPost(NULL
, 0));
404 scoped_ptr
<SpdyFrame
> chunk1(spdy_util_
.ConstructSpdyBodyFrame(1, false));
405 scoped_ptr
<SpdyFrame
> chunk2(
406 spdy_util_
.ConstructSpdyBodyFrame(
407 1, kUploadData1
, kUploadData1Size
, false));
408 scoped_ptr
<SpdyFrame
> chunk3(spdy_util_
.ConstructSpdyBodyFrame(1, true));
409 MockWrite writes
[] = {
410 CreateMockWrite(*req
.get(), 0),
411 CreateMockWrite(*chunk1
, 1), // POST upload frames
412 CreateMockWrite(*chunk2
, 2),
413 CreateMockWrite(*chunk3
, 3),
415 scoped_ptr
<SpdyFrame
> resp(spdy_util_
.ConstructSpdyPostSynReply(NULL
, 0));
417 CreateMockRead(*resp
, 4),
418 CreateMockRead(*chunk1
, 5),
419 CreateMockRead(*chunk2
, 6),
420 CreateMockRead(*chunk3
, 7),
421 MockRead(ASYNC
, 0, 8) // EOF
424 HostPortPair
host_port_pair("www.example.org", 80);
425 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
426 PRIVACY_MODE_DISABLED
);
427 InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
), key
);
429 ChunkedUploadDataStream
upload_stream(0);
431 HttpRequestInfo request
;
432 request
.method
= "POST";
433 request
.url
= GURL("http://www.example.org/");
434 request
.upload_data_stream
= &upload_stream
;
436 ASSERT_EQ(OK
, upload_stream
.Init(TestCompletionCallback().callback()));
437 upload_stream
.AppendData(kUploadData
, kUploadDataSize
, false);
440 scoped_ptr
<SpdyHttpStream
> http_stream(new SpdyHttpStream(session_
, true));
441 ASSERT_EQ(OK
, http_stream
->InitializeStream(&request
, DEFAULT_PRIORITY
,
442 net_log
, CompletionCallback()));
444 TestCompletionCallback callback
;
445 HttpRequestHeaders headers
;
446 HttpResponseInfo response
;
447 // This will attempt to Write() the initial request and headers, which will
448 // complete asynchronously.
449 EXPECT_EQ(ERR_IO_PENDING
, http_stream
->SendRequest(headers
, &response
,
450 callback
.callback()));
451 EXPECT_TRUE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
453 // Complete the initial request write and the first chunk.
454 base::RunLoop().RunUntilIdle();
455 ASSERT_TRUE(callback
.have_result());
456 EXPECT_EQ(OK
, callback
.WaitForResult());
458 // Now append the final two chunks which will enqueue two more writes.
459 upload_stream
.AppendData(kUploadData1
, kUploadData1Size
, false);
460 upload_stream
.AppendData(kUploadData
, kUploadDataSize
, true);
462 // Finish writing all the chunks and do all reads.
463 base::RunLoop().RunUntilIdle();
465 // Check response headers.
466 ASSERT_EQ(OK
, http_stream
->ReadResponseHeaders(callback
.callback()));
468 // Check |chunk1| response.
469 scoped_refptr
<IOBuffer
> buf1(new IOBuffer(kUploadDataSize
));
470 ASSERT_EQ(kUploadDataSize
,
471 http_stream
->ReadResponseBody(
472 buf1
.get(), kUploadDataSize
, callback
.callback()));
473 EXPECT_EQ(kUploadData
, std::string(buf1
->data(), kUploadDataSize
));
475 // Check |chunk2| response.
476 scoped_refptr
<IOBuffer
> buf2(new IOBuffer(kUploadData1Size
));
477 ASSERT_EQ(kUploadData1Size
,
478 http_stream
->ReadResponseBody(
479 buf2
.get(), kUploadData1Size
, callback
.callback()));
480 EXPECT_EQ(kUploadData1
, std::string(buf2
->data(), kUploadData1Size
));
482 // Check |chunk3| response.
483 scoped_refptr
<IOBuffer
> buf3(new IOBuffer(kUploadDataSize
));
484 ASSERT_EQ(kUploadDataSize
,
485 http_stream
->ReadResponseBody(
486 buf3
.get(), kUploadDataSize
, callback
.callback()));
487 EXPECT_EQ(kUploadData
, std::string(buf3
->data(), kUploadDataSize
));
489 ASSERT_TRUE(response
.headers
.get());
490 ASSERT_EQ(200, response
.headers
->response_code());
493 // Test that the SpdyStream state machine can handle sending a final empty data
494 // frame when uploading a chunked data stream.
495 TEST_P(SpdyHttpStreamTest
, DelayedSendChunkedPostWithEmptyFinalDataFrame
) {
496 scoped_ptr
<SpdyFrame
> req(spdy_util_
.ConstructChunkedSpdyPost(NULL
, 0));
497 scoped_ptr
<SpdyFrame
> chunk1(spdy_util_
.ConstructSpdyBodyFrame(1, false));
498 scoped_ptr
<SpdyFrame
> chunk2(
499 spdy_util_
.ConstructSpdyBodyFrame(1, "", 0, true));
500 MockWrite writes
[] = {
501 CreateMockWrite(*req
.get(), 0),
502 CreateMockWrite(*chunk1
, 1), // POST upload frames
503 CreateMockWrite(*chunk2
, 2),
505 scoped_ptr
<SpdyFrame
> resp(spdy_util_
.ConstructSpdyPostSynReply(NULL
, 0));
507 CreateMockRead(*resp
, 3),
508 CreateMockRead(*chunk1
, 4),
509 CreateMockRead(*chunk2
, 5),
510 MockRead(ASYNC
, 0, 6) // EOF
513 HostPortPair
host_port_pair("www.example.org", 80);
514 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
515 PRIVACY_MODE_DISABLED
);
516 InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
), key
);
518 ChunkedUploadDataStream
upload_stream(0);
520 HttpRequestInfo request
;
521 request
.method
= "POST";
522 request
.url
= GURL("http://www.example.org/");
523 request
.upload_data_stream
= &upload_stream
;
525 ASSERT_EQ(OK
, upload_stream
.Init(TestCompletionCallback().callback()));
526 upload_stream
.AppendData(kUploadData
, kUploadDataSize
, false);
529 scoped_ptr
<SpdyHttpStream
> http_stream(new SpdyHttpStream(session_
, true));
530 ASSERT_EQ(OK
, http_stream
->InitializeStream(&request
, DEFAULT_PRIORITY
,
531 net_log
, CompletionCallback()));
533 TestCompletionCallback callback
;
534 HttpRequestHeaders headers
;
535 HttpResponseInfo response
;
536 // This will attempt to Write() the initial request and headers, which will
537 // complete asynchronously.
538 EXPECT_EQ(ERR_IO_PENDING
, http_stream
->SendRequest(headers
, &response
,
539 callback
.callback()));
540 EXPECT_TRUE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
542 // Complete the initial request write and the first chunk.
543 base::RunLoop().RunUntilIdle();
544 ASSERT_TRUE(callback
.have_result());
545 EXPECT_EQ(OK
, callback
.WaitForResult());
547 // Now end the stream with an empty data frame and the FIN set.
548 upload_stream
.AppendData(NULL
, 0, true);
550 // Finish writing the final frame, and perform all reads.
551 base::RunLoop().RunUntilIdle();
553 // Check response headers.
554 ASSERT_EQ(OK
, http_stream
->ReadResponseHeaders(callback
.callback()));
556 // Check |chunk1| response.
557 scoped_refptr
<IOBuffer
> buf1(new IOBuffer(kUploadDataSize
));
558 ASSERT_EQ(kUploadDataSize
,
559 http_stream
->ReadResponseBody(
560 buf1
.get(), kUploadDataSize
, callback
.callback()));
561 EXPECT_EQ(kUploadData
, std::string(buf1
->data(), kUploadDataSize
));
563 // Check |chunk2| response.
565 http_stream
->ReadResponseBody(
566 buf1
.get(), kUploadDataSize
, callback
.callback()));
568 ASSERT_TRUE(response
.headers
.get());
569 ASSERT_EQ(200, response
.headers
->response_code());
572 // Test that the SpdyStream state machine handles a chunked upload with no
573 // payload. Unclear if this is a case worth supporting.
574 TEST_P(SpdyHttpStreamTest
, ChunkedPostWithEmptyPayload
) {
575 scoped_ptr
<SpdyFrame
> req(spdy_util_
.ConstructChunkedSpdyPost(NULL
, 0));
576 scoped_ptr
<SpdyFrame
> chunk(
577 spdy_util_
.ConstructSpdyBodyFrame(1, "", 0, true));
578 MockWrite writes
[] = {
579 CreateMockWrite(*req
.get(), 0),
580 CreateMockWrite(*chunk
, 1),
582 scoped_ptr
<SpdyFrame
> resp(spdy_util_
.ConstructSpdyPostSynReply(NULL
, 0));
584 CreateMockRead(*resp
, 2),
585 CreateMockRead(*chunk
, 3),
586 MockRead(ASYNC
, 0, 4) // EOF
589 HostPortPair
host_port_pair("www.example.org", 80);
590 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
591 PRIVACY_MODE_DISABLED
);
592 InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
), key
);
594 ChunkedUploadDataStream
upload_stream(0);
596 HttpRequestInfo request
;
597 request
.method
= "POST";
598 request
.url
= GURL("http://www.example.org/");
599 request
.upload_data_stream
= &upload_stream
;
601 ASSERT_EQ(OK
, upload_stream
.Init(TestCompletionCallback().callback()));
602 upload_stream
.AppendData("", 0, true);
605 scoped_ptr
<SpdyHttpStream
> http_stream(new SpdyHttpStream(session_
, true));
606 ASSERT_EQ(OK
, http_stream
->InitializeStream(&request
, DEFAULT_PRIORITY
,
607 net_log
, CompletionCallback()));
609 TestCompletionCallback callback
;
610 HttpRequestHeaders headers
;
611 HttpResponseInfo response
;
612 // This will attempt to Write() the initial request and headers, which will
613 // complete asynchronously.
614 EXPECT_EQ(ERR_IO_PENDING
, http_stream
->SendRequest(headers
, &response
,
615 callback
.callback()));
616 EXPECT_TRUE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
618 // Complete writing request, followed by a FIN.
619 base::RunLoop().RunUntilIdle();
620 ASSERT_TRUE(callback
.have_result());
621 EXPECT_EQ(OK
, callback
.WaitForResult());
623 // Check response headers.
624 ASSERT_EQ(OK
, http_stream
->ReadResponseHeaders(callback
.callback()));
626 // Check |chunk| response.
627 scoped_refptr
<IOBuffer
> buf(new IOBuffer(1));
629 http_stream
->ReadResponseBody(
630 buf
.get(), 1, callback
.callback()));
632 ASSERT_TRUE(response
.headers
.get());
633 ASSERT_EQ(200, response
.headers
->response_code());
636 // Test case for bug: http://code.google.com/p/chromium/issues/detail?id=50058
637 TEST_P(SpdyHttpStreamTest
, SpdyURLTest
) {
638 const char* const full_url
= "http://www.example.org/foo?query=what#anchor";
639 const char* const base_url
= "http://www.example.org/foo?query=what";
640 scoped_ptr
<SpdyFrame
> req(
641 spdy_util_
.ConstructSpdyGet(base_url
, false, 1, LOWEST
));
642 MockWrite writes
[] = {
643 CreateMockWrite(*req
.get(), 0),
645 scoped_ptr
<SpdyFrame
> resp(spdy_util_
.ConstructSpdyGetSynReply(NULL
, 0, 1));
647 CreateMockRead(*resp
, 1), MockRead(SYNCHRONOUS
, 0, 2) // EOF
650 HostPortPair
host_port_pair("www.example.org", 80);
651 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
652 PRIVACY_MODE_DISABLED
);
653 InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
), key
);
655 HttpRequestInfo request
;
656 request
.method
= "GET";
657 request
.url
= GURL(full_url
);
658 TestCompletionCallback callback
;
659 HttpResponseInfo response
;
660 HttpRequestHeaders headers
;
662 scoped_ptr
<SpdyHttpStream
> http_stream(new SpdyHttpStream(session_
, true));
664 http_stream
->InitializeStream(
665 &request
, DEFAULT_PRIORITY
, net_log
, CompletionCallback()));
667 EXPECT_EQ(ERR_IO_PENDING
, http_stream
->SendRequest(headers
, &response
,
668 callback
.callback()));
670 EXPECT_EQ(base_url
, http_stream
->stream()->GetUrlFromHeaders().spec());
672 callback
.WaitForResult();
674 // Because we abandoned the stream, we don't expect to find a session in the
676 EXPECT_FALSE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
679 // Test the receipt of a WINDOW_UPDATE frame while waiting for a chunk to be
680 // made available is handled correctly.
681 TEST_P(SpdyHttpStreamTest
, DelayedSendChunkedPostWithWindowUpdate
) {
682 scoped_ptr
<SpdyFrame
> req(spdy_util_
.ConstructChunkedSpdyPost(NULL
, 0));
683 scoped_ptr
<SpdyFrame
> chunk1(spdy_util_
.ConstructSpdyBodyFrame(1, true));
684 MockWrite writes
[] = {
685 CreateMockWrite(*req
.get(), 0),
686 CreateMockWrite(*chunk1
, 1),
688 scoped_ptr
<SpdyFrame
> resp(spdy_util_
.ConstructSpdyPostSynReply(NULL
, 0));
689 scoped_ptr
<SpdyFrame
> window_update(
690 spdy_util_
.ConstructSpdyWindowUpdate(1, kUploadDataSize
));
692 CreateMockRead(*window_update
, 2),
693 MockRead(ASYNC
, ERR_IO_PENDING
, 3),
694 CreateMockRead(*resp
, 4),
695 CreateMockRead(*chunk1
, 5),
696 MockRead(ASYNC
, 0, 6) // EOF
699 HostPortPair
host_port_pair("www.example.org", 80);
700 SpdySessionKey
key(host_port_pair
, ProxyServer::Direct(),
701 PRIVACY_MODE_DISABLED
);
703 InitSession(reads
, arraysize(reads
), writes
, arraysize(writes
), key
);
705 ChunkedUploadDataStream
upload_stream(0);
707 HttpRequestInfo request
;
708 request
.method
= "POST";
709 request
.url
= GURL("http://www.example.org/");
710 request
.upload_data_stream
= &upload_stream
;
712 ASSERT_EQ(OK
, upload_stream
.Init(TestCompletionCallback().callback()));
715 scoped_ptr
<SpdyHttpStream
> http_stream(new SpdyHttpStream(session_
, true));
716 ASSERT_EQ(OK
, http_stream
->InitializeStream(&request
, DEFAULT_PRIORITY
,
717 net_log
, CompletionCallback()));
719 HttpRequestHeaders headers
;
720 HttpResponseInfo response
;
721 // This will attempt to Write() the initial request and headers, which will
722 // complete asynchronously.
723 TestCompletionCallback callback
;
724 EXPECT_EQ(ERR_IO_PENDING
, http_stream
->SendRequest(headers
, &response
,
725 callback
.callback()));
726 EXPECT_TRUE(HasSpdySession(http_session_
->spdy_session_pool(), key
));
728 // Complete the initial request write and first chunk.
729 base::RunLoop().RunUntilIdle();
730 ASSERT_TRUE(callback
.have_result());
731 EXPECT_EQ(OK
, callback
.WaitForResult());
733 upload_stream
.AppendData(kUploadData
, kUploadDataSize
, true);
735 // Verify that the window size has decreased.
736 ASSERT_TRUE(http_stream
->stream() != NULL
);
737 EXPECT_NE(static_cast<int>(
738 SpdySession::GetDefaultInitialWindowSize(session_
->protocol())),
739 http_stream
->stream()->send_window_size());
741 // Read window update.
742 base::RunLoop().RunUntilIdle();
744 // Verify the window update.
745 ASSERT_TRUE(http_stream
->stream() != NULL
);
746 EXPECT_EQ(static_cast<int>(
747 SpdySession::GetDefaultInitialWindowSize(session_
->protocol())),
748 http_stream
->stream()->send_window_size());
750 // Read rest of data.
751 sequenced_data_
->CompleteRead();
752 base::RunLoop().RunUntilIdle();
754 // Check response headers.
755 ASSERT_EQ(OK
, http_stream
->ReadResponseHeaders(callback
.callback()));
757 // Check |chunk1| response.
758 scoped_refptr
<IOBuffer
> buf1(new IOBuffer(kUploadDataSize
));
759 ASSERT_EQ(kUploadDataSize
,
760 http_stream
->ReadResponseBody(
761 buf1
.get(), kUploadDataSize
, callback
.callback()));
762 EXPECT_EQ(kUploadData
, std::string(buf1
->data(), kUploadDataSize
));
764 ASSERT_TRUE(response
.headers
.get());
765 ASSERT_EQ(200, response
.headers
->response_code());
768 // TODO(willchan): Write a longer test for SpdyStream that exercises all