summaryrefslogtreecommitdiffstats
path: root/net/spdy
diff options
context:
space:
mode:
authorerikchen@google.com <erikchen@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2010-08-05 17:59:58 +0000
committererikchen@google.com <erikchen@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2010-08-05 17:59:58 +0000
commite3ebba0fbbfb2c7eec286a964717859aa70b9fcf (patch)
treeb2a53d996ca4537fbb6516dcddba122d1cb6cbcd /net/spdy
parent1b7cde151f8e0e922a8c52c6ca48f6f1c6de21ec (diff)
downloadchromium_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.cc13
-rw-r--r--net/spdy/spdy_network_transaction_unittest.cc669
-rw-r--r--net/spdy/spdy_protocol.h3
-rw-r--r--net/spdy/spdy_session.cc193
-rw-r--r--net/spdy/spdy_session.h13
-rw-r--r--net/spdy/spdy_session_unittest.cc78
-rw-r--r--net/spdy/spdy_stream.cc69
-rw-r--r--net/spdy/spdy_stream.h9
-rw-r--r--net/spdy/spdy_test_util.cc295
-rw-r--r--net/spdy/spdy_test_util.h88
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_