summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorakalin@chromium.org <akalin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-06-24 07:42:15 +0000
committerakalin@chromium.org <akalin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-06-24 07:42:15 +0000
commit6d116e1a351d44ced9f14fa6f7a56b2be487c8a1 (patch)
treeb230d09942c3babacc2d1bf5e80f50064c653b90 /net
parente87089dbddd82041c1cceab2a886e8cf1f528931 (diff)
downloadchromium_src-6d116e1a351d44ced9f14fa6f7a56b2be487c8a1.zip
chromium_src-6d116e1a351d44ced9f14fa6f7a56b2be487c8a1.tar.gz
chromium_src-6d116e1a351d44ced9f14fa6f7a56b2be487c8a1.tar.bz2
[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 R=rch@chromium.org Review URL: https://codereview.chromium.org/17382012 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@208169 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net')
-rw-r--r--net/spdy/spdy_http_stream.cc48
-rw-r--r--net/spdy/spdy_http_stream.h11
-rw-r--r--net/spdy/spdy_http_utils.cc2
-rw-r--r--net/spdy/spdy_network_transaction_unittest.cc184
-rw-r--r--net/spdy/spdy_proxy_client_socket.cc19
-rw-r--r--net/spdy/spdy_proxy_client_socket.h7
-rw-r--r--net/spdy/spdy_session.cc22
-rw-r--r--net/spdy/spdy_session.h5
-rw-r--r--net/spdy/spdy_session_unittest.cc4
-rw-r--r--net/spdy/spdy_stream.cc318
-rw-r--r--net/spdy/spdy_stream.h171
-rw-r--r--net/spdy/spdy_stream_test_util.cc41
-rw-r--r--net/spdy/spdy_stream_test_util.h23
-rw-r--r--net/spdy/spdy_stream_unittest.cc363
-rw-r--r--net/spdy/spdy_websocket_stream.cc11
-rw-r--r--net/spdy/spdy_websocket_stream.h14
-rw-r--r--net/spdy/spdy_websocket_stream_spdy2_unittest.cc9
-rw-r--r--net/spdy/spdy_websocket_stream_spdy3_unittest.cc9
-rw-r--r--net/websockets/websocket_job.cc11
-rw-r--r--net/websockets/websocket_job.h4
20 files changed, 745 insertions, 531 deletions
diff --git a/net/spdy/spdy_http_stream.cc b/net/spdy/spdy_http_stream.cc
index 9241424..77981fd 100644
--- a/net/spdy/spdy_http_stream.cc
+++ b/net/spdy/spdy_http_stream.cc
@@ -33,7 +33,7 @@ SpdyHttpStream::SpdyHttpStream(SpdySession* spdy_session, bool direct)
closed_stream_id_(0),
request_info_(NULL),
response_info_(NULL),
- response_headers_received_(false),
+ response_headers_status_(RESPONSE_HEADERS_ARE_INCOMPLETE),
user_buffer_len_(0),
request_body_buf_size_(0),
buffered_read_callback_pending_(false),
@@ -106,7 +106,7 @@ int SpdyHttpStream::ReadResponseHeaders(const CompletionCallback& callback) {
CHECK(stream_.get());
// Check if we already have the response headers. If so, return synchronously.
- if(stream_->response_received()) {
+ if (response_headers_status_ == RESPONSE_HEADERS_ARE_COMPLETE) {
CHECK(stream_->is_idle());
return OK;
}
@@ -292,32 +292,24 @@ void SpdyHttpStream::OnRequestHeadersSent() {
ReadAndSendRequestBodyData();
}
-int SpdyHttpStream::OnResponseHeadersReceived(const SpdyHeaderBlock& response,
- base::Time response_time,
- int status) {
+SpdyResponseHeadersStatus SpdyHttpStream::OnResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) {
+ CHECK_EQ(response_headers_status_, RESPONSE_HEADERS_ARE_INCOMPLETE);
+
if (!response_info_) {
DCHECK_EQ(stream_->type(), SPDY_PUSH_STREAM);
push_response_info_.reset(new HttpResponseInfo);
response_info_ = push_response_info_.get();
}
- // If the response is already received, these headers are too late.
- if (response_headers_received_) {
- LOG(WARNING) << "SpdyHttpStream headers received after response started.";
- return OK;
+ if (!SpdyHeadersToHttpResponse(
+ response_headers, stream_->GetProtocolVersion(), response_info_)) {
+ // We do not have complete headers yet.
+ return RESPONSE_HEADERS_ARE_INCOMPLETE;
}
- // TODO(mbelshe): This is the time of all headers received, not just time
- // to first byte.
- response_info_->response_time = base::Time::Now();
-
- if (!SpdyHeadersToHttpResponse(response, stream_->GetProtocolVersion(),
- response_info_)) {
- // We might not have complete headers yet.
- return ERR_INCOMPLETE_SPDY_HEADERS;
- }
-
- response_headers_received_ = true;
+ response_info_->response_time = stream_->response_time();
+ response_headers_status_ = RESPONSE_HEADERS_ARE_COMPLETE;
// Don't store the SSLInfo in the response here, HttpNetworkTransaction
// will take care of that part.
SSLInfo ssl_info;
@@ -343,22 +335,15 @@ int SpdyHttpStream::OnResponseHeadersReceived(const SpdyHeaderBlock& response,
}
response_info_->vary_data
.Init(*request_info_, *response_info_->headers.get());
- // TODO(ahendrickson): This is recorded after the entire SYN_STREAM control
- // frame has been received and processed. Move to framer?
- response_info_->response_time = response_time;
if (!callback_.is_null())
- DoCallback(status);
+ DoCallback(OK);
- return status;
+ return RESPONSE_HEADERS_ARE_COMPLETE;
}
-int SpdyHttpStream::OnDataReceived(scoped_ptr<SpdyBuffer> buffer) {
- // SpdyStream won't call us with data if the header block didn't contain a
- // valid set of headers. So we don't expect to not have headers received
- // here.
- if (!response_headers_received_)
- return ERR_INCOMPLETE_SPDY_HEADERS;
+void SpdyHttpStream::OnDataReceived(scoped_ptr<SpdyBuffer> buffer) {
+ CHECK_EQ(response_headers_status_, RESPONSE_HEADERS_ARE_COMPLETE);
// Note that data may be received for a SpdyStream prior to the user calling
// ReadResponseBody(), therefore user_buffer_ may be NULL. This may often
@@ -374,7 +359,6 @@ int SpdyHttpStream::OnDataReceived(scoped_ptr<SpdyBuffer> buffer) {
ScheduleBufferedReadCallback();
}
}
- return OK;
}
void SpdyHttpStream::OnDataSent() {
diff --git a/net/spdy/spdy_http_stream.h b/net/spdy/spdy_http_stream.h
index 1ce0290..33c253c 100644
--- a/net/spdy/spdy_http_stream.h
+++ b/net/spdy/spdy_http_stream.h
@@ -76,10 +76,9 @@ class NET_EXPORT_PRIVATE SpdyHttpStream : public SpdyStream::Delegate,
// SpdyStream::Delegate implementation.
virtual void OnRequestHeadersSent() OVERRIDE;
- virtual int OnResponseHeadersReceived(const SpdyHeaderBlock& response,
- base::Time response_time,
- int status) OVERRIDE;
- virtual int OnDataReceived(scoped_ptr<SpdyBuffer> buffer) OVERRIDE;
+ virtual SpdyResponseHeadersStatus OnResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) OVERRIDE;
+ virtual void OnDataReceived(scoped_ptr<SpdyBuffer> buffer) OVERRIDE;
virtual void OnDataSent() OVERRIDE;
virtual void OnClose(int status) OVERRIDE;
@@ -130,7 +129,9 @@ class NET_EXPORT_PRIVATE SpdyHttpStream : public SpdyStream::Delegate,
scoped_ptr<HttpResponseInfo> push_response_info_;
- bool response_headers_received_; // Indicates waiting for more HEADERS.
+ // We don't use SpdyStream's |response_header_status_| as we
+ // sometimes call back into our delegate before it is updated.
+ SpdyResponseHeadersStatus response_headers_status_;
// We buffer the response body as it arrives asynchronously from the stream.
SpdyReadQueue response_body_queue_;
diff --git a/net/spdy/spdy_http_utils.cc b/net/spdy/spdy_http_utils.cc
index bfd1665..5fa0061 100644
--- a/net/spdy/spdy_http_utils.cc
+++ b/net/spdy/spdy_http_utils.cc
@@ -40,8 +40,6 @@ bool SpdyHeadersToHttpResponse(const SpdyHeaderBlock& headers,
return false;
version = it->second;
- response->response_time = base::Time::Now();
-
std::string raw_headers(version);
raw_headers.push_back(' ');
raw_headers.append(status);
diff --git a/net/spdy/spdy_network_transaction_unittest.cc b/net/spdy/spdy_network_transaction_unittest.cc
index c8813e2..f0e21571 100644
--- a/net/spdy/spdy_network_transaction_unittest.cc
+++ b/net/spdy/spdy_network_transaction_unittest.cc
@@ -3223,133 +3223,6 @@ TEST_P(SpdyNetworkTransactionTest, RedirectGetRequest) {
EXPECT_TRUE(data2.at_write_eof());
}
-// Detect response with upper case headers and reset the stream.
-TEST_P(SpdyNetworkTransactionTest, UpperCaseHeaders) {
- if (GetParam().protocol > kProtoSPDY3)
- return;
-
- scoped_ptr<SpdyFrame> syn(
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
- scoped_ptr<SpdyFrame> rst(
- spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR));
- MockWrite writes[] = {
- CreateMockWrite(*syn, 0),
- CreateMockWrite(*rst, 2),
- };
-
- const char* const kExtraHeaders[] = {"X-UpperCase", "yes"};
- scoped_ptr<SpdyFrame>
- reply(spdy_util_.ConstructSpdyGetSynReply(kExtraHeaders, 1, 1));
- MockRead reads[] = {
- CreateMockRead(*reply, 1),
- MockRead(ASYNC, ERR_IO_PENDING, 3), // Force a pause
- };
-
- HttpResponseInfo response;
- HttpResponseInfo response2;
- OrderedSocketData data(reads, arraysize(reads),
- writes, arraysize(writes));
- NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
- BoundNetLog(), GetParam(), NULL);
- helper.RunToCompletion(&data);
- TransactionHelperResult out = helper.output();
- EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
-}
-
-// Detect response with upper case headers in a HEADERS frame and reset the
-// stream.
-TEST_P(SpdyNetworkTransactionTest, UpperCaseHeadersInHeadersFrame) {
- if (GetParam().protocol > kProtoSPDY3)
- return;
-
- scoped_ptr<SpdyFrame> syn(
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
- scoped_ptr<SpdyFrame> rst(
- spdy_util_.ConstructSpdyRstStream(1, RST_STREAM_PROTOCOL_ERROR));
- MockWrite writes[] = {
- CreateMockWrite(*syn, 0),
- CreateMockWrite(*rst, 2),
- };
-
- scoped_ptr<SpdyHeaderBlock> initial_headers(new SpdyHeaderBlock());
- (*initial_headers)[spdy_util_.GetStatusKey()] = "200 OK";
- (*initial_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1";
- scoped_ptr<SpdyFrame> stream1_reply(
- spdy_util_.ConstructSpdyControlFrame(initial_headers.Pass(),
- false,
- 1,
- LOWEST,
- SYN_REPLY,
- CONTROL_FLAG_NONE,
- 0));
-
- scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock());
- (*late_headers)["X-UpperCase"] = "yes";
- scoped_ptr<SpdyFrame> stream1_headers(
- spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(),
- false,
- 1,
- LOWEST,
- HEADERS,
- CONTROL_FLAG_NONE,
- 0));
- scoped_ptr<SpdyFrame> stream1_body(
- spdy_util_.ConstructSpdyBodyFrame(1, true));
- MockRead reads[] = {
- CreateMockRead(*stream1_reply),
- CreateMockRead(*stream1_headers),
- CreateMockRead(*stream1_body),
- MockRead(ASYNC, 0, 0) // EOF
- };
-
- DelayedSocketData data(1, reads, arraysize(reads),
- writes, arraysize(writes));
- NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
- BoundNetLog(), GetParam(), NULL);
- helper.RunToCompletion(&data);
- TransactionHelperResult out = helper.output();
- EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
-}
-
-// Detect push stream with upper case headers and reset the stream.
-TEST_P(SpdyNetworkTransactionTest, UpperCaseHeadersOnPush) {
- if (GetParam().protocol > kProtoSPDY3)
- return;
-
- scoped_ptr<SpdyFrame> syn(
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
- scoped_ptr<SpdyFrame> rst(
- spdy_util_.ConstructSpdyRstStream(2, RST_STREAM_PROTOCOL_ERROR));
- MockWrite writes[] = {
- CreateMockWrite(*syn, 0),
- CreateMockWrite(*rst, 2),
- };
-
- scoped_ptr<SpdyFrame>
- reply(spdy_util_.ConstructSpdyGetSynReply(NULL, 0, 1));
- const char* const extra_headers[] = {"X-UpperCase", "yes"};
- scoped_ptr<SpdyFrame>
- push(spdy_util_.ConstructSpdyPush(
- extra_headers, 1, 2, 1, "http://www.google.com"));
- scoped_ptr<SpdyFrame> body(spdy_util_.ConstructSpdyBodyFrame(1, true));
- MockRead reads[] = {
- CreateMockRead(*reply, 1),
- CreateMockRead(*push, 1),
- CreateMockRead(*body, 1),
- MockRead(ASYNC, ERR_IO_PENDING, 3), // Force a pause
- };
-
- HttpResponseInfo response;
- HttpResponseInfo response2;
- OrderedSocketData data(reads, arraysize(reads),
- writes, arraysize(writes));
- NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
- BoundNetLog(), GetParam(), NULL);
- helper.RunToCompletion(&data);
- TransactionHelperResult out = helper.output();
- EXPECT_EQ(OK, out.rv);
-}
-
// Send a spdy request to www.google.com. Get a pushed stream that redirects to
// www.foo.com.
TEST_P(SpdyNetworkTransactionTest, RedirectServerPush) {
@@ -6202,11 +6075,7 @@ TEST_P(SpdyNetworkTransactionTest, ServerPushWithTwoHeaderFrames) {
ReadResult(trans, &data, &result);
// Verify that the received push data is same as the expected push data.
- EXPECT_EQ(result2.compare(expected_push_result), 0)
- << "Received data: "
- << result2
- << "||||| Expected data: "
- << expected_push_result;
+ EXPECT_EQ(expected_push_result, result2);
// Verify the SYN_REPLY.
// Copy the response info, because trans goes away.
@@ -6469,57 +6338,6 @@ TEST_P(SpdyNetworkTransactionTest, SynReplyWithLateHeaders) {
EXPECT_EQ("hello!hello!", out.response_data);
}
-TEST_P(SpdyNetworkTransactionTest, SynReplyWithDuplicateLateHeaders) {
- if (GetParam().protocol > kProtoSPDY3)
- return;
-
- scoped_ptr<SpdyFrame> req(
- spdy_util_.ConstructSpdyGet(NULL, 0, false, 1, LOWEST, true));
- MockWrite writes[] = { CreateMockWrite(*req) };
-
- scoped_ptr<SpdyHeaderBlock> initial_headers(new SpdyHeaderBlock());
- (*initial_headers)[spdy_util_.GetStatusKey()] = "200 OK";
- (*initial_headers)[spdy_util_.GetVersionKey()] = "HTTP/1.1";
- scoped_ptr<SpdyFrame> stream1_reply(
- spdy_util_.ConstructSpdyControlFrame(initial_headers.Pass(),
- false,
- 1,
- LOWEST,
- SYN_REPLY,
- CONTROL_FLAG_NONE,
- 0));
-
- scoped_ptr<SpdyHeaderBlock> late_headers(new SpdyHeaderBlock());
- (*late_headers)[spdy_util_.GetStatusKey()] = "500 Server Error";
- scoped_ptr<SpdyFrame> stream1_headers(
- spdy_util_.ConstructSpdyControlFrame(late_headers.Pass(),
- false,
- 1,
- LOWEST,
- HEADERS,
- CONTROL_FLAG_NONE,
- 0));
- scoped_ptr<SpdyFrame> stream1_body(
- spdy_util_.ConstructSpdyBodyFrame(1, false));
- scoped_ptr<SpdyFrame> stream1_body2(
- spdy_util_.ConstructSpdyBodyFrame(1, true));
- MockRead reads[] = {
- CreateMockRead(*stream1_reply),
- CreateMockRead(*stream1_body),
- CreateMockRead(*stream1_headers),
- CreateMockRead(*stream1_body2),
- MockRead(ASYNC, 0, 0) // EOF
- };
-
- DelayedSocketData data(1, reads, arraysize(reads),
- writes, arraysize(writes));
- NormalSpdyTransactionHelper helper(CreateGetRequest(), DEFAULT_PRIORITY,
- BoundNetLog(), GetParam(), NULL);
- helper.RunToCompletion(&data);
- TransactionHelperResult out = helper.output();
- EXPECT_EQ(ERR_SPDY_PROTOCOL_ERROR, out.rv);
-}
-
TEST_P(SpdyNetworkTransactionTest, ServerPushCrossOriginCorrectness) {
if (GetParam().protocol > kProtoSPDY3)
return;
diff --git a/net/spdy/spdy_proxy_client_socket.cc b/net/spdy/spdy_proxy_client_socket.cc
index 641637a..d7b97c2 100644
--- a/net/spdy/spdy_proxy_client_socket.cc
+++ b/net/spdy/spdy_proxy_client_socket.cc
@@ -439,27 +439,25 @@ void SpdyProxyClientSocket::OnRequestHeadersSent() {
OnIOComplete(OK);
}
-int SpdyProxyClientSocket::OnResponseHeadersReceived(
- const SpdyHeaderBlock& response,
- base::Time response_time,
- int status) {
+SpdyResponseHeadersStatus SpdyProxyClientSocket::OnResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) {
// If we've already received the reply, existing headers are too late.
// TODO(mbelshe): figure out a way to make HEADERS frames useful after the
// initial response.
if (next_state_ != STATE_READ_REPLY_COMPLETE)
- return OK;
+ return RESPONSE_HEADERS_ARE_COMPLETE;
// Save the response
if (!SpdyHeadersToHttpResponse(
- response, spdy_stream_->GetProtocolVersion(), &response_))
- return ERR_INCOMPLETE_SPDY_HEADERS;
+ response_headers, spdy_stream_->GetProtocolVersion(), &response_))
+ return RESPONSE_HEADERS_ARE_INCOMPLETE;
- OnIOComplete(status);
- return OK;
+ OnIOComplete(OK);
+ return RESPONSE_HEADERS_ARE_COMPLETE;
}
// Called when data is received or on EOF (if |buffer| is NULL).
-int SpdyProxyClientSocket::OnDataReceived(scoped_ptr<SpdyBuffer> buffer) {
+void SpdyProxyClientSocket::OnDataReceived(scoped_ptr<SpdyBuffer> buffer) {
if (buffer) {
net_log_.AddByteTransferEvent(NetLog::TYPE_SOCKET_BYTES_RECEIVED,
buffer->GetRemainingSize(),
@@ -477,7 +475,6 @@ int SpdyProxyClientSocket::OnDataReceived(scoped_ptr<SpdyBuffer> buffer) {
user_buffer_len_ = 0;
c.Run(rv);
}
- return OK;
}
void SpdyProxyClientSocket::OnDataSent() {
diff --git a/net/spdy/spdy_proxy_client_socket.h b/net/spdy/spdy_proxy_client_socket.h
index be0ed69..3d79331 100644
--- a/net/spdy/spdy_proxy_client_socket.h
+++ b/net/spdy/spdy_proxy_client_socket.h
@@ -93,10 +93,9 @@ class NET_EXPORT_PRIVATE SpdyProxyClientSocket : public ProxyClientSocket,
// SpdyStream::Delegate implementation.
virtual void OnRequestHeadersSent() OVERRIDE;
- virtual int OnResponseHeadersReceived(const SpdyHeaderBlock& response,
- base::Time response_time,
- int status) OVERRIDE;
- virtual int OnDataReceived(scoped_ptr<SpdyBuffer> buffer) OVERRIDE;
+ virtual SpdyResponseHeadersStatus OnResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) OVERRIDE;
+ virtual void OnDataReceived(scoped_ptr<SpdyBuffer> buffer) OVERRIDE;
virtual void OnDataSent() OVERRIDE;
virtual void OnClose(int status) OVERRIDE;
diff --git a/net/spdy/spdy_session.cc b/net/spdy/spdy_session.cc
index 1f93c03..e605243 100644
--- a/net/spdy/spdy_session.cc
+++ b/net/spdy/spdy_session.cc
@@ -1638,11 +1638,15 @@ void SpdySession::OnSynStreamCompressed(
}
-bool SpdySession::Respond(const SpdyHeaderBlock& headers, SpdyStream* stream) {
+bool SpdySession::Respond(const SpdyHeaderBlock& response_headers,
+ base::Time response_time,
+ base::TimeTicks recv_first_byte_time,
+ SpdyStream* stream) {
int rv = OK;
SpdyStreamId stream_id = stream->stream_id();
// May invalidate |stream|.
- rv = stream->OnResponseHeadersReceived(headers);
+ rv = stream->OnInitialResponseHeadersReceived(
+ response_headers, response_time, recv_first_byte_time);
if (rv < 0) {
DCHECK_NE(rv, ERR_IO_PENDING);
CloseActiveStream(stream_id, rv);
@@ -1658,6 +1662,9 @@ void SpdySession::OnSynStream(SpdyStreamId stream_id,
bool fin,
bool unidirectional,
const SpdyHeaderBlock& headers) {
+ base::Time response_time = base::Time::Now();
+ base::TimeTicks recv_first_byte_time = base::TimeTicks::Now();
+
if (net_log_.IsLoggingAllEvents()) {
net_log_.AddEvent(
NetLog::TYPE_SPDY_SESSION_PUSHED_SYN_STREAM,
@@ -1753,7 +1760,6 @@ void SpdySession::OnSynStream(SpdyStreamId stream_id,
DeleteExpiredPushedStreams();
unclaimed_pushed_streams_[url] = PushedStreamInfo(stream_id, time_func_());
- stream->set_response_received();
InsertActivatedStream(stream.Pass());
ActiveStreamMap::iterator it = active_streams_.find(stream_id);
@@ -1763,7 +1769,7 @@ void SpdySession::OnSynStream(SpdyStreamId stream_id,
}
// Parse the headers.
- if (!Respond(headers, it->second.stream))
+ if (!Respond(headers, response_time, recv_first_byte_time, it->second.stream))
return;
base::StatsCounter push_requests("spdy.pushed_streams");
@@ -1808,6 +1814,9 @@ void SpdySession::DeleteExpiredPushedStreams() {
void SpdySession::OnSynReply(SpdyStreamId stream_id,
bool fin,
const SpdyHeaderBlock& headers) {
+ base::Time response_time = base::Time::Now();
+ base::TimeTicks recv_first_byte_time = base::TimeTicks::Now();
+
if (net_log().IsLoggingAllEvents()) {
net_log().AddEvent(
NetLog::TYPE_SPDY_SESSION_SYN_REPLY,
@@ -1833,10 +1842,9 @@ void SpdySession::OnSynReply(SpdyStreamId stream_id,
RST_STREAM_STREAM_IN_USE, error);
return;
}
- stream->set_response_received();
it->second.waiting_for_syn_reply = false;
- Respond(headers, stream);
+ Respond(headers, response_time, recv_first_byte_time, stream);
}
void SpdySession::OnHeaders(SpdyStreamId stream_id,
@@ -1860,7 +1868,7 @@ void SpdySession::OnHeaders(SpdyStreamId stream_id,
SpdyStream* stream = it->second.stream;
CHECK_EQ(stream->stream_id(), stream_id);
- int rv = stream->OnHeaders(headers);
+ int rv = stream->OnAdditionalResponseHeadersReceived(headers);
if (rv < 0) {
DCHECK_NE(rv, ERR_IO_PENDING);
CloseActiveStream(stream_id, rv);
diff --git a/net/spdy/spdy_session.h b/net/spdy/spdy_session.h
index 35e24f2..a0642548 100644
--- a/net/spdy/spdy_session.h
+++ b/net/spdy/spdy_session.h
@@ -636,7 +636,10 @@ class NET_EXPORT SpdySession : public base::RefCounted<SpdySession>,
// Calls OnResponseReceived().
// Returns true if successful.
- bool Respond(const SpdyHeaderBlock& headers, SpdyStream* stream);
+ bool Respond(const SpdyHeaderBlock& response_headers,
+ base::Time response_time,
+ base::TimeTicks recv_first_byte_time,
+ SpdyStream* stream);
void RecordPingRTTHistogram(base::TimeDelta duration);
void RecordHistograms();
diff --git a/net/spdy/spdy_session_unittest.cc b/net/spdy/spdy_session_unittest.cc
index df66fe3a..f838832 100644
--- a/net/spdy/spdy_session_unittest.cc
+++ b/net/spdy/spdy_session_unittest.cc
@@ -2694,9 +2694,7 @@ class DropReceivedDataDelegate : public test::StreamDelegateSendImmediate {
virtual ~DropReceivedDataDelegate() {}
// Drop any received data.
- virtual int OnDataReceived(scoped_ptr<SpdyBuffer> buffer) OVERRIDE {
- return OK;
- }
+ virtual void OnDataReceived(scoped_ptr<SpdyBuffer> buffer) OVERRIDE {}
};
// Send data back and forth but use a delegate that drops its received
diff --git a/net/spdy/spdy_stream.cc b/net/spdy/spdy_stream.cc
index 2c2749b..9ee4bbe 100644
--- a/net/spdy/spdy_stream.cc
+++ b/net/spdy/spdy_stream.cc
@@ -4,8 +4,6 @@
#include "net/spdy/spdy_stream.h"
-#include <limits>
-
#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/logging.h"
@@ -44,7 +42,7 @@ base::Value* NetLogSpdyStreamWindowUpdateCallback(
return dict;
}
-bool ContainsUpperAscii(const std::string& str) {
+bool ContainsUppercaseAscii(const std::string& str) {
for (std::string::const_iterator i(str.begin()); i != str.end(); ++i) {
if (*i >= 'A' && *i <= 'Z') {
return true;
@@ -89,7 +87,7 @@ SpdyStream::SpdyStream(SpdyStreamType type,
: type_(type),
weak_ptr_factory_(this),
in_do_loop_(false),
- continue_buffering_data_(true),
+ continue_buffering_data_(type_ == SPDY_PUSH_STREAM),
stream_id_(0),
path_(path),
priority_(priority),
@@ -98,14 +96,13 @@ SpdyStream::SpdyStream(SpdyStreamType type,
send_window_size_(initial_send_window_size),
recv_window_size_(initial_recv_window_size),
unacked_recv_window_bytes_(0),
- response_received_(false),
session_(session),
delegate_(NULL),
send_status_(
(type_ == SPDY_PUSH_STREAM) ?
NO_MORE_DATA_TO_SEND : MORE_DATA_TO_SEND),
request_time_(base::Time::Now()),
- response_(new SpdyHeaderBlock),
+ response_headers_status_(RESPONSE_HEADERS_ARE_INCOMPLETE),
io_state_((type_ == SPDY_PUSH_STREAM) ? STATE_OPEN : STATE_NONE),
response_status_(OK),
net_log_(net_log),
@@ -130,66 +127,85 @@ void SpdyStream::SetDelegate(Delegate* delegate) {
delegate_ = delegate;
if (type_ == SPDY_PUSH_STREAM) {
- CHECK(response_received());
+ DCHECK(continue_buffering_data_);
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&SpdyStream::PushedStreamReplayData, GetWeakPtr()));
- } else {
- continue_buffering_data_ = false;
}
}
+SpdyStream::Delegate* SpdyStream::GetDelegate() {
+ return delegate_;
+}
+
void SpdyStream::PushedStreamReplayData() {
+ DCHECK_EQ(type_, SPDY_PUSH_STREAM);
DCHECK_NE(stream_id_, 0u);
-
- if (!delegate_)
- return;
+ DCHECK(continue_buffering_data_);
continue_buffering_data_ = false;
- // TODO(akalin): This call may delete this object. Figure out what
- // to do in that case.
- int rv = delegate_->OnResponseHeadersReceived(*response_, response_time_, OK);
- if (rv == ERR_INCOMPLETE_SPDY_HEADERS) {
- // We don't have complete headers. Assume we're waiting for another
- // HEADERS frame. Since we don't have headers, we had better not have
- // any pending data frames.
- if (pending_buffers_.size() != 0U) {
+ // The delegate methods called below may delete |this|, so use
+ // |weak_this| to detect that.
+ base::WeakPtr<SpdyStream> weak_this = GetWeakPtr();
+
+ CHECK(delegate_);
+ SpdyResponseHeadersStatus status =
+ delegate_->OnResponseHeadersUpdated(response_headers_);
+ if (status == RESPONSE_HEADERS_ARE_INCOMPLETE) {
+ // Since RESPONSE_HEADERS_ARE_INCOMPLETE was returned, we must not
+ // have been closed. Since we don't have complete headers, assume
+ // we're waiting for another HEADERS frame, and we had better not
+ // have any pending data frames.
+ CHECK(weak_this);
+ if (!pending_buffers_.empty()) {
LogStreamError(ERR_SPDY_PROTOCOL_ERROR,
- "HEADERS incomplete headers, but pending data frames.");
+ "Data received with incomplete headers.");
session_->CloseActiveStream(stream_id_, ERR_SPDY_PROTOCOL_ERROR);
}
return;
}
- std::vector<SpdyBuffer*> buffers;
- pending_buffers_.release(&buffers);
- for (size_t i = 0; i < buffers.size(); ++i) {
- // It is always possible that a callback to the delegate results in
- // the delegate no longer being available.
- if (!delegate_)
- break;
- if (buffers[i]) {
- delegate_->OnDataReceived(scoped_ptr<SpdyBuffer>(buffers[i]));
- } else {
- delegate_->OnDataReceived(scoped_ptr<SpdyBuffer>());
+ // OnResponseHeadersUpdated() may have closed |this|.
+ if (!weak_this)
+ return;
+
+ response_headers_status_ = RESPONSE_HEADERS_ARE_COMPLETE;
+
+ while (!pending_buffers_.empty()) {
+ // Take ownership of the first element of |pending_buffers_|.
+ scoped_ptr<SpdyBuffer> buffer(pending_buffers_.front());
+ pending_buffers_.weak_erase(pending_buffers_.begin());
+
+ bool eof = (buffer == NULL);
+
+ CHECK(delegate_);
+ delegate_->OnDataReceived(buffer.Pass());
+
+ // OnDataReceived() may have closed |this|.
+ if (!weak_this)
+ return;
+
+ if (eof) {
+ DCHECK(pending_buffers_.empty());
session_->CloseActiveStream(stream_id_, OK);
- // Note: |this| may be deleted after calling CloseActiveStream.
- DCHECK_EQ(buffers.size() - 1, i);
+ DCHECK(!weak_this);
+ // |pending_buffers_| is invalid at this point.
+ break;
}
}
}
scoped_ptr<SpdyFrame> SpdyStream::ProduceSynStreamFrame() {
CHECK_EQ(io_state_, STATE_SEND_REQUEST_HEADERS_COMPLETE);
- CHECK(request_);
+ CHECK(request_headers_);
CHECK_GT(stream_id_, 0u);
SpdyControlFlags flags =
(send_status_ == NO_MORE_DATA_TO_SEND) ?
CONTROL_FLAG_FIN : CONTROL_FLAG_NONE;
scoped_ptr<SpdyFrame> frame(session_->CreateSynStream(
- stream_id_, priority_, slot_, flags, *request_));
+ stream_id_, priority_, slot_, flags, *request_headers_));
send_time_ = base::TimeTicks::Now();
return frame.Pass();
}
@@ -373,17 +389,12 @@ void SpdyStream::SetRequestTime(base::Time t) {
request_time_ = t;
}
-int SpdyStream::OnResponseHeadersReceived(const SpdyHeaderBlock& response) {
- int rv = OK;
-
- metrics_.StartStream();
-
- // TODO(akalin): This should be handled as a protocol error.
- DCHECK(response_->empty());
- *response_ = response; // TODO(ukai): avoid copy.
-
- recv_first_byte_time_ = base::TimeTicks::Now();
- response_time_ = base::Time::Now();
+int SpdyStream::OnInitialResponseHeadersReceived(
+ const SpdyHeaderBlock& initial_response_headers,
+ base::Time response_time,
+ base::TimeTicks recv_first_byte_time) {
+ // SpdySession guarantees that this is called at most once.
+ CHECK(response_headers_.empty());
// Check to make sure that we don't receive the response headers
// before we're ready for it.
@@ -411,91 +422,36 @@ int SpdyStream::OnResponseHeadersReceived(const SpdyHeaderBlock& response) {
break;
}
- DCHECK_EQ(io_state_, STATE_OPEN);
-
- // TODO(akalin): Merge the code below with the code in OnHeaders().
-
- // Append all the headers into the response header block.
- for (SpdyHeaderBlock::const_iterator it = response.begin();
- it != response.end(); ++it) {
- // Disallow uppercase headers.
- if (ContainsUpperAscii(it->first)) {
- session_->ResetStream(stream_id_, priority_, RST_STREAM_PROTOCOL_ERROR,
- "Upper case characters in header: " + it->first);
- return ERR_SPDY_PROTOCOL_ERROR;
- }
- }
-
- if ((*response_).find("transfer-encoding") != (*response_).end()) {
- session_->ResetStream(stream_id_, priority_, RST_STREAM_PROTOCOL_ERROR,
- "Received transfer-encoding header");
- return ERR_SPDY_PROTOCOL_ERROR;
- }
+ metrics_.StartStream();
- if (delegate_) {
- // May delete this object.
- rv = delegate_->OnResponseHeadersReceived(*response_, response_time_, rv);
- }
- // If delegate_ is not yet attached, we'll call
- // OnResponseHeadersReceived after the delegate gets attached to the
- // stream.
+ DCHECK_EQ(io_state_, STATE_OPEN);
- return rv;
+ response_time_ = response_time;
+ recv_first_byte_time_ = recv_first_byte_time;
+ return MergeWithResponseHeaders(initial_response_headers);
}
-int SpdyStream::OnHeaders(const SpdyHeaderBlock& headers) {
- DCHECK(!response_->empty());
-
- // Append all the headers into the response header block.
- for (SpdyHeaderBlock::const_iterator it = headers.begin();
- it != headers.end(); ++it) {
- // Disallow duplicate headers. This is just to be conservative.
- if ((*response_).find(it->first) != (*response_).end()) {
- LogStreamError(ERR_SPDY_PROTOCOL_ERROR, "HEADERS duplicate header");
- response_status_ = ERR_SPDY_PROTOCOL_ERROR;
- return ERR_SPDY_PROTOCOL_ERROR;
- }
-
- // Disallow uppercase headers.
- if (ContainsUpperAscii(it->first)) {
- session_->ResetStream(stream_id_, priority_, RST_STREAM_PROTOCOL_ERROR,
- "Upper case characters in header: " + it->first);
- return ERR_SPDY_PROTOCOL_ERROR;
- }
-
- (*response_)[it->first] = it->second;
- }
-
- if ((*response_).find("transfer-encoding") != (*response_).end()) {
- session_->ResetStream(stream_id_, priority_, RST_STREAM_PROTOCOL_ERROR,
- "Received transfer-encoding header");
- return ERR_SPDY_PROTOCOL_ERROR;
- }
-
- int rv = OK;
- if (delegate_) {
- // May delete this object.
- rv = delegate_->OnResponseHeadersReceived(*response_, response_time_, rv);
- // ERR_INCOMPLETE_SPDY_HEADERS means that we are waiting for more
- // headers before the response header block is complete.
- if (rv == ERR_INCOMPLETE_SPDY_HEADERS)
- rv = OK;
+int SpdyStream::OnAdditionalResponseHeadersReceived(
+ const SpdyHeaderBlock& additional_response_headers) {
+ if (type_ == SPDY_REQUEST_RESPONSE_STREAM) {
+ LOG(WARNING) << "Additional headers received for request/response stream";
+ return OK;
+ } else if (type_ == SPDY_PUSH_STREAM &&
+ response_headers_status_ == RESPONSE_HEADERS_ARE_COMPLETE) {
+ LOG(WARNING) << "Additional headers received for push stream";
+ return OK;
}
- return rv;
+ return MergeWithResponseHeaders(additional_response_headers);
}
void SpdyStream::OnDataReceived(scoped_ptr<SpdyBuffer> buffer) {
DCHECK(session_->IsStreamActive(stream_id_));
- // 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
- // received.
- if (!response_received()) {
- LogStreamError(ERR_SYN_REPLY_NOT_RECEIVED, "Didn't receive a response.");
- session_->CloseActiveStream(stream_id_, ERR_SYN_REPLY_NOT_RECEIVED);
- return;
- }
+ // If we're still buffering data for a push stream, we will do the
+ // check for data received with incomplete headers in
+ // PushedStreamReplayData().
if (!delegate_ || continue_buffering_data_) {
+ DCHECK_EQ(type_, SPDY_PUSH_STREAM);
// 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 (buffer) {
@@ -509,12 +465,21 @@ void SpdyStream::OnDataReceived(scoped_ptr<SpdyBuffer> buffer) {
return;
}
+ // If we have response headers but the delegate has indicated that
+ // it's still incomplete, then that's a protocol error.
+ if (response_headers_status_ == RESPONSE_HEADERS_ARE_INCOMPLETE) {
+ LogStreamError(ERR_SPDY_PROTOCOL_ERROR,
+ "Data received with incomplete headers.");
+ session_->CloseActiveStream(stream_id_, ERR_SPDY_PROTOCOL_ERROR);
+ return;
+ }
+
CHECK(!closed());
if (!buffer) {
metrics_.StopStream();
+ // Deletes |this|.
session_->CloseActiveStream(stream_id_, OK);
- // Note: |this| may be deleted after calling CloseActiveStream.
return;
}
@@ -531,12 +496,8 @@ void SpdyStream::OnDataReceived(scoped_ptr<SpdyBuffer> buffer) {
recv_bytes_ += length;
recv_last_byte_time_ = base::TimeTicks::Now();
- if (delegate_->OnDataReceived(buffer.Pass()) != OK) {
- // |delegate_| rejected the data.
- LogStreamError(ERR_SPDY_PROTOCOL_ERROR, "Delegate rejected the data");
- session_->CloseActiveStream(stream_id_, ERR_SPDY_PROTOCOL_ERROR);
- return;
- }
+ // May close |this|.
+ delegate_->OnDataReceived(buffer.Pass());
}
void SpdyStream::OnFrameWriteComplete(SpdyFrameType frame_type,
@@ -592,14 +553,14 @@ void SpdyStream::Close() {
}
}
-int SpdyStream::SendRequestHeaders(scoped_ptr<SpdyHeaderBlock> headers,
+int SpdyStream::SendRequestHeaders(scoped_ptr<SpdyHeaderBlock> request_headers,
SpdySendStatus send_status) {
CHECK_NE(type_, SPDY_PUSH_STREAM);
CHECK_EQ(send_status_, MORE_DATA_TO_SEND);
- CHECK(!request_);
+ CHECK(!request_headers_);
CHECK(!pending_send_data_.get());
CHECK_EQ(io_state_, STATE_NONE);
- request_ = headers.Pass();
+ request_headers_ = request_headers.Pass();
send_status_ = send_status;
io_state_ = STATE_GET_DOMAIN_BOUND_CERT;
return DoLoop(OK);
@@ -645,26 +606,25 @@ base::WeakPtr<SpdyStream> SpdyStream::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
-bool SpdyStream::HasUrl() const {
- if (type_ == SPDY_PUSH_STREAM)
- return response_received();
- return request_ != NULL;
+bool SpdyStream::GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const {
+ if (stream_id_ == 0)
+ return false;
+
+ return session_->GetLoadTimingInfo(stream_id_, load_timing_info);
}
GURL SpdyStream::GetUrl() const {
- DCHECK(HasUrl());
+ if (type_ != SPDY_PUSH_STREAM && !request_headers_)
+ return GURL();
const SpdyHeaderBlock& headers =
- (type_ == SPDY_PUSH_STREAM) ? *response_ : *request_;
+ (type_ == SPDY_PUSH_STREAM) ? response_headers_ : *request_headers_;
return GetUrlFromHeaderBlock(headers, GetProtocolVersion(),
type_ == SPDY_PUSH_STREAM);
}
-bool SpdyStream::GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const {
- if (stream_id_ == 0)
- return false;
-
- return session_->GetLoadTimingInfo(stream_id_, load_timing_info);
+bool SpdyStream::HasUrl() const {
+ return !GetUrl().is_empty();
}
void SpdyStream::OnGetDomainBoundCertComplete(int result) {
@@ -734,7 +694,7 @@ int SpdyStream::DoLoop(int result) {
}
int SpdyStream::DoGetDomainBoundCert() {
- CHECK(request_);
+ CHECK(request_headers_);
DCHECK_NE(type_, SPDY_PUSH_STREAM);
GURL url = GetUrl();
if (!session_->NeedsCredentials() || !url.SchemeIs("https")) {
@@ -774,7 +734,7 @@ int SpdyStream::DoGetDomainBoundCertComplete(int result) {
}
int SpdyStream::DoSendDomainBoundCert() {
- CHECK(request_);
+ CHECK(request_headers_);
DCHECK_NE(type_, SPDY_PUSH_STREAM);
io_state_ = STATE_SEND_DOMAIN_BOUND_CERT_COMPLETE;
@@ -866,14 +826,12 @@ int SpdyStream::DoSendRequestHeadersComplete() {
io_state_ = STATE_OPEN;
- // Do this before calling into the |delegate_| as that call may
- // delete us.
- int result = GetOpenStateResult(type_, send_status_);
-
CHECK(delegate_);
+ // Must not close |this|; if it does, it will trigger the |in_do_loop_|
+ // check in the destructor.
delegate_->OnRequestHeadersSent();
- return result;
+ return GetOpenStateResult(type_, send_status_);
}
int SpdyStream::DoOpen() {
@@ -909,14 +867,12 @@ int SpdyStream::DoOpen() {
pending_send_data_ = NULL;
- // Do this before calling into the |delegate_| as that call may
- // delete us.
- int result = GetOpenStateResult(type_, send_status_);
-
CHECK(delegate_);
+ // Must not close |this|; if it does, it will trigger the
+ // |in_do_loop_| check in the destructor.
delegate_->OnDataSent();
- return result;
+ return GetOpenStateResult(type_, send_status_);
}
void SpdyStream::UpdateHistograms() {
@@ -990,4 +946,58 @@ void SpdyStream::QueueNextDataFrame() {
new SimpleBufferProducer(data_buffer.Pass())));
}
+int SpdyStream::MergeWithResponseHeaders(
+ const SpdyHeaderBlock& new_response_headers) {
+ if (new_response_headers.find("transfer-encoding") !=
+ new_response_headers.end()) {
+ session_->ResetStream(stream_id_, priority_, RST_STREAM_PROTOCOL_ERROR,
+ "Received transfer-encoding header");
+ return ERR_SPDY_PROTOCOL_ERROR;
+ }
+
+ for (SpdyHeaderBlock::const_iterator it = new_response_headers.begin();
+ it != new_response_headers.end(); ++it) {
+ // Disallow uppercase headers.
+ if (ContainsUppercaseAscii(it->first)) {
+ session_->ResetStream(stream_id_, priority_, RST_STREAM_PROTOCOL_ERROR,
+ "Upper case characters in header: " + it->first);
+ return ERR_SPDY_PROTOCOL_ERROR;
+ }
+
+ SpdyHeaderBlock::iterator it2 = response_headers_.lower_bound(it->first);
+ // Disallow duplicate headers. This is just to be conservative.
+ if (it2 != response_headers_.end() && it2->first == it->first) {
+ session_->ResetStream(stream_id_, priority_, RST_STREAM_PROTOCOL_ERROR,
+ "Duplicate header: " + it->first);
+ return ERR_SPDY_PROTOCOL_ERROR;
+ }
+
+ response_headers_.insert(it2, *it);
+ }
+
+ // If delegate_ is not yet attached, we'll call
+ // OnResponseHeadersUpdated() after the delegate gets attached to
+ // the stream.
+ if (delegate_) {
+ // The call to OnResponseHeadersUpdated() below may delete |this|,
+ // so use |weak_this| to detect that.
+ base::WeakPtr<SpdyStream> weak_this = GetWeakPtr();
+
+ SpdyResponseHeadersStatus status =
+ delegate_->OnResponseHeadersUpdated(response_headers_);
+ if (status == RESPONSE_HEADERS_ARE_INCOMPLETE) {
+ // Since RESPONSE_HEADERS_ARE_INCOMPLETE was returned, we must not
+ // have been closed.
+ CHECK(weak_this);
+ // Incomplete headers are OK only for push streams.
+ if (type_ != SPDY_PUSH_STREAM)
+ return ERR_INCOMPLETE_SPDY_HEADERS;
+ } else if (weak_this) {
+ response_headers_status_ = RESPONSE_HEADERS_ARE_COMPLETE;
+ }
+ }
+
+ return OK;
+}
+
} // namespace net
diff --git a/net/spdy/spdy_stream.h b/net/spdy/spdy_stream.h
index b410226..cb5da04 100644
--- a/net/spdy/spdy_stream.h
+++ b/net/spdy/spdy_stream.h
@@ -49,13 +49,20 @@ enum SpdyStreamType {
SPDY_PUSH_STREAM
};
-// Returned by some SpdyStream::Delegate functions to indicate whether
-// there's more data to send.
+// Passed to some SpdyStream functions to indicate whether there's
+// more data to send.
enum SpdySendStatus {
MORE_DATA_TO_SEND,
NO_MORE_DATA_TO_SEND
};
+// Returned by SpdyStream::OnResponseHeadersUpdated() to indicate
+// whether the current response headers are complete or not.
+enum SpdyResponseHeadersStatus {
+ RESPONSE_HEADERS_ARE_INCOMPLETE,
+ RESPONSE_HEADERS_ARE_COMPLETE
+};
+
// The SpdyStream is used by the SpdySession to represent each stream known
// on the SpdySession. This class provides interfaces for SpdySession to use.
// Streams can be created either by the client or by the server. When they
@@ -71,30 +78,74 @@ class NET_EXPORT_PRIVATE SpdyStream {
Delegate() {}
// Called when the request headers have been sent. Never called
- // for push streams.
+ // for push streams. Must not cause the stream to be closed.
virtual void OnRequestHeadersSent() = 0;
- // Called when the SYN_STREAM, SYN_REPLY, or HEADERS frames are received.
- // Normal streams will receive a SYN_REPLY and optional HEADERS frames.
- // Pushed streams will receive a SYN_STREAM and optional HEADERS frames.
- // Because a stream may have a SYN_* frame and multiple HEADERS frames,
- // this callback may be called multiple times.
- // |status| indicates network error. Returns network error code.
- virtual int OnResponseHeadersReceived(const SpdyHeaderBlock& response,
- base::Time response_time,
- int status) = 0;
-
- // Called when data is received. |buffer| may be NULL, which
- // signals EOF. Must return OK if the data was received
- // successfully, or a network error code otherwise.
- virtual int OnDataReceived(scoped_ptr<SpdyBuffer> buffer) = 0;
-
- // Called when data is sent.
+ // WARNING: This function is complicated! Be sure to read the
+ // whole comment below if you're working with code that implements
+ // or calls this function.
+ //
+ // Called when the response headers are updated from the
+ // server. |response_headers| contains the set of all headers
+ // received up to this point; delegates can assume that any
+ // headers previously received remain unchanged.
+ //
+ // This is called at least once before any data is received. If
+ // RESPONSE_HEADERS_ARE_INCOMPLETE is returned, this will be
+ // called again when more headers are received until
+ // RESPONSE_HEADERS_ARE_COMPLETE is returned, and any data
+ // received before then will be treated as a protocol error.
+ //
+ // If RESPONSE_HEADERS_ARE_INCOMPLETE is returned, the delegate
+ // must not have closed the stream. Otherwise, if
+ // RESPONSE_HEADERS_ARE_COMPLETE is returned, the delegate has
+ // processed the headers successfully. However, it still may have
+ // closed the stream, e.g. if the headers indicated an error
+ // condition.
+ //
+ // Some type-specific behavior:
+ //
+ // - For bidirectional streams, this may be called even after
+ // data is received, but it is expected that
+ // RESPONSE_HEADERS_ARE_COMPLETE is always returned. If
+ // RESPONSE_HEADERS_ARE_INCOMPLETE is returned, this is
+ // treated as a protocol error.
+ //
+ // - For request/response streams, this function is called
+ // exactly once before data is received, and it is expected
+ // that RESPONSE_HEADERS_ARE_COMPLETE is returned. If
+ // RESPONSE_HEADERS_ARE_INCOMPLETE is returned, this is
+ // treated as a protocol error.
+ //
+ // - For push streams, it is expected that this function will be
+ // called until RESPONSE_HEADERS_ARE_COMPLETE is returned
+ // before any data is received; any deviation from this is
+ // treated as a protocol error.
+ //
+ // TODO(akalin): Treat headers received after data has been
+ // received as a protocol error for non-bidirectional streams.
+ virtual SpdyResponseHeadersStatus OnResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) = 0;
+
+ // Called when data is received after all required response
+ // headers have been received. |buffer| may be NULL, which signals
+ // EOF. Must return OK if the data was received successfully, or
+ // a network error code otherwise.
+ //
+ // May cause the stream to be closed.
+ virtual void OnDataReceived(scoped_ptr<SpdyBuffer> buffer) = 0;
+
+ // Called when data is sent. Must not cause the stream to be
+ // closed.
virtual void OnDataSent() = 0;
// Called when SpdyStream is closed. No other delegate functions
// will be called after this is called, and the delegate must not
- // access the stream after this is called.
+ // access the stream after this is called. Must not cause the
+ // stream to be be (re-)closed.
+ //
+ // TODO(akalin): Allow this function to re-close the stream and
+ // handle it gracefully.
virtual void OnClose(int status) = 0;
protected:
@@ -115,25 +166,26 @@ class NET_EXPORT_PRIVATE SpdyStream {
~SpdyStream();
- // Set new |delegate|. |delegate| must not be NULL. If it already
- // received SYN_REPLY or data, OnResponseHeadersReceived() or
- // OnDataReceived() will be called.
+ // Set the delegate, which must not be NULL. Must not be called more
+ // than once. For push streams, calling this may cause buffered data
+ // to be sent to the delegate (from a posted task).
void SetDelegate(Delegate* delegate);
- Delegate* GetDelegate() { return delegate_; }
+ Delegate* GetDelegate();
// Detach the delegate from the stream, which must not yet be
// closed, and cancel it.
void DetachDelegate();
+ // The time at which the first bytes of the response were received
+ // from the server, or null if the response hasn't been received
+ // yet.
+ base::Time response_time() const { return response_time_; }
+
SpdyStreamType type() const { return type_; }
SpdyStreamId stream_id() const { return stream_id_; }
void set_stream_id(SpdyStreamId stream_id) { stream_id_ = stream_id; }
- // TODO(akalin): Remove these two functions.
- bool response_received() const { return response_received_; }
- void set_response_received() { response_received_ = true; }
-
const std::string& path() const { return path_; }
RequestPriority priority() const { return priority_; }
@@ -230,14 +282,27 @@ class NET_EXPORT_PRIVATE SpdyStream {
base::Time GetRequestTime() const;
void SetRequestTime(base::Time t);
- // Called by the SpdySession when a response (e.g. a SYN_STREAM or
- // SYN_REPLY) has been received for this stream. This is the entry
- // point for a push stream. Returns a status code.
- int OnResponseHeadersReceived(const SpdyHeaderBlock& response);
-
- // Called by the SpdySession when late-bound headers are received for a
- // stream. Returns a status code.
- int OnHeaders(const SpdyHeaderBlock& headers);
+ // Called at most once by the SpdySession when the initial response
+ // headers have been received for this stream, i.e., a SYN_REPLY (or
+ // SYN_STREAM for push streams) frame has been received. This is the
+ // entry point for a push stream. Returns a status code; if it is an
+ // error, the stream may have already been closed.
+ //
+ // TODO(akalin): Guarantee that the stream is already closed if an
+ // error is returned.
+ int OnInitialResponseHeadersReceived(const SpdyHeaderBlock& response_headers,
+ base::Time response_time,
+ base::TimeTicks recv_first_byte_time);
+
+ // Called by the SpdySession (only after
+ // OnInitialResponseHeadersReceived() has been called) when
+ // late-bound headers are received for a stream. Returns a status
+ // code; if it is an error, ths stream may have already been closed.
+ //
+ // TODO(akalin): Guarantee that the stream is already closed if an
+ // error is returned.
+ int OnAdditionalResponseHeadersReceived(
+ const SpdyHeaderBlock& additional_response_headers);
// Called by the SpdySession when response data has been received
// for this stream. This callback may be called multiple times as
@@ -291,7 +356,7 @@ class NET_EXPORT_PRIVATE SpdyStream {
// bidirectional streams; for request/response streams, it must be
// MORE_DATA_TO_SEND if the request has data to upload, or
// NO_MORE_DATA_TO_SEND if not.
- int SendRequestHeaders(scoped_ptr<SpdyHeaderBlock> headers,
+ int SendRequestHeaders(scoped_ptr<SpdyHeaderBlock> request_headers,
SpdySendStatus send_status);
// Sends a DATA frame. The delegate will be notified via
@@ -328,13 +393,15 @@ class NET_EXPORT_PRIVATE SpdyStream {
bool GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const;
- // Returns true if the URL for this stream is known.
- bool HasUrl() const;
-
- // Get the URL associated with this stream. Only valid when has_url() is
- // true.
+ // Get the URL associated with this stream, or the empty GURL() if
+ // it is unknown.
GURL GetUrl() const;
+ // Returns whether the URL for this stream is known.
+ //
+ // TODO(akalin): Remove this, as it's only used in tests.
+ bool HasUrl() const;
+
int GetProtocolVersion() const;
private:
@@ -373,8 +440,9 @@ class NET_EXPORT_PRIVATE 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.
+ // When a server-pushed stream is first created, this function is
+ // posted on the current MessageLoop to replay the data that the
+ // server has already sent.
void PushedStreamReplayData();
// Produces the SYN_STREAM frame for the stream. The stream must
@@ -391,6 +459,15 @@ class NET_EXPORT_PRIVATE SpdyStream {
// |pending_send_data_| is set.
void QueueNextDataFrame();
+ // Merge the given headers into |response_headers_| and calls
+ // OnResponseHeadersUpdated() on the delegate (if attached).
+ // Returns a status code; if it is an error, the stream may have
+ // already been closed.
+ //
+ // TODO(akalin): Guarantee that the stream is already closed if an
+ // error is returned.
+ int MergeWithResponseHeaders(const SpdyHeaderBlock& new_response_headers);
+
const SpdyStreamType type_;
base::WeakPtrFactory<SpdyStream> weak_ptr_factory_;
@@ -416,7 +493,6 @@ class NET_EXPORT_PRIVATE SpdyStream {
int32 unacked_recv_window_bytes_;
ScopedBandwidthMetrics metrics_;
- bool response_received_;
scoped_refptr<SpdySession> session_;
@@ -430,7 +506,7 @@ class NET_EXPORT_PRIVATE SpdyStream {
//
// TODO(akalin): Hang onto this only until we send it. This
// necessitates stashing the URL separately.
- scoped_ptr<SpdyHeaderBlock> request_;
+ scoped_ptr<SpdyHeaderBlock> request_headers_;
// The data waiting to be sent.
scoped_refptr<DrainableIOBuffer> pending_send_data_;
@@ -439,7 +515,8 @@ class NET_EXPORT_PRIVATE SpdyStream {
// For cached responses, this time could be "far" in the past.
base::Time request_time_;
- scoped_ptr<SpdyHeaderBlock> response_;
+ SpdyHeaderBlock response_headers_;
+ SpdyResponseHeadersStatus response_headers_status_;
base::Time response_time_;
State io_state_;
diff --git a/net/spdy/spdy_stream_test_util.cc b/net/spdy/spdy_stream_test_util.cc
index 8024f8f..782c345 100644
--- a/net/spdy/spdy_stream_test_util.cc
+++ b/net/spdy/spdy_stream_test_util.cc
@@ -22,15 +22,12 @@ ClosingDelegate::~ClosingDelegate() {}
void ClosingDelegate::OnRequestHeadersSent() {}
-int ClosingDelegate::OnResponseHeadersReceived(const SpdyHeaderBlock& response,
- base::Time response_time,
- int status) {
- return OK;
+SpdyResponseHeadersStatus ClosingDelegate::OnResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) {
+ return RESPONSE_HEADERS_ARE_COMPLETE;
}
-int ClosingDelegate::OnDataReceived(scoped_ptr<SpdyBuffer> buffer) {
- return OK;
-}
+void ClosingDelegate::OnDataReceived(scoped_ptr<SpdyBuffer> buffer) {}
void ClosingDelegate::OnDataSent() {}
@@ -56,19 +53,16 @@ void StreamDelegateBase::OnRequestHeadersSent() {
send_headers_completed_ = true;
}
-int StreamDelegateBase::OnResponseHeadersReceived(
- const SpdyHeaderBlock& response,
- base::Time response_time,
- int status) {
- EXPECT_TRUE(send_headers_completed_);
- response_ = response;
- return status;
+SpdyResponseHeadersStatus StreamDelegateBase::OnResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) {
+ EXPECT_EQ(stream_->type() != SPDY_PUSH_STREAM, send_headers_completed_);
+ response_headers_ = response_headers;
+ return RESPONSE_HEADERS_ARE_COMPLETE;
}
-int StreamDelegateBase::OnDataReceived(scoped_ptr<SpdyBuffer> buffer) {
+void StreamDelegateBase::OnDataReceived(scoped_ptr<SpdyBuffer> buffer) {
if (buffer)
received_data_queue_.Enqueue(buffer.Pass());
- return OK;
}
void StreamDelegateBase::OnDataSent() {}
@@ -100,8 +94,8 @@ std::string StreamDelegateBase::TakeReceivedData() {
std::string StreamDelegateBase::GetResponseHeaderValue(
const std::string& name) const {
- SpdyHeaderBlock::const_iterator it = response_.find(name);
- return (it == response_.end()) ? std::string() : it->second;
+ SpdyHeaderBlock::const_iterator it = response_headers_.find(name);
+ return (it == response_headers_.end()) ? std::string() : it->second;
}
StreamDelegateDoNothing::StreamDelegateDoNothing(
@@ -120,13 +114,10 @@ StreamDelegateSendImmediate::StreamDelegateSendImmediate(
StreamDelegateSendImmediate::~StreamDelegateSendImmediate() {
}
-int StreamDelegateSendImmediate::OnResponseHeadersReceived(
- const SpdyHeaderBlock& response,
- base::Time response_time,
- int status) {
- status =
- StreamDelegateBase::OnResponseHeadersReceived(
- response, response_time, status);
+SpdyResponseHeadersStatus StreamDelegateSendImmediate::OnResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) {
+ SpdyResponseHeadersStatus status =
+ StreamDelegateBase::OnResponseHeadersUpdated(response_headers);
if (data_.data()) {
scoped_refptr<StringIOBuffer> buf(new StringIOBuffer(data_.as_string()));
stream()->SendData(buf.get(), buf->size(), MORE_DATA_TO_SEND);
diff --git a/net/spdy/spdy_stream_test_util.h b/net/spdy/spdy_stream_test_util.h
index 3641536..2d0a198 100644
--- a/net/spdy/spdy_stream_test_util.h
+++ b/net/spdy/spdy_stream_test_util.h
@@ -27,10 +27,9 @@ class ClosingDelegate : public SpdyStream::Delegate {
// SpdyStream::Delegate implementation.
virtual void OnRequestHeadersSent() OVERRIDE;
- virtual int OnResponseHeadersReceived(const SpdyHeaderBlock& response,
- base::Time response_time,
- int status) OVERRIDE;
- virtual int OnDataReceived(scoped_ptr<SpdyBuffer> buffer) OVERRIDE;
+ virtual SpdyResponseHeadersStatus OnResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) OVERRIDE;
+ virtual void OnDataReceived(scoped_ptr<SpdyBuffer> buffer) OVERRIDE;
virtual void OnDataSent() OVERRIDE;
virtual void OnClose(int status) OVERRIDE;
@@ -49,10 +48,9 @@ class StreamDelegateBase : public SpdyStream::Delegate {
virtual ~StreamDelegateBase();
virtual void OnRequestHeadersSent() OVERRIDE;
- virtual int OnResponseHeadersReceived(const SpdyHeaderBlock& response,
- base::Time response_time,
- int status) OVERRIDE;
- virtual int OnDataReceived(scoped_ptr<SpdyBuffer> buffer) OVERRIDE;
+ virtual SpdyResponseHeadersStatus OnResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) OVERRIDE;
+ virtual void OnDataReceived(scoped_ptr<SpdyBuffer> buffer) OVERRIDE;
virtual void OnDataSent() OVERRIDE;
virtual void OnClose(int status) OVERRIDE;
@@ -82,7 +80,7 @@ class StreamDelegateBase : public SpdyStream::Delegate {
SpdyStreamId stream_id_;
TestCompletionCallback callback_;
bool send_headers_completed_;
- SpdyHeaderBlock response_;
+ SpdyHeaderBlock response_headers_;
SpdyReadQueue received_data_queue_;
};
@@ -94,7 +92,7 @@ class StreamDelegateDoNothing : public StreamDelegateBase {
virtual ~StreamDelegateDoNothing();
};
-// Test delegate that sends data immediately in OnResponseHeadersReceived().
+// Test delegate that sends data immediately in OnResponseHeadersUpdated().
class StreamDelegateSendImmediate : public StreamDelegateBase {
public:
// |data| can be NULL.
@@ -102,9 +100,8 @@ class StreamDelegateSendImmediate : public StreamDelegateBase {
base::StringPiece data);
virtual ~StreamDelegateSendImmediate();
- virtual int OnResponseHeadersReceived(const SpdyHeaderBlock& response,
- base::Time response_time,
- int status) OVERRIDE;
+ virtual SpdyResponseHeadersStatus OnResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) OVERRIDE;
private:
base::StringPiece data_;
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 @@ TEST_P(SpdyStreamTest, PushedStream) {
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);
+
+ AddReadEOF();
- OrderedSocketData data(reads, arraysize(reads), NULL, 0);
+ OrderedSocketData data(GetReads(), GetNumReads(),
+ GetWrites(), GetNumWrites());
MockConnect connect_data(SYNCHRONOUS, OK);
data.set_connect_data(connect_data);
@@ -228,25 +234,30 @@ TEST_P(SpdyStreamTest, PushedStream) {
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 @@ TEST_P(SpdyStreamTest, SendLargeDataAfterOpenBidirectional) {
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
diff --git a/net/spdy/spdy_websocket_stream.cc b/net/spdy/spdy_websocket_stream.cc
index 3363d51..97b6dc0 100644
--- a/net/spdy/spdy_websocket_stream.cc
+++ b/net/spdy/spdy_websocket_stream.cc
@@ -87,17 +87,16 @@ void SpdyWebSocketStream::OnRequestHeadersSent() {
delegate_->OnSentSpdyHeaders();
}
-int SpdyWebSocketStream::OnResponseHeadersReceived(
- const SpdyHeaderBlock& response,
- base::Time response_time, int status) {
+SpdyResponseHeadersStatus SpdyWebSocketStream::OnResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) {
DCHECK(delegate_);
- return delegate_->OnReceivedSpdyResponseHeader(response, status);
+ delegate_->OnSpdyResponseHeadersUpdated(response_headers);
+ return RESPONSE_HEADERS_ARE_COMPLETE;
}
-int SpdyWebSocketStream::OnDataReceived(scoped_ptr<SpdyBuffer> buffer) {
+void SpdyWebSocketStream::OnDataReceived(scoped_ptr<SpdyBuffer> buffer) {
DCHECK(delegate_);
delegate_->OnReceivedSpdyData(buffer.Pass());
- return OK;
}
void SpdyWebSocketStream::OnDataSent() {
diff --git a/net/spdy/spdy_websocket_stream.h b/net/spdy/spdy_websocket_stream.h
index 903c0bc..6975461 100644
--- a/net/spdy/spdy_websocket_stream.h
+++ b/net/spdy/spdy_websocket_stream.h
@@ -38,13 +38,12 @@ class NET_EXPORT_PRIVATE SpdyWebSocketStream
// has been sent.
virtual void OnSentSpdyHeaders() = 0;
- // Called on corresponding to OnResponseHeadersReceived() or
+ // Called on corresponding to OnResponseHeadersUpdated() or
// SPDY's SYN_STREAM, SYN_REPLY, or HEADERS frames are
// received. This callback may be called multiple times as SPDY's
// delegate does.
- virtual int OnReceivedSpdyResponseHeader(
- const SpdyHeaderBlock& headers,
- int status) = 0;
+ virtual void OnSpdyResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) = 0;
// Called when data is sent.
virtual void OnSentSpdyData(size_t bytes_sent) = 0;
@@ -76,10 +75,9 @@ class NET_EXPORT_PRIVATE SpdyWebSocketStream
// SpdyStream::Delegate
virtual void OnRequestHeadersSent() OVERRIDE;
- virtual int OnResponseHeadersReceived(const SpdyHeaderBlock& response,
- base::Time response_time,
- int status) OVERRIDE;
- virtual int OnDataReceived(scoped_ptr<SpdyBuffer> buffer) OVERRIDE;
+ virtual SpdyResponseHeadersStatus OnResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) OVERRIDE;
+ virtual void OnDataReceived(scoped_ptr<SpdyBuffer> buffer) OVERRIDE;
virtual void OnDataSent() OVERRIDE;
virtual void OnClose(int status) OVERRIDE;
diff --git a/net/spdy/spdy_websocket_stream_spdy2_unittest.cc b/net/spdy/spdy_websocket_stream_spdy2_unittest.cc
index be91009..63789a3 100644
--- a/net/spdy/spdy_websocket_stream_spdy2_unittest.cc
+++ b/net/spdy/spdy_websocket_stream_spdy2_unittest.cc
@@ -95,17 +95,16 @@ class SpdyWebSocketStreamEventRecorder : public SpdyWebSocketStream::Delegate {
if (!on_sent_data_.is_null())
on_sent_data_.Run(&events_.back());
}
- virtual int OnReceivedSpdyResponseHeader(
- const SpdyHeaderBlock& headers, int status) OVERRIDE {
+ virtual void OnSpdyResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) OVERRIDE {
events_.push_back(
SpdyWebSocketStreamEvent(
SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER,
- headers,
- status,
+ response_headers,
+ OK,
std::string()));
if (!on_received_header_.is_null())
on_received_header_.Run(&events_.back());
- return status;
}
virtual void OnSentSpdyData(size_t bytes_sent) OVERRIDE {
events_.push_back(
diff --git a/net/spdy/spdy_websocket_stream_spdy3_unittest.cc b/net/spdy/spdy_websocket_stream_spdy3_unittest.cc
index d6e3479..447da99 100644
--- a/net/spdy/spdy_websocket_stream_spdy3_unittest.cc
+++ b/net/spdy/spdy_websocket_stream_spdy3_unittest.cc
@@ -95,17 +95,16 @@ class SpdyWebSocketStreamEventRecorder : public SpdyWebSocketStream::Delegate {
if (!on_sent_data_.is_null())
on_sent_data_.Run(&events_.back());
}
- virtual int OnReceivedSpdyResponseHeader(
- const SpdyHeaderBlock& headers, int status) OVERRIDE {
+ virtual void OnSpdyResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) OVERRIDE {
events_.push_back(
SpdyWebSocketStreamEvent(
SpdyWebSocketStreamEvent::EVENT_RECEIVED_HEADER,
- headers,
- status,
+ response_headers,
+ OK,
std::string()));
if (!on_received_header_.is_null())
on_received_header_.Run(&events_.back());
- return status;
}
virtual void OnSentSpdyData(size_t bytes_sent) OVERRIDE {
events_.push_back(
diff --git a/net/websockets/websocket_job.cc b/net/websockets/websocket_job.cc
index 2884895..1fe2f49 100644
--- a/net/websockets/websocket_job.cc
+++ b/net/websockets/websocket_job.cc
@@ -306,20 +306,17 @@ void WebSocketJob::OnSentSpdyHeaders() {
handshake_request_.reset();
}
-int WebSocketJob::OnReceivedSpdyResponseHeader(
- const SpdyHeaderBlock& headers, int status) {
+void WebSocketJob::OnSpdyResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) {
DCHECK_NE(INITIALIZED, state_);
if (state_ != CONNECTING)
- return status;
- if (status != OK)
- return status;
+ return;
// TODO(toyoshim): Fallback to non-spdy connection?
- handshake_response_->ParseResponseHeaderBlock(headers,
+ handshake_response_->ParseResponseHeaderBlock(response_headers,
challenge_,
spdy_protocol_version_);
SaveCookiesAndNotifyHeadersComplete();
- return OK;
}
void WebSocketJob::OnSentSpdyData(size_t bytes_sent) {
diff --git a/net/websockets/websocket_job.h b/net/websockets/websocket_job.h
index e17534f..ee72390 100644
--- a/net/websockets/websocket_job.h
+++ b/net/websockets/websocket_job.h
@@ -80,8 +80,8 @@ class NET_EXPORT WebSocketJob
// SpdyWebSocketStream::Delegate methods.
virtual void OnCreatedSpdyStream(int status) OVERRIDE;
virtual void OnSentSpdyHeaders() OVERRIDE;
- virtual int OnReceivedSpdyResponseHeader(
- const SpdyHeaderBlock& headers, int status) OVERRIDE;
+ virtual void OnSpdyResponseHeadersUpdated(
+ const SpdyHeaderBlock& response_headers) OVERRIDE;
virtual void OnSentSpdyData(size_t bytes_sent) OVERRIDE;
virtual void OnReceivedSpdyData(scoped_ptr<SpdyBuffer> buffer) OVERRIDE;
virtual void OnCloseSpdyStream() OVERRIDE;