diff options
author | erikchen@google.com <erikchen@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-05 17:59:58 +0000 |
---|---|---|
committer | erikchen@google.com <erikchen@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-05 17:59:58 +0000 |
commit | e3ebba0fbbfb2c7eec286a964717859aa70b9fcf (patch) | |
tree | b2a53d996ca4537fbb6516dcddba122d1cb6cbcd /net/spdy | |
parent | 1b7cde151f8e0e922a8c52c6ca48f6f1c6de21ec (diff) | |
download | chromium_src-e3ebba0fbbfb2c7eec286a964717859aa70b9fcf.zip chromium_src-e3ebba0fbbfb2c7eec286a964717859aa70b9fcf.tar.gz chromium_src-e3ebba0fbbfb2c7eec286a964717859aa70b9fcf.tar.bz2 |
Implement server push protocol 2.
TEST=net_unittests
BUG=34761
Review URL: http://codereview.chromium.org/3020032
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@55095 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/spdy')
-rw-r--r-- | net/spdy/spdy_http_stream.cc | 13 | ||||
-rw-r--r-- | net/spdy/spdy_network_transaction_unittest.cc | 669 | ||||
-rw-r--r-- | net/spdy/spdy_protocol.h | 3 | ||||
-rw-r--r-- | net/spdy/spdy_session.cc | 193 | ||||
-rw-r--r-- | net/spdy/spdy_session.h | 13 | ||||
-rw-r--r-- | net/spdy/spdy_session_unittest.cc | 78 | ||||
-rw-r--r-- | net/spdy/spdy_stream.cc | 69 | ||||
-rw-r--r-- | net/spdy/spdy_stream.h | 9 | ||||
-rw-r--r-- | net/spdy/spdy_test_util.cc | 295 | ||||
-rw-r--r-- | net/spdy/spdy_test_util.h | 88 |
10 files changed, 1093 insertions, 337 deletions
diff --git a/net/spdy/spdy_http_stream.cc b/net/spdy/spdy_http_stream.cc index bb64021..4e62b45 100644 --- a/net/spdy/spdy_http_stream.cc +++ b/net/spdy/spdy_http_stream.cc @@ -262,6 +262,7 @@ int SpdyHttpStream::SendRequest(const std::string& /*headers_string*/, CHECK(stream_.get()); stream_->SetDelegate(this); + linked_ptr<spdy::SpdyHeaderBlock> headers(new spdy::SpdyHeaderBlock); CreateSpdyHeadersFromHttpRequest(*request_info_, headers.get()); stream_->set_spdy_headers(headers); @@ -285,7 +286,7 @@ int SpdyHttpStream::SendRequest(const std::string& /*headers_string*/, CHECK(!stream_->cancelled()); CHECK(response); - if (stream_->response_complete()) { + if (!stream_->pushed() && stream_->response_complete()) { if (stream_->response_status() == OK) return ERR_FAILED; else @@ -297,16 +298,14 @@ int SpdyHttpStream::SendRequest(const std::string& /*headers_string*/, // a) A client initiated request. In this case, |response_info_| should be // NULL to start with. // b) A client request which matches a response that the server has already - // pushed. In this case, the value of |*push_response_info_| is copied - // over to the new response object |*response|. |push_response_info_| is - // deleted, and |response_info_| is reset |response|. + // pushed. if (push_response_info_.get()) { - *response = *push_response_info_; + *response = *(push_response_info_.get()); push_response_info_.reset(); - response_info_ = NULL; } + else + DCHECK_EQ(static_cast<HttpResponseInfo*>(NULL), response_info_); - DCHECK_EQ(static_cast<HttpResponseInfo*>(NULL), response_info_); response_info_ = response; bool has_upload_data = request_body_stream_.get() != NULL; diff --git a/net/spdy/spdy_network_transaction_unittest.cc b/net/spdy/spdy_network_transaction_unittest.cc index 11d20cb..25a03b2 100644 --- a/net/spdy/spdy_network_transaction_unittest.cc +++ b/net/spdy/spdy_network_transaction_unittest.cc @@ -9,6 +9,7 @@ #include "net/spdy/spdy_http_stream.h" #include "net/spdy/spdy_network_transaction.h" #include "net/spdy/spdy_test_util.h" +#include "net/url_request/url_request_unittest.h" #include "testing/platform_test.h" //----------------------------------------------------------------------------- @@ -83,7 +84,6 @@ class SpdyNetworkTransactionTest if (!session_.get()) session_ = SpdySessionDependencies::SpdyCreateSession( session_deps_.get()); - HttpNetworkTransaction::SetUseAlternateProtocols(false); HttpNetworkTransaction::SetUseSSLOverSpdyWithoutNPN(false); HttpNetworkTransaction::SetUseSpdyWithoutNPN(false); @@ -181,8 +181,8 @@ class SpdyNetworkTransactionTest } void RunToCompletion(StaticSocketDataProvider* data) { - AddData(data); RunPreTestSetup(); + AddData(data); RunDefaultTest(); VerifyDataConsumed(); } @@ -244,6 +244,13 @@ class SpdyNetworkTransactionTest void ConnectStatusHelper(const MockRead& status); + const HttpRequestInfo& CreateGetPushRequest() { + google_get_push_request_.method = "GET"; + google_get_push_request_.url = GURL("http://www.google.com/foo.dat"); + google_get_push_request_.load_flags = 0; + return google_get_push_request_; + } + const HttpRequestInfo& CreateGetRequest() { if (!google_get_request_initialized_) { google_get_request_.method = "GET"; @@ -254,9 +261,99 @@ class SpdyNetworkTransactionTest return google_get_request_; } + class RunServerPushTestCallback : public CallbackRunner< Tuple1<int> > { + public: + RunServerPushTestCallback(scoped_refptr<net::IOBufferWithSize> buffer, + std::string& result, bool& need_read_callback) + : buffer_(buffer), result_(result), + need_read_callback_(need_read_callback) {} + + virtual void RunWithParams(const Tuple1<int>& params) { + // Indicates some type of error. + if(params.a <= 0) + return; + + std::string temp(buffer_->data(), params.a); + result_.append(temp); + need_read_callback_ = true; + } + + private: + scoped_refptr<net::IOBufferWithSize> buffer_; + std::string& result_; + bool need_read_callback_; + }; + + void RunServerPushTest(MockWrite writes[], int writes_length, + MockRead reads[], int reads_length, + HttpResponseInfo* response, + HttpResponseInfo* response2, + std::string& expected) { + scoped_refptr<OrderedSocketData> data( + new OrderedSocketData(reads, reads_length, + writes, writes_length)); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + + helper.RunPreTestSetup(); + helper.AddData(data.get()); + + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start(&CreateGetRequest(), &callback, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + + // Request the pushed path. + const int kSize = 3000; + scoped_refptr<net::IOBufferWithSize> buf = new net::IOBufferWithSize(kSize); + scoped_ptr<HttpNetworkTransaction> trans2( + new HttpNetworkTransaction(helper.session())); + rv = trans2->Start(&CreateGetPushRequest(), &callback, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + MessageLoop::current()->RunAllPending(); + + // The data for the pushed path may be coming in more than 1 packet. Compile + // the results into a single string. + std::string result; + bool need_read_callback = true; + RunServerPushTestCallback callback2(buf, result, need_read_callback); + while(!data->at_read_eof()) + { + if(need_read_callback) { + while((rv = trans2->Read(buf, kSize, &callback2)) > 0) { + std::string result1(buf->data(),rv); + result.append(result1); + } + need_read_callback = false; + } + else + data->CompleteRead(); + MessageLoop::current()->RunAllPending(); + } + + // Verify that we consumed all test data. + EXPECT_TRUE(data->at_read_eof()); + EXPECT_TRUE(data->at_write_eof()); + + // Verify that the received push data is same as the expected push data. + EXPECT_EQ(result.compare(expected),0) << "Received data: " + << result + << "||||| Expected data: " + << expected; + + // Verify the SYN_REPLY. + // Copy the response info, because trans goes away. + *response = *trans->GetResponseInfo(); + *response2 = *trans2->GetResponseInfo(); + } + private: bool google_get_request_initialized_; HttpRequestInfo google_get_request_; + HttpRequestInfo google_get_push_request_; }; //----------------------------------------------------------------------------- @@ -264,7 +361,7 @@ class SpdyNetworkTransactionTest // negotiation, SPDY without SSL, and SPDY with SSL. INSTANTIATE_TEST_CASE_P(SpdyNetworkingTest, SpdyNetworkTransactionTest, - ::testing::Values(SPDYNPN, SPDYNOSSL, SPDYSSL)); + ::testing::Values(SPDYNOSSL, SPDYSSL, SPDYNPN)); // Verify HttpNetworkTransaction constructor. @@ -1014,8 +1111,8 @@ TEST_P(SpdyNetworkTransactionTest, PostWithEarlySynReply) { writes, arraysize(writes))); NormalSpdyTransactionHelper helper(request, BoundNetLog(), GetParam()); - helper.AddData(data.get()); helper.RunPreTestSetup(); + helper.AddData(data.get()); helper.RunDefaultTest(); TransactionHelperResult out = helper.output(); EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv); @@ -1097,8 +1194,8 @@ TEST_P(SpdyNetworkTransactionTest, ResponseWithTwoSynReplies) { NormalSpdyTransactionHelper helper(CreateGetRequest(), BoundNetLog(), GetParam()); - helper.AddData(data.get()); helper.RunPreTestSetup(); + helper.AddData(data.get()); HttpNetworkTransaction* trans = helper.trans(); @@ -1291,8 +1388,8 @@ TEST_P(SpdyNetworkTransactionTest, CancelledTransaction) { NormalSpdyTransactionHelper helper(CreateGetRequest(), BoundNetLog(), GetParam()); - helper.AddData(&data); helper.RunPreTestSetup(); + helper.AddData(&data); HttpNetworkTransaction* trans = helper.trans(); TestCompletionCallback callback; @@ -1329,8 +1426,8 @@ TEST_P(SpdyNetworkTransactionTest, CancelledTransactionSendRst) { NormalSpdyTransactionHelper helper(CreateGetRequest(), BoundNetLog(), GetParam()); - helper.AddData(data.get()); helper.RunPreTestSetup(); + helper.AddData(data.get()); HttpNetworkTransaction* trans = helper.trans(); TestCompletionCallback callback; @@ -1413,9 +1510,9 @@ TEST_P(SpdyNetworkTransactionTest, StartTransactionOnReadCallback) { NormalSpdyTransactionHelper helper(CreateGetRequest(), BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); helper.AddData(data.get()); helper.AddData(data2.get()); - helper.RunPreTestSetup(); HttpNetworkTransaction* trans = helper.trans(); // Start the transaction with basic parameters. @@ -1472,8 +1569,8 @@ TEST_P(SpdyNetworkTransactionTest, DeleteSessionOnReadCallback) { NormalSpdyTransactionHelper helper(CreateGetRequest(), BoundNetLog(), GetParam()); - helper.AddData(data.get()); helper.RunPreTestSetup(); + helper.AddData(data.get()); HttpNetworkTransaction* trans = helper.trans(); // Start the transaction with basic parameters. @@ -1496,6 +1593,536 @@ TEST_P(SpdyNetworkTransactionTest, DeleteSessionOnReadCallback) { helper.VerifyDataConsumed(); } +// Send a spdy request to www.google.com that gets redirected to www.foo.com. +TEST_P(SpdyNetworkTransactionTest, RedirectGetRequest) { + // These are headers which the URLRequest tacks on. + const char* const kExtraHeaders[] = { + "accept-charset", + "", + "accept-encoding", + "gzip,deflate", + "accept-language", + "", + }; + const SpdyHeaderInfo kSynStartHeader = make_spdy_header(spdy::SYN_STREAM); + const char* const kStandardGetHeaders[] = { + "host", + "www.google.com", + "method", + "GET", + "scheme", + "http", + "url", + "/", + "user-agent", + "", + "version", + "HTTP/1.1" + }; + const char* const kStandardGetHeaders2[] = { + "host", + "www.foo.com", + "method", + "GET", + "scheme", + "http", + "url", + "/index.php", + "user-agent", + "", + "version", + "HTTP/1.1" + }; + + // Setup writes/reads to www.google.com + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPacket( + kSynStartHeader, kExtraHeaders, arraysize(kExtraHeaders)/2, + kStandardGetHeaders, arraysize(kStandardGetHeaders)/2)); + scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyPacket( + kSynStartHeader, kExtraHeaders, arraysize(kExtraHeaders)/2, + kStandardGetHeaders2, arraysize(kStandardGetHeaders2)/2)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReplyRedirect(1)); + MockWrite writes[] = { + CreateMockWrite(*req, 1), + }; + MockRead reads[] = { + CreateMockRead(*resp, 2), + MockRead(true, 0, 0, 3) // EOF + }; + + // Setup writes/reads to www.foo.com + scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body2(ConstructSpdyBodyFrame(1, true)); + MockWrite writes2[] = { + CreateMockWrite(*req2, 1), + }; + MockRead reads2[] = { + CreateMockRead(*resp2, 2), + CreateMockRead(*body2, 3), + MockRead(true, 0, 0, 4) // EOF + }; + scoped_refptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + scoped_refptr<OrderedSocketData> data2( + new OrderedSocketData(reads2, arraysize(reads2), + writes2, arraysize(writes2))); + + // TODO(erikchen): Make test support SPDYSSL, SPDYNPN + HttpNetworkTransaction::SetUseSSLOverSpdyWithoutNPN(false); + HttpNetworkTransaction::SetUseSpdyWithoutNPN(true); + TestDelegate d; + { + URLRequest r(GURL("http://www.google.com/"), &d); + SpdyURLRequestContext* spdy_url_request_context = + new SpdyURLRequestContext(); + r.set_context(spdy_url_request_context); + spdy_url_request_context->socket_factory(). + AddSocketDataProvider(data.get()); + spdy_url_request_context->socket_factory(). + AddSocketDataProvider(data2.get()); + + d.set_quit_on_redirect(true); + r.Start(); + MessageLoop::current()->Run(); + + EXPECT_EQ(1, d.received_redirect_count()); + + r.FollowDeferredRedirect(); + MessageLoop::current()->Run(); + EXPECT_EQ(1, d.response_started_count()); + EXPECT_FALSE(d.received_data_before_response()); + EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status()); + std::string contents("hello!"); + EXPECT_EQ(contents, d.data_received()); + } + EXPECT_TRUE(data->at_read_eof()); + EXPECT_TRUE(data->at_write_eof()); + EXPECT_TRUE(data2->at_read_eof()); + EXPECT_TRUE(data2->at_write_eof()); +} + +// Send a spdy request to www.google.com. Get a pushed stream that redirects to +// www.foo.com. +TEST_P(SpdyNetworkTransactionTest, RedirectServerPush) { + // These are headers which the URLRequest tacks on. + const char* const kExtraHeaders[] = { + "accept-charset", + "", + "accept-encoding", + "gzip,deflate", + "accept-language", + "", + }; + const SpdyHeaderInfo kSynStartHeader = make_spdy_header(spdy::SYN_STREAM); + const char* const kStandardGetHeaders[] = { + "host", + "www.google.com", + "method", + "GET", + "scheme", + "http", + "url", + "/", + "user-agent", + "", + "version", + "HTTP/1.1" + }; + const char* const kStandardGetHeaders2[] = { + "host", + "www.foo.com", + "method", + "GET", + "scheme", + "http", + "url", + "/index.php", + "user-agent", + "", + "version", + "HTTP/1.1" + }; + + // Setup writes/reads to www.google.com + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyPacket( + kSynStartHeader, kExtraHeaders, arraysize(kExtraHeaders)/2, + kStandardGetHeaders, arraysize(kStandardGetHeaders)/2)); + scoped_ptr<spdy::SpdyFrame> req2(ConstructSpdyPacket( + kSynStartHeader, kExtraHeaders, arraysize(kExtraHeaders)/2, + kStandardGetHeaders2, arraysize(kStandardGetHeaders2)/2)); + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> rep(ConstructSpdyPush(NULL, 0, 2, 1, "/foo.dat", + "301 Moved Permanently", "http://www.foo.com/index.php", + "http://www.foo.com/index.php")); + scoped_ptr<spdy::SpdyFrame> body(ConstructSpdyBodyFrame(1, true)); + scoped_ptr<spdy::SpdyFrame> res( + ConstructSpdyRstStream(2, spdy::CANCEL)); + MockWrite writes[] = { + CreateMockWrite(*req, 1), + CreateMockWrite(*res, 6), + }; + MockRead reads[] = { + CreateMockRead(*resp, 2), + CreateMockRead(*rep, 3), + CreateMockRead(*body, 4), + MockRead(true, ERR_IO_PENDING, 5), // Force a pause + MockRead(true, 0, 0, 7) // EOF + }; + + // Setup writes/reads to www.foo.com + scoped_ptr<spdy::SpdyFrame> resp2(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> body2(ConstructSpdyBodyFrame(1, true)); + MockWrite writes2[] = { + CreateMockWrite(*req2, 1), + }; + MockRead reads2[] = { + CreateMockRead(*resp2, 2), + CreateMockRead(*body2, 3), + MockRead(true, 0, 0, 5) // EOF + }; + scoped_refptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + scoped_refptr<OrderedSocketData> data2( + new OrderedSocketData(reads2, arraysize(reads2), + writes2, arraysize(writes2))); + + // TODO(erikchen): Make test support SPDYSSL, SPDYNPN + HttpNetworkTransaction::SetUseSSLOverSpdyWithoutNPN(false); + HttpNetworkTransaction::SetUseSpdyWithoutNPN(true); + TestDelegate d; + TestDelegate d2; + { + URLRequest r(GURL("http://www.google.com/"), &d); + SpdyURLRequestContext* spdy_url_request_context = + new SpdyURLRequestContext(); + r.set_context(spdy_url_request_context); + spdy_url_request_context->socket_factory(). + AddSocketDataProvider(data.get()); + + r.Start(); + MessageLoop::current()->Run(); + + EXPECT_EQ(0, d.received_redirect_count()); + std::string contents("hello!"); + EXPECT_EQ(contents, d.data_received()); + + URLRequest r2(GURL("http://www.google.com/foo.dat"), &d2); + r2.set_context(spdy_url_request_context); + spdy_url_request_context->socket_factory(). + AddSocketDataProvider(data2.get()); + + d2.set_quit_on_redirect(true); + r2.Start(); + MessageLoop::current()->Run(); + EXPECT_EQ(1, d2.received_redirect_count()); + + r2.FollowDeferredRedirect(); + MessageLoop::current()->Run(); + EXPECT_EQ(1, d2.response_started_count()); + EXPECT_FALSE(d2.received_data_before_response()); + EXPECT_EQ(URLRequestStatus::SUCCESS, r2.status().status()); + std::string contents2("hello!"); + EXPECT_EQ(contents2, d2.data_received()); + } + data->CompleteRead(); + data2->CompleteRead(); + EXPECT_TRUE(data->at_read_eof()); + EXPECT_TRUE(data->at_write_eof()); + EXPECT_TRUE(data2->at_read_eof()); + EXPECT_TRUE(data2->at_write_eof()); +} + +TEST_P(SpdyNetworkTransactionTest, ServerPushSingleDataFrame) { + static const unsigned char kPushBodyFrame[] = { + 0x00, 0x00, 0x00, 0x02, // header, ID + 0x01, 0x00, 0x00, 0x05, // FIN, length + 'h', 'e', 'l', 'l', 'o', // "hello" + }; + + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { + CreateMockWrite(*req, 1), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> rep(ConstructSpdyPush(NULL, 0, 2, 1, "/foo.dat")); + MockRead reads[] = { + CreateMockRead(*resp, 2), + CreateMockRead(*rep, 3), + MockRead(true, reinterpret_cast<const char*>(kPushBodyFrame), + arraysize(kPushBodyFrame), 4), + MockRead(true, ERR_IO_PENDING, 5), // Force a pause + MockRead(true, 0, 0, 6) // EOF + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("hello"); + RunServerPushTest(writes, arraysize(writes), reads, arraysize(reads), + &response, &response2, expected_push_result); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionTest, ServerPushMultipleDataFrame) { + static const unsigned char kPushBodyFrame1[] = { + 0x00, 0x00, 0x00, 0x02, // header, ID + 0x01, 0x00, 0x00, 0x1E, // FIN, length + 'h', 'e', 'l', 'l', 'o', // "hello" + }; + static const char kPushBodyFrame2[] = " my darling"; + static const char kPushBodyFrame3[] = " hello"; + static const char kPushBodyFrame4[] = " my baby"; + + scoped_ptr<spdy::SpdyFrame> req( + ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { + CreateMockWrite(*req, 1), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> rep(ConstructSpdyPush(NULL, 0, 2, 1, "/foo.dat")); + MockRead reads[] = { + CreateMockRead(*resp, 2), + CreateMockRead(*rep, 3), + MockRead(true, reinterpret_cast<const char*>(kPushBodyFrame1), + arraysize(kPushBodyFrame1), 4), + MockRead(true, reinterpret_cast<const char*>(kPushBodyFrame2), + arraysize(kPushBodyFrame2) - 1, 5), + MockRead(true, reinterpret_cast<const char*>(kPushBodyFrame3), + arraysize(kPushBodyFrame3) - 1, 6), + MockRead(true, reinterpret_cast<const char*>(kPushBodyFrame4), + arraysize(kPushBodyFrame4) - 1, 7), + MockRead(true, ERR_IO_PENDING, 8), // Force a pause + MockRead(true, 0, 0, 9) // EOF + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("hello my darling hello my baby"); + RunServerPushTest(writes, arraysize(writes), reads, arraysize(reads), + &response, &response2, expected_push_result); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionTest, ServerPushMultipleDataFrameInterrupted) { + static const unsigned char kPushBodyFrame1[] = { + 0x00, 0x00, 0x00, 0x02, // header, ID + 0x01, 0x00, 0x00, 0x1E, // FIN, length + 'h', 'e', 'l', 'l', 'o', // "hello" + }; + static const char kPushBodyFrame2[] = " my darling"; + static const char kPushBodyFrame3[] = " hello"; + static const char kPushBodyFrame4[] = " my baby"; + + scoped_ptr<spdy::SpdyFrame> req( + ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + MockWrite writes[] = { + CreateMockWrite(*req, 1), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> rep(ConstructSpdyPush(NULL, 0, 2, 1, "/foo.dat")); + MockRead reads[] = { + CreateMockRead(*resp, 2), + CreateMockRead(*rep, 3), + MockRead(true, reinterpret_cast<const char*>(kPushBodyFrame1), + arraysize(kPushBodyFrame1), 4), + MockRead(true, reinterpret_cast<const char*>(kPushBodyFrame2), + arraysize(kPushBodyFrame2) - 1, 5), + MockRead(true, ERR_IO_PENDING, 6), // Force a pause + MockRead(true, reinterpret_cast<const char*>(kPushBodyFrame3), + arraysize(kPushBodyFrame3) - 1, 7), + MockRead(true, reinterpret_cast<const char*>(kPushBodyFrame4), + arraysize(kPushBodyFrame4) - 1, 8), + MockRead(true, 0, 0, 9) // EOF + }; + + HttpResponseInfo response; + HttpResponseInfo response2; + std::string expected_push_result("hello my darling hello my baby"); + RunServerPushTest(writes, arraysize(writes), reads, arraysize(reads), + &response, &response2, expected_push_result); + + // Verify the SYN_REPLY. + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); + + // Verify the pushed stream. + EXPECT_TRUE(response2.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response2.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionTest, ServerPushInvalidAssociatedStreamID0) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> res( + ConstructSpdyRstStream(2, spdy::INVALID_STREAM)); + MockWrite writes[] = { + CreateMockWrite(*req, 1), + CreateMockWrite(*res, 4), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> rep(ConstructSpdyPush(NULL, 0, 2, 0, "/foo.dat")); + MockRead reads[] = { + CreateMockRead(*resp, 2), + CreateMockRead(*rep, 3), + MockRead(true, 0, 0, 5) // EOF + }; + + scoped_refptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + + helper.RunPreTestSetup(); + helper.AddData(data.get()); + + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start(&CreateGetRequest(), &callback, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + data->CompleteRead(); + EXPECT_EQ(OK, rv); + + // Verify that we consumed all test data. + EXPECT_TRUE(data->at_read_eof()) << "Read count: " + << data->read_count() + << " Read index: " + << data->read_index(); + EXPECT_TRUE(data->at_write_eof()) << "Write count: " + << data->write_count() + << " Write index: " + << data->write_index(); + + // Verify the SYN_REPLY. + HttpResponseInfo response = *trans->GetResponseInfo(); + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionTest, ServerPushInvalidAssociatedStreamID9) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> res( + ConstructSpdyRstStream(2, spdy::INVALID_ASSOCIATED_STREAM)); + MockWrite writes[] = { + CreateMockWrite(*req, 1), + CreateMockWrite(*res, 4), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> rep(ConstructSpdyPush(NULL, 0, 2, 9, "/foo.dat")); + MockRead reads[] = { + CreateMockRead(*resp, 2), + CreateMockRead(*rep, 3), + MockRead(true, 0, 0, 5) // EOF + }; + + scoped_refptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + + helper.RunPreTestSetup(); + helper.AddData(data.get()); + + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start(&CreateGetRequest(), &callback, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + data->CompleteRead(); + EXPECT_EQ(OK, rv); + + // Verify that we consumed all test data. + EXPECT_TRUE(data->at_read_eof()) << "Read count: " + << data->read_count() + << " Read index: " + << data->read_index(); + EXPECT_TRUE(data->at_write_eof()) << "Write count: " + << data->write_count() + << " Write index: " + << data->write_index(); + + // Verify the SYN_REPLY. + HttpResponseInfo response = *trans->GetResponseInfo(); + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); +} + +TEST_P(SpdyNetworkTransactionTest, ServerPushNoURL) { + scoped_ptr<spdy::SpdyFrame> req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST)); + scoped_ptr<spdy::SpdyFrame> res( + ConstructSpdyRstStream(2, spdy::PROTOCOL_ERROR)); + MockWrite writes[] = { + CreateMockWrite(*req, 1), + CreateMockWrite(*res, 4), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr<spdy::SpdyFrame> rep(ConstructSpdyPush(NULL, 0, 2, 1)); + MockRead reads[] = { + CreateMockRead(*resp, 2), + CreateMockRead(*rep, 3), + MockRead(true, 0, 0, 5) // EOF + }; + + scoped_refptr<OrderedSocketData> data( + new OrderedSocketData(reads, arraysize(reads), + writes, arraysize(writes))); + NormalSpdyTransactionHelper helper(CreateGetRequest(), + BoundNetLog(), GetParam()); + + helper.RunPreTestSetup(); + helper.AddData(data.get()); + + HttpNetworkTransaction* trans = helper.trans(); + + // Start the transaction with basic parameters. + TestCompletionCallback callback; + int rv = trans->Start(&CreateGetRequest(), &callback, BoundNetLog()); + EXPECT_EQ(ERR_IO_PENDING, rv); + rv = callback.WaitForResult(); + data->CompleteRead(); + EXPECT_EQ(OK, rv); + // Verify that we consumed all test data. + EXPECT_TRUE(data->at_read_eof()) << "Read count: " + << data->read_count() + << " Read index: " + << data->read_index(); + EXPECT_TRUE(data->at_write_eof()) << "Write count: " + << data->write_count() + << " Write index: " + << data->write_index(); + + // Verify the SYN_REPLY. + HttpResponseInfo response = *trans->GetResponseInfo(); + EXPECT_TRUE(response.headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response.headers->GetStatusLine()); +} + // Verify that various SynReply headers parse correctly through the // HTTP layer. TEST_P(SpdyNetworkTransactionTest, SynReplyHeaders) { @@ -2041,8 +2668,8 @@ TEST_P(SpdyNetworkTransactionTest, BufferFull) { NormalSpdyTransactionHelper helper(CreateGetRequest(), BoundNetLog(), GetParam()); - helper.AddData(data.get()); helper.RunPreTestSetup(); + helper.AddData(data.get()); HttpNetworkTransaction* trans = helper.trans(); int rv = trans->Start(&CreateGetRequest(), &callback, BoundNetLog()); EXPECT_EQ(ERR_IO_PENDING, rv); @@ -2120,8 +2747,8 @@ TEST_P(SpdyNetworkTransactionTest, ConnectFailureFallbackToHttp) { writes, arraysize(writes))); NormalSpdyTransactionHelper helper(CreateGetRequest(), BoundNetLog(), GetParam()); - helper.AddData(data.get()); helper.RunPreTestSetup(); + helper.AddData(data.get()); // Set up http fallback data. MockRead http_fallback_data[] = { @@ -2204,8 +2831,8 @@ TEST_P(SpdyNetworkTransactionTest, Buffering) { NormalSpdyTransactionHelper helper(CreateGetRequest(), BoundNetLog(), GetParam()); - helper.AddData(data.get()); helper.RunPreTestSetup(); + helper.AddData(data.get()); HttpNetworkTransaction* trans = helper.trans(); TestCompletionCallback callback; @@ -2299,8 +2926,8 @@ TEST_P(SpdyNetworkTransactionTest, BufferedAll) { NormalSpdyTransactionHelper helper(CreateGetRequest(), BoundNetLog(), GetParam()); - helper.AddData(data.get()); helper.RunPreTestSetup(); + helper.AddData(data.get()); HttpNetworkTransaction* trans = helper.trans(); TestCompletionCallback callback; @@ -2387,8 +3014,8 @@ TEST_P(SpdyNetworkTransactionTest, BufferedClosed) { NormalSpdyTransactionHelper helper(CreateGetRequest(), BoundNetLog(), GetParam()); - helper.AddData(data.get()); helper.RunPreTestSetup(); + helper.AddData(data.get()); HttpNetworkTransaction* trans = helper.trans(); TestCompletionCallback callback; @@ -2467,8 +3094,8 @@ TEST_P(SpdyNetworkTransactionTest, BufferedCancelled) { NormalSpdyTransactionHelper helper(CreateGetRequest(), BoundNetLog(), GetParam()); - helper.AddData(data.get()); helper.RunPreTestSetup(); + helper.AddData(data.get()); HttpNetworkTransaction* trans = helper.trans(); TestCompletionCallback callback; @@ -2535,6 +3162,7 @@ TEST_P(SpdyNetworkTransactionTest, SettingsSaved) { NormalSpdyTransactionHelper helper(CreateGetRequest(), BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); // Verify that no settings exist initially. HostPortPair host_port_pair("www.google.com", helper.port()); @@ -2589,7 +3217,9 @@ TEST_P(SpdyNetworkTransactionTest, SettingsSaved) { scoped_refptr<DelayedSocketData> data( new DelayedSocketData(1, reads, arraysize(reads), writes, arraysize(writes))); - helper.RunToCompletion(data.get()); + helper.AddData(data.get()); + helper.RunDefaultTest(); + helper.VerifyDataConsumed(); TransactionHelperResult out = helper.output(); EXPECT_EQ(OK, out.rv); EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); @@ -2640,6 +3270,7 @@ TEST_P(SpdyNetworkTransactionTest, SettingsPlayback) { NormalSpdyTransactionHelper helper(CreateGetRequest(), BoundNetLog(), GetParam()); + helper.RunPreTestSetup(); // Verify that no settings exist initially. HostPortPair host_port_pair("www.google.com", helper.port()); @@ -2698,7 +3329,9 @@ TEST_P(SpdyNetworkTransactionTest, SettingsPlayback) { scoped_refptr<DelayedSocketData> data( new DelayedSocketData(2, reads, arraysize(reads), writes, arraysize(writes))); - helper.RunToCompletion(data.get()); + helper.AddData(data.get()); + helper.RunDefaultTest(); + helper.VerifyDataConsumed(); TransactionHelperResult out = helper.output(); EXPECT_EQ(OK, out.rv); EXPECT_EQ("HTTP/1.1 200 OK", out.status_line); @@ -2809,8 +3442,8 @@ TEST_P(SpdyNetworkTransactionTest, CloseWithActiveStream) { BoundNetLog log; NormalSpdyTransactionHelper helper(CreateGetRequest(), log, GetParam()); - helper.AddData(data.get()); helper.RunPreTestSetup(); + helper.AddData(data.get()); HttpNetworkTransaction* trans = helper.trans(); TestCompletionCallback callback; diff --git a/net/spdy/spdy_protocol.h b/net/spdy/spdy_protocol.h index 4637b5f..ce074c4d 100644 --- a/net/spdy/spdy_protocol.h +++ b/net/spdy/spdy_protocol.h @@ -189,7 +189,8 @@ enum SpdyStatusCodes { CANCEL = 5, INTERNAL_ERROR = 6, FLOW_CONTROL_ERROR = 7, - NUM_STATUS_CODES = 8 + INVALID_ASSOCIATED_STREAM = 8, + NUM_STATUS_CODES = 9 }; // A SPDY stream id is a 31 bit entity. diff --git a/net/spdy/spdy_session.cc b/net/spdy/spdy_session.cc index 44bbf68..3e4d049 100644 --- a/net/spdy/spdy_session.cc +++ b/net/spdy/spdy_session.cc @@ -279,24 +279,7 @@ int SpdySession::GetPushStream( streams_pushed_and_claimed_count_++; return OK; } - - // Check if we have a pending push stream for this url. - // Note that we shouldn't have a pushed stream for non-GET method. - PendingStreamMap::iterator it; - it = pending_streams_.find(path); - if (it != pending_streams_.end()) { - // Server has advertised a stream, but not yet sent it. - DCHECK(!it->second); - // Server will assign a stream id when the push stream arrives. Use 0 for - // now. - net_log_.AddEvent(NetLog::TYPE_SPDY_STREAM_ADOPTED_PUSH_STREAM, NULL); - *stream = new SpdyStream(this, 0, true); - (*stream)->set_path(path); - (*stream)->set_net_log(stream_net_log); - it->second = *stream; - return OK; - } - return OK; + return NULL; } int SpdySession::CreateStream( @@ -473,16 +456,19 @@ void SpdySession::CloseStream(spdy::SpdyStreamId stream_id, int status) { void SpdySession::ResetStream( spdy::SpdyStreamId stream_id, spdy::SpdyStatusCodes status) { - DCHECK(IsStreamActive(stream_id)); - scoped_refptr<SpdyStream> stream = active_streams_[stream_id]; - CHECK_EQ(stream->stream_id(), stream_id); - LOG(INFO) << "Sending a RST_STREAM frame for stream " << stream_id << " with status " << status; scoped_ptr<spdy::SpdyRstStreamControlFrame> rst_frame( spdy_framer_.CreateRstStream(stream_id, status)); - QueueFrame(rst_frame.get(), stream->priority(), stream); + + // Default to lowest priority unless we know otherwise. + int priority = 3; + if(IsStreamActive(stream_id)) { + scoped_refptr<SpdyStream> stream = active_streams_[stream_id]; + priority = stream->priority(); + } + QueueFrame(rst_frame.get(), priority, NULL); DeleteStream(stream_id, ERR_SPDY_PROTOCOL_ERROR); } @@ -786,9 +772,9 @@ void SpdySession::CloseAllStreams(net::Error status) { if (!active_streams_.empty()) abandoned_streams.Add(active_streams_.size()); - if (!pushed_streams_.empty()) { - streams_abandoned_count_ += pushed_streams_.size(); - abandoned_push_streams.Add(pushed_streams_.size()); + if (!unclaimed_pushed_streams_.empty()) { + streams_abandoned_count_ += unclaimed_pushed_streams_.size(); + abandoned_push_streams.Add(unclaimed_pushed_streams_.size()); } for (int i = 0;i < NUM_PRIORITIES;++i) { @@ -808,16 +794,6 @@ void SpdySession::CloseAllStreams(net::Error status) { DeleteStream(stream->stream_id(), status); } - // TODO(erikchen): ideally stream->OnClose() is only ever called by - // DeleteStream, but pending streams fall into their own category for now. - PendingStreamMap::iterator it; - for (it = pending_streams_.begin(); it != pending_streams_.end(); ++it) { - const scoped_refptr<SpdyStream>& stream = it->second; - if (stream) - stream->OnClose(ERR_ABORTED); - } - pending_streams_.clear(); - // We also need to drain the queue. while (queue_.size()) queue_.pop(); @@ -870,12 +846,13 @@ void SpdySession::ActivateStream(SpdyStream* stream) { } void SpdySession::DeleteStream(spdy::SpdyStreamId id, int status) { - // Remove the stream from pushed_streams_ and active_streams_. - ActivePushedStreamList::iterator it; - for (it = pushed_streams_.begin(); it != pushed_streams_.end(); ++it) { - scoped_refptr<SpdyStream> curr = *it; + // Remove the stream from unclaimed_pushed_streams_ and active_streams_. + PushedStreamMap::iterator it; + for (it = unclaimed_pushed_streams_.begin(); + it != unclaimed_pushed_streams_.end(); ++it) { + scoped_refptr<SpdyStream> curr = it->second; if (id == curr->stream_id()) { - pushed_streams_.erase(it); + unclaimed_pushed_streams_.erase(it); break; } } @@ -906,22 +883,19 @@ scoped_refptr<SpdyStream> SpdySession::GetActivePushStream( LOG(INFO) << "Looking for push stream: " << path; - scoped_refptr<SpdyStream> stream; - - // We just walk a linear list here. - ActivePushedStreamList::iterator it; - for (it = pushed_streams_.begin(); it != pushed_streams_.end(); ++it) { - stream = *it; - if (path == stream->path()) { - CHECK(stream->pushed()); - pushed_streams_.erase(it); - used_push_streams.Increment(); - LOG(INFO) << "Push Stream Claim for: " << path; - return stream; - } + PushedStreamMap::iterator it = unclaimed_pushed_streams_.find(path); + if (it != unclaimed_pushed_streams_.end()) { + LOG(INFO) << "Push stream: " << path << " found."; + net_log_.AddEvent(NetLog::TYPE_SPDY_STREAM_ADOPTED_PUSH_STREAM, NULL); + scoped_refptr<SpdyStream> stream = it->second; + unclaimed_pushed_streams_.erase(it); + used_push_streams.Increment(); + return stream; + } + else { + LOG(INFO) << "Push stream: " << path << " not found."; + return NULL; } - - return NULL; } bool SpdySession::GetSSLInfo(SSLInfo* ssl_info, bool* was_npn_negotiated) { @@ -972,9 +946,9 @@ bool SpdySession::Respond(const spdy::SpdyHeaderBlock& headers, void SpdySession::OnSyn(const spdy::SpdySynStreamControlFrame& frame, const linked_ptr<spdy::SpdyHeaderBlock>& headers) { spdy::SpdyStreamId stream_id = frame.stream_id(); - - LOG(INFO) << "Spdy SynStream for stream " << stream_id; - + spdy::SpdyStreamId associated_stream_id = frame.associated_stream_id(); + LOG(INFO) << "Spdy SynStream for stream " << stream_id + << " with associated stream " << associated_stream_id; // Server-initiated streams should have even sequence numbers. if ((stream_id & 0x1) != 0) { LOG(ERROR) << "Received invalid OnSyn stream id " << stream_id; @@ -986,6 +960,14 @@ void SpdySession::OnSyn(const spdy::SpdySynStreamControlFrame& frame, return; } + if (associated_stream_id == 0) { + LOG(ERROR) << "Received invalid OnSyn associated stream id " + << associated_stream_id + << " for stream " << stream_id; + ResetStream(stream_id, spdy::INVALID_STREAM); + return; + } + streams_pushed_count_++; LOG(INFO) << "SpdySession: Syn received for stream: " << stream_id; @@ -999,54 +981,47 @@ void SpdySession::OnSyn(const spdy::SpdySynStreamControlFrame& frame, headers->find("path")->second : ""; // Verify that the response had a URL for us. - DCHECK(!path.empty()); if (path.empty()) { + ResetStream(stream_id, spdy::PROTOCOL_ERROR); LOG(WARNING) << "Pushed stream did not contain a path."; return; } - // Only HTTP push a stream. + if (!IsStreamActive(associated_stream_id)) { + LOG(ERROR) << "Received OnSyn with inactive associated stream " + << associated_stream_id; + ResetStream(stream_id, spdy::INVALID_ASSOCIATED_STREAM); + return; + } + scoped_refptr<SpdyStream> stream; - // Check if we already have a delegate awaiting this stream. - PendingStreamMap::iterator it; - it = pending_streams_.find(path); - if (it != pending_streams_.end()) { - stream = it->second; - pending_streams_.erase(it); - } + stream = new SpdyStream(this, stream_id, true); - if (stream) { - CHECK(stream->pushed()); - CHECK_EQ(0u, stream->stream_id()); - stream->set_stream_id(stream_id); - const BoundNetLog& log = stream->net_log(); - if (log.HasListener()) { - log.AddEvent( - NetLog::TYPE_SPDY_STREAM_PUSHED_SYN_STREAM, - new NetLogSpdySynParameter( - headers, static_cast<spdy::SpdyControlFlags>(frame.flags()), - stream_id)); - } - } else { - stream = new SpdyStream(this, stream_id, true); - - if (net_log_.HasListener()) { - net_log_.AddEvent( - NetLog::TYPE_SPDY_SESSION_PUSHED_SYN_STREAM, - new NetLogSpdySynParameter( - headers, static_cast<spdy::SpdyControlFlags>(frame.flags()), - stream_id)); - } + if (net_log_.HasListener()) { + net_log_.AddEvent( + NetLog::TYPE_SPDY_SESSION_PUSHED_SYN_STREAM, + new NetLogSpdySynParameter( + headers, static_cast<spdy::SpdyControlFlags>(frame.flags()), + stream_id)); } - pushed_streams_.push_back(stream); + // TODO(erikchen): Actually do something with the associated id. + + stream->set_path(path); + + // There should not be an existing pushed stream with the same path. + PushedStreamMap::iterator it = unclaimed_pushed_streams_.find(path); + if (it != unclaimed_pushed_streams_.end()) { + LOG(ERROR) << "Received duplicate pushed stream with path: " << path; + ResetStream(stream_id, spdy::PROTOCOL_ERROR); + } + unclaimed_pushed_streams_[path] = stream; // Activate a stream and parse the headers. ActivateStream(stream); - stream->set_path(path); - + // Parse the headers. if (!Respond(*headers, stream)) return; @@ -1082,34 +1057,6 @@ void SpdySession::OnSynReply(const spdy::SpdySynReplyControlFrame& frame, } stream->set_syn_reply_received(); - // We record content declared as being pushed so that we don't - // request a duplicate stream which is already scheduled to be - // sent to us. - spdy::SpdyHeaderBlock::const_iterator it; - it = headers->find("x-associated-content"); - if (it != headers->end()) { - const std::string& content = it->second; - std::string::size_type start = 0; - std::string::size_type end = 0; - do { - end = content.find("||", start); - if (end == std::string::npos) - end = content.length(); - std::string url = content.substr(start, end - start); - std::string::size_type pos = url.find("??"); - if (pos == std::string::npos) - break; - url = url.substr(pos + 2); - GURL gurl(url); - std::string path = gurl.PathForRequest(); - if (path.length()) - pending_streams_[path] = NULL; - else - LOG(INFO) << "Invalid X-Associated-Content path: " << url; - start = end + 2; - } while (start < content.length()); - } - const BoundNetLog& log = stream->net_log(); if (log.HasListener()) { log.AddEvent( @@ -1152,7 +1099,7 @@ void SpdySession::OnControl(const spdy::SpdyControlFrame* frame) { *reinterpret_cast<const spdy::SpdySettingsControlFrame*>(frame)); break; case spdy::RST_STREAM: - OnFin(*reinterpret_cast<const spdy::SpdyRstStreamControlFrame*>(frame)); + OnRst(*reinterpret_cast<const spdy::SpdyRstStreamControlFrame*>(frame)); break; case spdy::SYN_STREAM: OnSyn(*reinterpret_cast<const spdy::SpdySynStreamControlFrame*>(frame), @@ -1172,7 +1119,7 @@ void SpdySession::OnControl(const spdy::SpdyControlFrame* frame) { } } -void SpdySession::OnFin(const spdy::SpdyRstStreamControlFrame& frame) { +void SpdySession::OnRst(const spdy::SpdyRstStreamControlFrame& frame) { spdy::SpdyStreamId stream_id = frame.stream_id(); LOG(INFO) << "Spdy Fin for stream " << stream_id; diff --git a/net/spdy/spdy_session.h b/net/spdy/spdy_session.h index 39fe56b..68094d7 100644 --- a/net/spdy/spdy_session.h +++ b/net/spdy/spdy_session.h @@ -188,8 +188,7 @@ class SpdySession : public base::RefCounted<SpdySession>, PendingCreateStreamQueue; typedef std::map<int, scoped_refptr<SpdyStream> > ActiveStreamMap; // Only HTTP push a stream. - typedef std::list<scoped_refptr<SpdyStream> > ActivePushedStreamList; - typedef std::map<std::string, scoped_refptr<SpdyStream> > PendingStreamMap; + typedef std::map<std::string, scoped_refptr<SpdyStream> > PushedStreamMap; typedef std::priority_queue<SpdyIOBuffer> OutputQueue; virtual ~SpdySession(); @@ -213,7 +212,7 @@ class SpdySession : public base::RefCounted<SpdySession>, const linked_ptr<spdy::SpdyHeaderBlock>& headers); void OnSynReply(const spdy::SpdySynReplyControlFrame& frame, const linked_ptr<spdy::SpdyHeaderBlock>& headers); - void OnFin(const spdy::SpdyRstStreamControlFrame& frame); + void OnRst(const spdy::SpdyRstStreamControlFrame& frame); void OnGoAway(const spdy::SpdyGoAwayControlFrame& frame); void OnSettings(const spdy::SpdySettingsControlFrame& frame); void OnWindowUpdate(const spdy::SpdyWindowUpdateControlFrame& frame); @@ -310,13 +309,9 @@ class SpdySession : public base::RefCounted<SpdySession>, // them into a separate ActiveStreamMap, and not deliver network events to // them? ActiveStreamMap active_streams_; - // List of all the streams that have already started to be pushed by the + // Map of all the streams that have already started to be pushed by the // server, but do not have consumers yet. - ActivePushedStreamList pushed_streams_; - // List of streams declared in X-Associated-Content headers, but do not have - // consumers yet. - // The key is a string representing the path of the URI being pushed. - PendingStreamMap pending_streams_; + PushedStreamMap unclaimed_pushed_streams_; // As we gather data to be sent, we put it into the output queue. OutputQueue queue_; diff --git a/net/spdy/spdy_session_unittest.cc b/net/spdy/spdy_session_unittest.cc index e78f0d9..b3659e7 100644 --- a/net/spdy/spdy_session_unittest.cc +++ b/net/spdy/spdy_session_unittest.cc @@ -111,82 +111,4 @@ TEST_F(SpdySessionTest, GoAway) { } } // namespace - -TEST_F(SpdySessionTest, GetActivePushStream) { - spdy::SpdyFramer framer; - SpdySessionTest::TurnOffCompression(); - - SpdySessionDependencies session_deps; - session_deps.host_resolver->set_synchronous_mode(true); - - MockConnect connect_data(false, OK); - spdy::SpdyHeaderBlock headers; - headers["path"] = "/foo.js"; - headers["status"] = "200"; - headers["version"] = "HTTP/1.1"; - scoped_ptr<spdy::SpdyFrame> push_syn(framer.CreateSynStream( - 2, 1, 0, spdy::CONTROL_FLAG_NONE, false, &headers)); - MockRead reads[] = { - CreateMockRead(*push_syn), - MockRead(true, ERR_IO_PENDING, 0) // EOF - }; - StaticSocketDataProvider data(reads, arraysize(reads), NULL, 0); - data.set_connect_data(connect_data); - session_deps.socket_factory.AddSocketDataProvider(&data); - - SSLSocketDataProvider ssl(false, OK); - session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); - - scoped_refptr<HttpNetworkSession> http_session( - SpdySessionDependencies::SpdyCreateSession(&session_deps)); - - const std::string kTestHost("www.foo.com"); - const int kTestPort = 80; - HostPortPair test_host_port_pair(kTestHost, kTestPort); - HostPortProxyPair pair(test_host_port_pair, ""); - - scoped_refptr<SpdySessionPool> spdy_session_pool( - http_session->spdy_session_pool()); - EXPECT_FALSE(spdy_session_pool->HasSession(pair)); - scoped_refptr<SpdySession> session = - spdy_session_pool->Get(pair, http_session.get(), BoundNetLog()); - EXPECT_TRUE(spdy_session_pool->HasSession(pair)); - - // No push streams should exist in the beginning. - std::string test_push_path = "/foo.js"; - scoped_refptr<SpdyStream> first_stream = session->GetActivePushStream( - test_push_path); - EXPECT_EQ(static_cast<SpdyStream*>(NULL), first_stream.get()); - - // Read in the data which contains a server-issued SYN_STREAM. - scoped_refptr<TCPSocketParams> tcp_params = - new TCPSocketParams(test_host_port_pair, MEDIUM, GURL(), false); - int rv = session->Connect(kTestHost, tcp_params, MEDIUM); - ASSERT_EQ(OK, rv); - MessageLoop::current()->RunAllPending(); - - // An unpushed path should not work. - scoped_refptr<SpdyStream> unpushed_stream = session->GetActivePushStream( - "/unpushed_path"); - EXPECT_EQ(static_cast<SpdyStream*>(NULL), unpushed_stream.get()); - - // The pushed path should be found. - scoped_refptr<SpdyStream> second_stream = session->GetActivePushStream( - test_push_path); - ASSERT_NE(static_cast<SpdyStream*>(NULL), second_stream.get()); - EXPECT_EQ(test_push_path, second_stream->path()); - EXPECT_EQ(2U, second_stream->stream_id()); - EXPECT_EQ(0, second_stream->priority()); - - // Clean up - second_stream = NULL; - session = NULL; - spdy_session_pool->CloseAllSessions(); - - // RunAllPending needs to be called here because the - // ClientSocketPoolBase posts a task to clean up and destroy the - // underlying socket. - MessageLoop::current()->RunAllPending(); -} - } // namespace net diff --git a/net/spdy/spdy_stream.cc b/net/spdy/spdy_stream.cc index b79cb99..5fd5f14 100644 --- a/net/spdy/spdy_stream.cc +++ b/net/spdy/spdy_stream.cc @@ -13,7 +13,8 @@ namespace net { SpdyStream::SpdyStream( SpdySession* session, spdy::SpdyStreamId stream_id, bool pushed) - : stream_id_(stream_id), + : continue_buffering_data_(true), + stream_id_(stream_id), priority_(0), send_window_size_(spdy::kInitialWindowSize), pushed_(pushed), @@ -39,16 +40,32 @@ void SpdyStream::SetDelegate(Delegate* delegate) { CHECK(delegate); delegate_ = delegate; - if (!response_->empty()) { - // The stream already got response. - delegate_->OnResponseReceived(*response_, response_time_, OK); + if (pushed_) { + CHECK(!response_->empty()); + MessageLoop::current()->PostTask( + FROM_HERE, NewRunnableMethod(this, + &SpdyStream::PushedStreamReplayData)); + } else { + continue_buffering_data_ = false; } +} +void SpdyStream::PushedStreamReplayData() { + if (cancelled_ || delegate_ == NULL) + return; + + delegate_->OnResponseReceived(*response_, response_time_, OK); + + continue_buffering_data_ = false; std::vector<scoped_refptr<IOBufferWithSize> > buffers; buffers.swap(pending_buffers_); for (size_t i = 0; i < buffers.size(); ++i) { - if (delegate_) - delegate_->OnDataReceived(buffers[i]->data(), buffers[i]->size()); + if (delegate_){ + if (buffers[i]) + delegate_->OnDataReceived(buffers[i]->data(), buffers[i]->size()); + else + delegate_->OnDataReceived(NULL, 0); + } } } @@ -131,13 +148,7 @@ int SpdyStream::OnResponseReceived(const spdy::SpdyHeaderBlock& response) { CHECK(pushed_); io_state_ = STATE_READ_HEADERS; } else if (io_state_ == STATE_READ_HEADERS_COMPLETE) { - // This SpdyStream could be in this state in both true and false pushed_ - // conditions. - // The false pushed_ condition (client request) will always go through - // this state. - // The true pushed_condition (server push) can be in this state when the - // client requests an X-Associated-Content piece of content prior - // to when the server push happens. + CHECK(!pushed_); } else { // We're not expecting a response while in this state. Error! rv = ERR_SPDY_PROTOCOL_ERROR; @@ -146,8 +157,8 @@ int SpdyStream::OnResponseReceived(const spdy::SpdyHeaderBlock& response) { rv = DoLoop(rv); if (delegate_) rv = delegate_->OnResponseReceived(*response_, response_time_, rv); - // if delegate_ is not yet attached, we'll return response when delegate - // gets attached to the stream. + // If delegate_ is not yet attached, we'll call OnResponseReceived after the + // delegate gets attached to the stream. return rv; } @@ -157,7 +168,20 @@ void SpdyStream::OnDataReceived(const char* data, int length) { LOG(INFO) << "SpdyStream: Data (" << length << " bytes) received for " << stream_id_; - CHECK(!response_complete_); + if (!delegate_ || continue_buffering_data_) { + // It should be valid for this to happen in the server push case. + // We'll return received data when delegate gets attached to the stream. + if (length > 0) { + IOBufferWithSize* buf = new IOBufferWithSize(length); + memcpy(buf->data(), data, length); + pending_buffers_.push_back(buf); + } + else + pending_buffers_.push_back(NULL); + return; + } + + CHECK(!response_complete_); // If we don't have a response, then the SYN_REPLY did not come through. // We cannot pass data up to the caller unless the reply headers have been @@ -248,16 +272,11 @@ int SpdyStream::DoSendRequest(bool has_upload_data) { DCHECK_EQ(io_state_, STATE_NONE); io_state_ = STATE_SEND_HEADERS; } else { + // Pushed stream should not have upload data. DCHECK(!has_upload_data); - if (!response_->empty()) { - // We already have response headers, so we don't need to read the header. - // Pushed stream should not have upload data. - // We don't need to call DoLoop() in this state. - DCHECK_EQ(io_state_, STATE_OPEN); - return OK; - } else { - io_state_ = STATE_READ_HEADERS; - } + DCHECK(!response_->empty()); + DCHECK_EQ(io_state_, STATE_OPEN); + return ERR_IO_PENDING; } return DoLoop(result); } diff --git a/net/spdy/spdy_stream.h b/net/spdy/spdy_stream.h index cdf3132..cad23f2 100644 --- a/net/spdy/spdy_stream.h +++ b/net/spdy/spdy_stream.h @@ -211,6 +211,15 @@ class SpdyStream : public base::RefCounted<SpdyStream> { // be called after the stream has completed. void UpdateHistograms(); + // When a server pushed stream is first created, this function is posted on + // the MessageLoop to replay all the data that the server has already sent. + void PushedStreamReplayData(); + + // There is a small period of time between when a server pushed stream is + // first created, and the pushed data is replayed. Any data received during + // this time should continue to be buffered. + bool continue_buffering_data_; + spdy::SpdyStreamId stream_id_; std::string path_; int priority_; diff --git a/net/spdy/spdy_test_util.cc b/net/spdy/spdy_test_util.cc index aa566d1..56d45ec 100644 --- a/net/spdy/spdy_test_util.cc +++ b/net/spdy/spdy_test_util.cc @@ -251,6 +251,57 @@ int ConstructSpdyHeader(const char* const extra_headers[], return n; } +spdy::SpdyFrame* ConstructSpdyControlFrame(const char* const extra_headers[], + int extra_header_count, + bool compressed, + int stream_id, + RequestPriority request_priority, + spdy::SpdyControlType type, + spdy::SpdyControlFlags flags, + const char* const* kHeaders, + int kHeadersSize) { + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + compressed, + stream_id, + request_priority, + type, + flags, + kHeaders, + kHeadersSize, + 0); +} + +spdy::SpdyFrame* ConstructSpdyControlFrame(const char* const extra_headers[], + int extra_header_count, + bool compressed, + int stream_id, + RequestPriority request_priority, + spdy::SpdyControlType type, + spdy::SpdyControlFlags flags, + const char* const* kHeaders, + int kHeadersSize, + int associated_stream_id) { + const SpdyHeaderInfo kSynStartHeader = { + type, // Kind = Syn + stream_id, // Stream ID + associated_stream_id, // Associated stream ID + net::ConvertRequestPriorityToSpdyPriority(request_priority), + // Priority + flags, // Control Flags + compressed, // Compressed + spdy::INVALID, // Status + NULL, // Data + 0, // Length + spdy::DATA_FLAG_NONE // Data Flags + }; + return ConstructSpdyPacket(kSynStartHeader, + extra_headers, + extra_header_count, + kHeaders, + kHeadersSize / 2); +} + // Constructs a standard SPDY GET SYN packet, optionally compressed // for the url |url|. // |extra_headers| are the extra header-value pairs, which typically @@ -319,19 +370,6 @@ spdy::SpdyFrame* ConstructSpdyGet(const char* const extra_headers[], bool compressed, int stream_id, RequestPriority request_priority) { - const SpdyHeaderInfo kSynStartHeader = { - spdy::SYN_STREAM, // Kind = Syn - stream_id, // Stream ID - 0, // Associated stream ID - net::ConvertRequestPriorityToSpdyPriority(request_priority), - // Priority - spdy::CONTROL_FLAG_FIN, // Control Flags - compressed, // Compressed - spdy::INVALID, // Status - NULL, // Data - 0, // Length - spdy::DATA_FLAG_NONE // Data Flags - }; static const char* const kStandardGetHeaders[] = { "method", "GET", @@ -344,12 +382,132 @@ spdy::SpdyFrame* ConstructSpdyGet(const char* const extra_headers[], "version", "HTTP/1.1" }; - return ConstructSpdyPacket( - kSynStartHeader, - extra_headers, - extra_header_count, - kStandardGetHeaders, - arraysize(kStandardGetHeaders) / 2); + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + compressed, + stream_id, + request_priority, + spdy::SYN_STREAM, + spdy::CONTROL_FLAG_FIN, + kStandardGetHeaders, + arraysize(kStandardGetHeaders)); +} + +// Constructs a standard SPDY push SYN packet. +// |extra_headers| are the extra header-value pairs, which typically +// will vary the most between calls. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdyPush(const char* const extra_headers[], + int extra_header_count, + int stream_id, + int associated_stream_id) { + const char* const kStandardGetHeaders[] = { + "hello", + "bye", + "status", + "200", + "version", + "HTTP/1.1" + }; + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + false, + stream_id, + LOWEST, + spdy::SYN_STREAM, + spdy::CONTROL_FLAG_NONE, + kStandardGetHeaders, + arraysize(kStandardGetHeaders), + associated_stream_id); +} + +spdy::SpdyFrame* ConstructSpdyPush(const char* const extra_headers[], + int extra_header_count, + int stream_id, + int associated_stream_id, + const char* path) { + const char* const kStandardGetHeaders[] = { + "hello", + "bye", + "path", + path, + "status", + "200 OK", + "url", + path, + "version", + "HTTP/1.1" + }; + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + false, + stream_id, + LOWEST, + spdy::SYN_STREAM, + spdy::CONTROL_FLAG_NONE, + kStandardGetHeaders, + arraysize(kStandardGetHeaders), + associated_stream_id); + +} +spdy::SpdyFrame* ConstructSpdyPush(const char* const extra_headers[], + int extra_header_count, + int stream_id, + int associated_stream_id, + const char* path, + const char* status, + const char* location, + const char* url) { + const char* const kStandardGetHeaders[] = { + "hello", + "bye", + "path", + path, + "status", + status, + "location", + location, + "url", + url, + "version", + "HTTP/1.1" + }; + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + false, + stream_id, + LOWEST, + spdy::SYN_STREAM, + spdy::CONTROL_FLAG_NONE, + kStandardGetHeaders, + arraysize(kStandardGetHeaders), + associated_stream_id); +} + +// Constructs a standard SPDY SYN_REPLY packet to match the SPDY GET. +// |extra_headers| are the extra header-value pairs, which typically +// will vary the most between calls. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdyGetSynReplyRedirect(int stream_id) { + static const char* const kStandardGetHeaders[] = { + "hello", + "bye", + "status", + "301 Moved Permanently", + "location", + "http://www.foo.com/index.php", + "version", + "HTTP/1.1" + }; + return ConstructSpdyControlFrame(NULL, + 0, + false, + stream_id, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + kStandardGetHeaders, + arraysize(kStandardGetHeaders)); } // Constructs a standard SPDY SYN_REPLY packet to match the SPDY GET. @@ -359,19 +517,6 @@ spdy::SpdyFrame* ConstructSpdyGet(const char* const extra_headers[], spdy::SpdyFrame* ConstructSpdyGetSynReply(const char* const extra_headers[], int extra_header_count, int stream_id) { - const SpdyHeaderInfo kSynStartHeader = { - spdy::SYN_REPLY, // Kind = SynReply - stream_id, // Stream ID - 0, // Associated stream ID - net::ConvertRequestPriorityToSpdyPriority(LOWEST), - // Priority - spdy::CONTROL_FLAG_NONE, // Control Flags - false, // Compressed - spdy::INVALID, // Status - NULL, // Data - 0, // Length - spdy::DATA_FLAG_NONE // Data Flags - }; static const char* const kStandardGetHeaders[] = { "hello", "bye", @@ -382,12 +527,15 @@ spdy::SpdyFrame* ConstructSpdyGetSynReply(const char* const extra_headers[], "version", "HTTP/1.1" }; - return ConstructSpdyPacket( - kSynStartHeader, - extra_headers, - extra_header_count, - kStandardGetHeaders, - arraysize(kStandardGetHeaders) / 2); + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + false, + stream_id, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + kStandardGetHeaders, + arraysize(kStandardGetHeaders)); } // Constructs a standard SPDY POST SYN packet. @@ -398,19 +546,6 @@ spdy::SpdyFrame* ConstructSpdyGetSynReply(const char* const extra_headers[], spdy::SpdyFrame* ConstructSpdyPost(int64 content_length, const char* const extra_headers[], int extra_header_count) { - const SpdyHeaderInfo kSynStartHeader = { - spdy::SYN_STREAM, // Kind = Syn - 1, // Stream ID - 0, // Associated stream ID - net::ConvertRequestPriorityToSpdyPriority(LOWEST), - // Priority - spdy::CONTROL_FLAG_NONE, // Control Flags - false, // Compressed - spdy::INVALID, // Status - NULL, // Data - 0, // Length - spdy::DATA_FLAG_NONE // Data Flags - }; std::string length_str = base::Int64ToString(content_length); const char* post_headers[] = { "method", @@ -426,12 +561,15 @@ spdy::SpdyFrame* ConstructSpdyPost(int64 content_length, "content-length", length_str.c_str() }; - return ConstructSpdyPacket( - kSynStartHeader, - extra_headers, - extra_header_count, - post_headers, - arraysize(post_headers) / 2); + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + false, + 1, + LOWEST, + spdy::SYN_STREAM, + spdy::CONTROL_FLAG_NONE, + post_headers, + arraysize(post_headers)); } // Constructs a standard SPDY SYN_REPLY packet to match the SPDY POST. @@ -440,19 +578,6 @@ spdy::SpdyFrame* ConstructSpdyPost(int64 content_length, // Returns a SpdyFrame. spdy::SpdyFrame* ConstructSpdyPostSynReply(const char* const extra_headers[], int extra_header_count) { - const SpdyHeaderInfo kSynStartHeader = { - spdy::SYN_REPLY, // Kind = SynReply - 1, // Stream ID - 0, // Associated stream ID - net::ConvertRequestPriorityToSpdyPriority(LOWEST), - // Priority - spdy::CONTROL_FLAG_NONE, // Control Flags - false, // Compressed - spdy::INVALID, // Status - NULL, // Data - 0, // Length - spdy::DATA_FLAG_NONE // Data Flags - }; static const char* const kStandardGetHeaders[] = { "hello", "bye", @@ -463,12 +588,15 @@ spdy::SpdyFrame* ConstructSpdyPostSynReply(const char* const extra_headers[], "version", "HTTP/1.1" }; - return ConstructSpdyPacket( - kSynStartHeader, - extra_headers, - extra_header_count, - kStandardGetHeaders, - arraysize(kStandardGetHeaders) / 2); + return ConstructSpdyControlFrame(extra_headers, + extra_header_count, + false, + 1, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + kStandardGetHeaders, + arraysize(kStandardGetHeaders)); } // Constructs a single SPDY data frame with the contents "hello!" @@ -607,4 +735,19 @@ ProxyService* SpdyCreateFixedProxyService(const std::string& proxy) { return ProxyService::CreateFixed(proxy_config); } +const SpdyHeaderInfo make_spdy_header(spdy::SpdyControlType type) { + const SpdyHeaderInfo kHeader = { + type, // Kind = Syn + 1, // Stream ID + 0, // Associated stream ID + 2, // Priority + spdy::CONTROL_FLAG_FIN, // Control Flags + false, // Compressed + spdy::INVALID, // Status + NULL, // Data + 0, // Length + spdy::DATA_FLAG_NONE // Data Flags + }; + return kHeader; +} } // namespace net diff --git a/net/spdy/spdy_test_util.h b/net/spdy/spdy_test_util.h index 26b9a87..ec60a30 100644 --- a/net/spdy/spdy_test_util.h +++ b/net/spdy/spdy_test_util.h @@ -11,11 +11,15 @@ #include "net/base/request_priority.h" #include "net/base/ssl_config_service_defaults.h" #include "net/http/http_auth_handler_factory.h" +#include "net/http/http_cache.h" #include "net/http/http_network_session.h" +#include "net/http/http_network_layer.h" +#include "net/http/http_transaction_factory.h" #include "net/proxy/proxy_service.h" #include "net/socket/socket_test_util.h" #include "net/spdy/spdy_framer.h" #include "net/spdy/spdy_session_pool.h" +#include "net/url_request/url_request_context.h" namespace net { @@ -109,6 +113,27 @@ spdy::SpdyFrame* ConstructSpdyPacket(const SpdyHeaderInfo& header_info, const char* const tail[], int tail_header_count); +// Construct a generic SpdyControlFrame. +spdy::SpdyFrame* ConstructSpdyControlFrame(const char* const extra_headers[], + int extra_header_count, + bool compressed, + int stream_id, + RequestPriority request_priority, + spdy::SpdyControlType type, + spdy::SpdyControlFlags flags, + const char* const* kHeaders, + int kHeadersSize); +spdy::SpdyFrame* ConstructSpdyControlFrame(const char* const extra_headers[], + int extra_header_count, + bool compressed, + int stream_id, + RequestPriority request_priority, + spdy::SpdyControlType type, + spdy::SpdyControlFlags flags, + const char* const* kHeaders, + int kHeadersSize, + int associated_stream_id); + // Construct an expected SPDY reply string. // |extra_headers| are the extra header-value pairs, which typically // will vary the most between calls. @@ -169,6 +194,28 @@ spdy::SpdyFrame* ConstructSpdyGet(const char* const extra_headers[], int stream_id, RequestPriority request_priority); +// Constructs a standard SPDY push SYN packet. +// |extra_headers| are the extra header-value pairs, which typically +// will vary the most between calls. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdyPush(const char* const extra_headers[], + int extra_header_count, + int stream_id, + int associated_stream_id); +spdy::SpdyFrame* ConstructSpdyPush(const char* const extra_headers[], + int extra_header_count, + int stream_id, + int associated_stream_id, + const char* path); +spdy::SpdyFrame* ConstructSpdyPush(const char* const extra_headers[], + int extra_header_count, + int stream_id, + int associated_stream_id, + const char* path, + const char* status, + const char* location, + const char* url); + // Constructs a standard SPDY SYN_REPLY packet to match the SPDY GET. // |extra_headers| are the extra header-value pairs, which typically // will vary the most between calls. @@ -177,6 +224,13 @@ spdy::SpdyFrame* ConstructSpdyGetSynReply(const char* const extra_headers[], int extra_header_count, int stream_id); +// Constructs a standard SPDY SYN_REPLY packet to match the SPDY GET. +// |extra_headers| are the extra header-value pairs, which typically +// will vary the most between calls. +// Returns a SpdyFrame. +spdy::SpdyFrame* ConstructSpdyGetSynReplyRedirect(int stream_id); + + // Constructs a standard SPDY POST SYN packet. // |extra_headers| are the extra header-value pairs, which typically // will vary the most between calls. @@ -261,10 +315,44 @@ class SpdySessionDependencies { } }; +class SpdyURLRequestContext : public URLRequestContext { + public: + SpdyURLRequestContext() { + host_resolver_ = new MockHostResolver; + proxy_service_ = ProxyService::CreateNull(); + spdy_session_pool_ = new SpdySessionPool(); + ssl_config_service_ = new SSLConfigServiceDefaults; + http_auth_handler_factory_ = HttpAuthHandlerFactory::CreateDefault(); + http_transaction_factory_ = new net::HttpCache( + new HttpNetworkLayer(&socket_factory_, + host_resolver_, + proxy_service_, + ssl_config_service_, + spdy_session_pool_.get(), + http_auth_handler_factory_, + network_delegate_, + NULL), + net::HttpCache::DefaultBackend::InMemory(0)); + } + + MockClientSocketFactory& socket_factory() { return socket_factory_; } + + protected: + virtual ~SpdyURLRequestContext() { + delete http_transaction_factory_; + delete http_auth_handler_factory_; + } + + private: + MockClientSocketFactory socket_factory_; + scoped_refptr<SpdySessionPool> spdy_session_pool_; +}; + // This creates a proxy for testing purposes. // |proxy| should be in the form "myproxy:70". ProxyService* SpdyCreateFixedProxyService(const std::string& proxy); +const SpdyHeaderInfo make_spdy_header(spdy::SpdyControlType type); } // namespace net #endif // NET_SPDY_SPDY_TEST_UTIL_H_ |