[SPDY] Refactor SpdyStream's handling of response headers
Rename OnResponseHeadersReceived() to
OnResponseHeadersUpdated() and remove some of
its extraneous parameters. Document its semantics and that
of the other delegate functions.
Fix bug in PushedStreamReplayData() where the stream being
closed/deleted wasn't handled correctly. Also fix memory leaks
of the pending frames.
Use continue_buffering_data_ only for push streams.
Rename request_/response_ to request_headers_/response_headers_.
Keep track of whether all required response headers are complete,
and send data to the delegate only after that becomes true.
Rename OnResponseHeadersReceived() and OnHeaders() to
On{Initial,Additional}ResponseHeadersReceived(), respectively.
Add MergeWithResponseHeaders() utility function and call it
from On{Initial,Additional}ResponseHeadersReceived(). Always convert
ERR_INCOMPLETE_SPDY_HEADERS to OK for push streams.
Rename ContainsUpperAscii() (ambiguous) to ContainsUppercaseAscii().
Move some tests from SpdyNetworkTransaction to SpdyStream.
Some other miscellaneous renaming and cleanup.
BUG=251442
[email protected]
Review URL: https://codereview.chromium.org/17382012
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@208169 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/net/spdy/spdy_stream_unittest.cc b/net/spdy/spdy_stream_unittest.cc
index df3c372..c27ad2e 100644
--- a/net/spdy/spdy_stream_unittest.cc
+++ b/net/spdy/spdy_stream_unittest.cc
@@ -206,11 +206,17 @@
session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_);
scoped_refptr<SpdySession> spdy_session(CreateSpdySession());
- MockRead reads[] = {
- MockRead(ASYNC, 0, 0), // EOF
- };
+ scoped_ptr<SpdyFrame> initial_window_update(
+ spdy_util_.ConstructSpdyWindowUpdate(
+ kSessionFlowControlStreamId,
+ kDefaultInitialRecvWindowSize - kSpdySessionInitialWindowSize));
+ if (spdy_util_.protocol() >= kProtoSPDY31)
+ AddWrite(*initial_window_update);
- OrderedSocketData data(reads, arraysize(reads), NULL, 0);
+ AddReadEOF();
+
+ OrderedSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
MockConnect connect_data(SYNCHRONOUS, OK);
data.set_connect_data(connect_data);
@@ -228,25 +234,30 @@
kSpdyStreamInitialWindowSize,
net_log);
stream.set_stream_id(2);
- EXPECT_FALSE(stream.response_received());
EXPECT_FALSE(stream.HasUrl());
// Set a couple of headers.
SpdyHeaderBlock response;
spdy_util_.AddUrlToHeaderBlock(kStreamUrl, &response);
- stream.OnResponseHeadersReceived(response);
+ stream.OnInitialResponseHeadersReceived(
+ response, base::Time::Now(), base::TimeTicks::Now());
// Send some basic headers.
SpdyHeaderBlock headers;
- response[spdy_util_.GetStatusKey()] = "200";
- response[spdy_util_.GetVersionKey()] = "OK";
- stream.OnHeaders(headers);
+ headers[spdy_util_.GetStatusKey()] = "200";
+ headers[spdy_util_.GetVersionKey()] = "OK";
+ stream.OnAdditionalResponseHeadersReceived(headers);
- stream.set_response_received();
- EXPECT_TRUE(stream.response_received());
EXPECT_TRUE(stream.HasUrl());
EXPECT_EQ(kStreamUrl, stream.GetUrl().spec());
+ StreamDelegateDoNothing delegate(stream.GetWeakPtr());
+ stream.SetDelegate(&delegate);
+
+ base::MessageLoop::current()->RunUntilIdle();
+
+ EXPECT_EQ("200", delegate.GetResponseHeaderValue(spdy_util_.GetStatusKey()));
+
spdy_session->CloseSessionOnError(
ERR_CONNECTION_CLOSED, true, "Closing session");
}
@@ -488,6 +499,336 @@
EXPECT_TRUE(data.at_write_eof());
}
+// Receiving a header with uppercase ASCII should result in a protocol
+// error.
+TEST_P(SpdyStreamTest, UpperCaseHeaders) {
+ GURL url(kStreamUrl);
+
+ session_ =
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_);
+
+ scoped_ptr<SpdyFrame> initial_window_update(
+ spdy_util_.ConstructSpdyWindowUpdate(
+ kSessionFlowControlStreamId,
+ kDefaultInitialRecvWindowSize - kSpdySessionInitialWindowSize));
+ if (spdy_util_.protocol() >= kProtoSPDY31)
+ AddWrite(*initial_window_update);
+
+ scoped_ptr<SpdyFrame> syn(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ AddWrite(*syn);
+
+ const char* const kExtraHeaders[] = {"X-UpperCase", "yes"};
+ scoped_ptr<SpdyFrame>
+ reply(spdy_util_.ConstructSpdyGetSynReply(kExtraHeaders, 1, 1));
+ AddRead(*reply);
+
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR));
+ AddWrite(*rst);
+
+ AddReadEOF();
+
+ DeterministicSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ scoped_refptr<SpdySession> session(CreateSpdySession());
+
+ InitializeSpdySession(session, host_port_pair_);
+
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(
+ SPDY_REQUEST_RESPONSE_STREAM, session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream.get() != NULL);
+
+ StreamDelegateDoNothing delegate(stream);
+ stream->SetDelegate(&delegate);
+
+ EXPECT_FALSE(stream->HasUrl());
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlock(kStreamUrl));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrl());
+ EXPECT_EQ(kStreamUrl, stream->GetUrl().spec());
+
+ // For the initial window update.
+ if (spdy_util_.protocol() >= kProtoSPDY31)
+ data.RunFor(1);
+
+ data.RunFor(4);
+
+ EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, delegate.WaitForClose());
+}
+
+// Receiving a header with uppercase ASCII should result in a protocol
+// error even for a push stream.
+TEST_P(SpdyStreamTest, UpperCaseHeadersOnPush) {
+ GURL url(kStreamUrl);
+
+ session_ =
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_);
+
+ scoped_ptr<SpdyFrame> initial_window_update(
+ spdy_util_.ConstructSpdyWindowUpdate(
+ kSessionFlowControlStreamId,
+ kDefaultInitialRecvWindowSize - kSpdySessionInitialWindowSize));
+ if (spdy_util_.protocol() >= kProtoSPDY31)
+ AddWrite(*initial_window_update);
+
+ scoped_ptr<SpdyFrame> syn(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ AddWrite(*syn);
+
+ scoped_ptr<SpdyFrame>
+ reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ AddRead(*reply);
+
+ const char* const extra_headers[] = {"X-UpperCase", "yes"};
+ scoped_ptr<SpdyFrame>
+ push(spdy_util_.ConstructSpdyPush(extra_headers, 1, 2, 1, kStreamUrl));
+ AddRead(*push);
+
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_PROTOCOL_ERROR));
+ AddWrite(*rst);
+
+ AddReadEOF();
+
+ DeterministicSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ scoped_refptr<SpdySession> session(CreateSpdySession());
+
+ InitializeSpdySession(session, host_port_pair_);
+
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(
+ SPDY_REQUEST_RESPONSE_STREAM, session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream.get() != NULL);
+
+ StreamDelegateDoNothing delegate(stream);
+ stream->SetDelegate(&delegate);
+
+ EXPECT_FALSE(stream->HasUrl());
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlock(kStreamUrl));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrl());
+ EXPECT_EQ(kStreamUrl, stream->GetUrl().spec());
+
+ // For the initial window update.
+ if (spdy_util_.protocol() >= kProtoSPDY31)
+ data.RunFor(1);
+
+ data.RunFor(4);
+
+ base::WeakPtr<SpdyStream> push_stream;
+ EXPECT_EQ(OK, session->GetPushStream(url, &push_stream, BoundNetLog()));
+ EXPECT_FALSE(push_stream);
+
+ data.RunFor(1);
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose());
+}
+
+// Receiving a header with uppercase ASCII in a HEADERS frame should
+// result in a protocol error.
+TEST_P(SpdyStreamTest, UpperCaseHeadersInHeadersFrame) {
+ GURL url(kStreamUrl);
+
+ session_ =
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_);
+
+ scoped_ptr<SpdyFrame> initial_window_update(
+ spdy_util_.ConstructSpdyWindowUpdate(
+ kSessionFlowControlStreamId,
+ kDefaultInitialRecvWindowSize - kSpdySessionInitialWindowSize));
+ if (spdy_util_.protocol() >= kProtoSPDY31)
+ AddWrite(*initial_window_update);
+
+ scoped_ptr<SpdyFrame> syn(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ AddWrite(*syn);
+
+ scoped_ptr<SpdyFrame>
+ reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ AddRead(*reply);
+
+ scoped_ptr<SpdyFrame>
+ push(spdy_util_.ConstructSpdyPush(NULL, 0, 2, 1, kStreamUrl));
+ AddRead(*push);
+
+ scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock());
+ (*late_headers)["X-UpperCase"] = "yes";
+ scoped_ptr<SpdyFrame> headers_frame(
+ spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(),
+ false,
+ 2,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ 0));
+ AddRead(*headers_frame);
+
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_PROTOCOL_ERROR));
+ AddWrite(*rst);
+
+ AddReadEOF();
+
+ DeterministicSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ scoped_refptr<SpdySession> session(CreateSpdySession());
+
+ InitializeSpdySession(session, host_port_pair_);
+
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(
+ SPDY_REQUEST_RESPONSE_STREAM, session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream.get() != NULL);
+
+ StreamDelegateDoNothing delegate(stream);
+ stream->SetDelegate(&delegate);
+
+ EXPECT_FALSE(stream->HasUrl());
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlock(kStreamUrl));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrl());
+ EXPECT_EQ(kStreamUrl, stream->GetUrl().spec());
+
+ // For the initial window update.
+ if (spdy_util_.protocol() >= kProtoSPDY31)
+ data.RunFor(1);
+
+ data.RunFor(3);
+
+ base::WeakPtr<SpdyStream> push_stream;
+ EXPECT_EQ(OK, session->GetPushStream(url, &push_stream, BoundNetLog()));
+ EXPECT_TRUE(push_stream);
+
+ data.RunFor(1);
+
+ EXPECT_EQ(OK, session->GetPushStream(url, &push_stream, BoundNetLog()));
+ EXPECT_FALSE(push_stream);
+
+ data.RunFor(2);
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose());
+}
+
+// Receiving a duplicate header in a HEADERS frame should result in a
+// protocol error.
+TEST_P(SpdyStreamTest, DuplicateHeaders) {
+ GURL url(kStreamUrl);
+
+ session_ =
+ SpdySessionDependencies::SpdyCreateSessionDeterministic(&session_deps_);
+
+ scoped_ptr<SpdyFrame> initial_window_update(
+ spdy_util_.ConstructSpdyWindowUpdate(
+ kSessionFlowControlStreamId,
+ kDefaultInitialRecvWindowSize - kSpdySessionInitialWindowSize));
+ if (spdy_util_.protocol() >= kProtoSPDY31)
+ AddWrite(*initial_window_update);
+
+ scoped_ptr<SpdyFrame> syn(
+ spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
+ AddWrite(*syn);
+
+ scoped_ptr<SpdyFrame>
+ reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
+ AddRead(*reply);
+
+ scoped_ptr<SpdyFrame>
+ push(spdy_util_.ConstructSpdyPush(NULL, 0, 2, 1, kStreamUrl));
+ AddRead(*push);
+
+ scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock());
+ (*late_headers)[spdy_util_.GetStatusKey()] = "500 Server Error";
+ scoped_ptr<SpdyFrame> headers_frame(
+ spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(),
+ false,
+ 2,
+ LOWEST,
+ HEADERS,
+ CONTROL_FLAG_NONE,
+ 0));
+ AddRead(*headers_frame);
+
+ scoped_ptr<SpdyFrame> rst(
+ spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_PROTOCOL_ERROR));
+ AddWrite(*rst);
+
+ AddReadEOF();
+
+ DeterministicSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
+ MockConnect connect_data(SYNCHRONOUS, OK);
+ data.set_connect_data(connect_data);
+
+ session_deps_.deterministic_socket_factory->AddSocketDataProvider(&data);
+
+ scoped_refptr<SpdySession> session(CreateSpdySession());
+
+ InitializeSpdySession(session, host_port_pair_);
+
+ base::WeakPtr<SpdyStream> stream =
+ CreateStreamSynchronously(
+ SPDY_REQUEST_RESPONSE_STREAM, session, url, LOWEST, BoundNetLog());
+ ASSERT_TRUE(stream.get() != NULL);
+
+ StreamDelegateDoNothing delegate(stream);
+ stream->SetDelegate(&delegate);
+
+ EXPECT_FALSE(stream->HasUrl());
+
+ scoped_ptr<SpdyHeaderBlock> headers(
+ spdy_util_.ConstructGetHeaderBlock(kStreamUrl));
+ EXPECT_EQ(ERR_IO_PENDING,
+ stream->SendRequestHeaders(headers.Pass(), NO_MORE_DATA_TO_SEND));
+ EXPECT_TRUE(stream->HasUrl());
+ EXPECT_EQ(kStreamUrl, stream->GetUrl().spec());
+
+ // For the initial window update.
+ if (spdy_util_.protocol() >= kProtoSPDY31)
+ data.RunFor(1);
+
+ data.RunFor(3);
+
+ base::WeakPtr<SpdyStream> push_stream;
+ EXPECT_EQ(OK, session->GetPushStream(url, &push_stream, BoundNetLog()));
+ EXPECT_TRUE(push_stream);
+
+ data.RunFor(1);
+
+ EXPECT_EQ(OK, session->GetPushStream(url, &push_stream, BoundNetLog()));
+ EXPECT_FALSE(push_stream);
+
+ data.RunFor(2);
+
+ EXPECT_EQ(ERR_CONNECTION_CLOSED, delegate.WaitForClose());
+}
+
// The tests below are only for SPDY/3 and above.
// Call IncreaseSendWindowSize on a stream with a large enough delta