| Index: net/spdy/spdy_stream.cc
|
| diff --git a/net/spdy/spdy_stream.cc b/net/spdy/spdy_stream.cc
|
| index 2c2749ba13a886a24ef765a5ed67171228648232..9ee4bbef882dfe75b89c932f6b1fc4b9d6b924cf 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
|
|
|