Index: net/spdy/spdy_session.cc |
diff --git a/net/spdy/spdy_session.cc b/net/spdy/spdy_session.cc |
index 8362e567b95df1654368108d8e9f8f19938d1653..7561145917e92b16a88dc23e8ab24fb0d03f10d8 100644 |
--- a/net/spdy/spdy_session.cc |
+++ b/net/spdy/spdy_session.cc |
@@ -235,6 +235,19 @@ base::Value* NetLogSpdyGoAwayCallback(SpdyStreamId last_stream_id, |
return dict; |
} |
+base::Value* NetLogSpdyPushPromiseReceivedCallback( |
+ const SpdyHeaderBlock* headers, |
+ SpdyStreamId stream_id, |
+ SpdyStreamId promised_stream_id, |
+ NetLog::LogLevel log_level) { |
+ base::DictionaryValue* dict = new base::DictionaryValue(); |
+ dict->Set("headers", |
+ SpdyHeaderBlockToListValue(*headers, log_level).release()); |
+ dict->SetInteger("id", stream_id); |
+ dict->SetInteger("promised_stream_id", promised_stream_id); |
+ return dict; |
+} |
+ |
// Helper function to return the total size of an array of objects |
// with .size() member functions. |
template <typename T, size_t N> size_t GetTotalSize(const T (&arr)[N]) { |
@@ -501,7 +514,8 @@ SpdySession::ActiveStreamInfo::ActiveStreamInfo() |
SpdySession::ActiveStreamInfo::ActiveStreamInfo(SpdyStream* stream) |
: stream(stream), |
- waiting_for_syn_reply(stream->type() != SPDY_PUSH_STREAM) {} |
+ waiting_for_syn_reply(stream->type() != SPDY_PUSH_STREAM) { |
+} |
SpdySession::ActiveStreamInfo::~ActiveStreamInfo() {} |
@@ -543,12 +557,12 @@ SpdySession::SpdySession( |
read_state_(READ_STATE_DO_READ), |
write_state_(WRITE_STATE_IDLE), |
error_on_close_(OK), |
- max_concurrent_streams_(initial_max_concurrent_streams == 0 ? |
- kInitialMaxConcurrentStreams : |
- initial_max_concurrent_streams), |
- max_concurrent_streams_limit_(max_concurrent_streams_limit == 0 ? |
- kMaxConcurrentStreamLimit : |
- max_concurrent_streams_limit), |
+ max_concurrent_streams_(initial_max_concurrent_streams == 0 |
+ ? kInitialMaxConcurrentStreams |
+ : initial_max_concurrent_streams), |
+ max_concurrent_streams_limit_(max_concurrent_streams_limit == 0 |
+ ? kMaxConcurrentStreamLimit |
+ : max_concurrent_streams_limit), |
streams_initiated_count_(0), |
streams_pushed_count_(0), |
streams_pushed_and_claimed_count_(0), |
@@ -565,9 +579,9 @@ SpdySession::SpdySession( |
send_connection_header_prefix_(false), |
flow_control_state_(FLOW_CONTROL_NONE), |
stream_initial_send_window_size_(kSpdyStreamInitialWindowSize), |
- stream_initial_recv_window_size_(stream_initial_recv_window_size == 0 ? |
- kDefaultInitialRecvWindowSize : |
- stream_initial_recv_window_size), |
+ stream_initial_recv_window_size_(stream_initial_recv_window_size == 0 |
+ ? kDefaultInitialRecvWindowSize |
+ : stream_initial_recv_window_size), |
session_send_window_size_(0), |
session_recv_window_size_(0), |
session_unacked_recv_window_bytes_(0), |
@@ -580,8 +594,7 @@ SpdySession::SpdySession( |
protocol_(default_protocol), |
connection_at_risk_of_loss_time_( |
base::TimeDelta::FromSeconds(kDefaultConnectionAtRiskOfLossSeconds)), |
- hung_interval_( |
- base::TimeDelta::FromSeconds(kHungIntervalSeconds)), |
+ hung_interval_(base::TimeDelta::FromSeconds(kHungIntervalSeconds)), |
trusted_spdy_proxy_(trusted_spdy_proxy), |
time_func_(time_func), |
weak_factory_(this) { |
@@ -2084,6 +2097,12 @@ void SpdySession::OnSynStream(SpdyStreamId stream_id, |
const SpdyHeaderBlock& headers) { |
CHECK(in_io_loop_); |
+ if (GetProtocolVersion() >= SPDY4) { |
+ DCHECK_EQ(0u, associated_stream_id); |
+ OnHeaders(stream_id, fin, headers); |
+ return; |
+ } |
+ |
base::Time response_time = base::Time::Now(); |
base::TimeTicks recv_first_byte_time = time_func_(); |
@@ -2095,122 +2114,15 @@ void SpdySession::OnSynStream(SpdyStreamId stream_id, |
stream_id, associated_stream_id)); |
} |
- // Server-initiated streams should have even sequence numbers. |
- if ((stream_id & 0x1) != 0) { |
- LOG(WARNING) << "Received invalid OnSyn stream id " << stream_id; |
- return; |
- } |
- |
- if (IsStreamActive(stream_id)) { |
- LOG(WARNING) << "Received OnSyn for active stream " << stream_id; |
- return; |
- } |
- |
- RequestPriority request_priority = |
- ConvertSpdyPriorityToRequestPriority(priority, GetProtocolVersion()); |
- |
- if (availability_state_ == STATE_GOING_AWAY) { |
- // TODO(akalin): This behavior isn't in the SPDY spec, although it |
- // probably should be. |
- EnqueueResetStreamFrame(stream_id, request_priority, |
- RST_STREAM_REFUSED_STREAM, |
- "OnSyn received when going away"); |
- return; |
- } |
- |
- // TODO(jgraettinger): SpdyFramer simulates OnSynStream() from HEADERS |
- // frames, which don't convey associated stream ID. Disable this check |
- // for now, and re-enable when PUSH_PROMISE is implemented properly. |
- if (associated_stream_id == 0 && GetProtocolVersion() < SPDY4) { |
- std::string description = base::StringPrintf( |
- "Received invalid OnSyn associated stream id %d for stream %d", |
- associated_stream_id, stream_id); |
- EnqueueResetStreamFrame(stream_id, request_priority, |
- RST_STREAM_REFUSED_STREAM, description); |
- return; |
- } |
- |
- streams_pushed_count_++; |
- |
- // TODO(mbelshe): DCHECK that this is a GET method? |
- |
- // Verify that the response had a URL for us. |
- GURL gurl = GetUrlFromHeaderBlock(headers, GetProtocolVersion(), true); |
- if (!gurl.is_valid()) { |
- EnqueueResetStreamFrame( |
- stream_id, request_priority, RST_STREAM_PROTOCOL_ERROR, |
- "Pushed stream url was invalid: " + gurl.spec()); |
- return; |
- } |
- |
- // Verify we have a valid stream association. |
- ActiveStreamMap::iterator associated_it = |
- active_streams_.find(associated_stream_id); |
- // TODO(jgraettinger): (See PUSH_PROMISE comment above). |
- if (GetProtocolVersion() < SPDY4 && associated_it == active_streams_.end()) { |
- EnqueueResetStreamFrame( |
- stream_id, request_priority, RST_STREAM_INVALID_STREAM, |
- base::StringPrintf( |
- "Received OnSyn with inactive associated stream %d", |
- associated_stream_id)); |
- return; |
- } |
- |
- // Check that the SYN advertises the same origin as its associated stream. |
- // Bypass this check if and only if this session is with a SPDY proxy that |
- // is trusted explicitly via the --trusted-spdy-proxy switch. |
- if (trusted_spdy_proxy_.Equals(host_port_pair())) { |
- // Disallow pushing of HTTPS content. |
- if (gurl.SchemeIs("https")) { |
- EnqueueResetStreamFrame( |
- stream_id, request_priority, RST_STREAM_REFUSED_STREAM, |
- base::StringPrintf( |
- "Rejected push of Cross Origin HTTPS content %d", |
- associated_stream_id)); |
- } |
- } else if (GetProtocolVersion() < SPDY4) { |
- // TODO(jgraettinger): (See PUSH_PROMISE comment above). |
- GURL associated_url(associated_it->second.stream->GetUrlFromHeaders()); |
- if (associated_url.GetOrigin() != gurl.GetOrigin()) { |
- EnqueueResetStreamFrame( |
- stream_id, request_priority, RST_STREAM_REFUSED_STREAM, |
- base::StringPrintf( |
- "Rejected Cross Origin Push Stream %d", |
- associated_stream_id)); |
- return; |
- } |
- } |
+ // Split headers to simulate push promise and response. |
+ SpdyHeaderBlock request_headers; |
+ SpdyHeaderBlock response_headers; |
+ SplitPushedHeadersToRequestAndResponse( |
+ headers, GetProtocolVersion(), &request_headers, &response_headers); |
- // There should not be an existing pushed stream with the same path. |
- PushedStreamMap::iterator pushed_it = |
- unclaimed_pushed_streams_.lower_bound(gurl); |
- if (pushed_it != unclaimed_pushed_streams_.end() && |
- pushed_it->first == gurl) { |
- EnqueueResetStreamFrame( |
- stream_id, request_priority, RST_STREAM_PROTOCOL_ERROR, |
- "Received duplicate pushed stream with url: " + |
- gurl.spec()); |
+ if (!TryCreatePushStream( |
+ stream_id, associated_stream_id, priority, request_headers)) |
return; |
- } |
- |
- scoped_ptr<SpdyStream> stream( |
- new SpdyStream(SPDY_PUSH_STREAM, GetWeakPtr(), gurl, |
- request_priority, |
- stream_initial_send_window_size_, |
- stream_initial_recv_window_size_, |
- net_log_)); |
- stream->set_stream_id(stream_id); |
- stream->IncrementRawReceivedBytes(last_compressed_frame_len_); |
- last_compressed_frame_len_ = 0; |
- |
- DeleteExpiredPushedStreams(); |
- PushedStreamMap::iterator inserted_pushed_it = |
- unclaimed_pushed_streams_.insert( |
- pushed_it, |
- std::make_pair(gurl, PushedStreamInfo(stream_id, time_func_()))); |
- DCHECK(inserted_pushed_it != pushed_it); |
- |
- InsertActivatedStream(stream.Pass()); |
ActiveStreamMap::iterator active_it = active_streams_.find(stream_id); |
if (active_it == active_streams_.end()) { |
@@ -2218,18 +2130,6 @@ void SpdySession::OnSynStream(SpdyStreamId stream_id, |
return; |
} |
- // Parse the headers. |
- |
- // Split headers to simulate push promise and response. |
- SpdyHeaderBlock request_headers; |
- SpdyHeaderBlock response_headers; |
- SplitPushedHeadersToRequestAndResponse( |
- headers, GetProtocolVersion(), &request_headers, &response_headers); |
- |
- if (active_it->second.stream->OnPushPromiseHeadersReceived(request_headers) != |
- OK) |
- return; |
- |
if (OnInitialResponseHeadersReceived(response_headers, |
response_time, |
recv_first_byte_time, |
@@ -2348,6 +2248,9 @@ void SpdySession::OnHeaders(SpdyStreamId stream_id, |
stream->IncrementRawReceivedBytes(last_compressed_frame_len_); |
last_compressed_frame_len_ = 0; |
+ base::Time response_time = base::Time::Now(); |
+ base::TimeTicks recv_first_byte_time = time_func_(); |
+ |
if (it->second.waiting_for_syn_reply) { |
if (GetProtocolVersion() < SPDY4) { |
const std::string& error = |
@@ -2356,12 +2259,13 @@ void SpdySession::OnHeaders(SpdyStreamId stream_id, |
ResetStreamIterator(it, RST_STREAM_PROTOCOL_ERROR, error); |
return; |
} |
- base::Time response_time = base::Time::Now(); |
- base::TimeTicks recv_first_byte_time = time_func_(); |
it->second.waiting_for_syn_reply = false; |
ignore_result(OnInitialResponseHeadersReceived( |
headers, response_time, recv_first_byte_time, stream)); |
+ } else if (it->second.stream->IsReservedRemote()) { |
+ ignore_result(OnInitialResponseHeadersReceived( |
+ headers, response_time, recv_first_byte_time, stream)); |
} else { |
int rv = stream->OnAdditionalResponseHeadersReceived(headers); |
if (rv < 0) { |
@@ -2521,10 +2425,172 @@ void SpdySession::OnWindowUpdate(SpdyStreamId stream_id, |
} |
} |
+bool SpdySession::TryCreatePushStream(SpdyStreamId stream_id, |
+ SpdyStreamId associated_stream_id, |
+ SpdyPriority priority, |
+ const SpdyHeaderBlock& headers) { |
+ // Server-initiated streams should have even sequence numbers. |
+ if ((stream_id & 0x1) != 0) { |
+ LOG(WARNING) << "Received invalid push stream id " << stream_id; |
+ return false; |
+ } |
+ |
+ if (IsStreamActive(stream_id)) { |
+ LOG(WARNING) << "Received push for active stream " << stream_id; |
+ return false; |
+ } |
+ |
+ RequestPriority request_priority = |
+ ConvertSpdyPriorityToRequestPriority(priority, GetProtocolVersion()); |
+ |
+ if (availability_state_ == STATE_GOING_AWAY) { |
+ // TODO(akalin): This behavior isn't in the SPDY spec, although it |
+ // probably should be. |
+ EnqueueResetStreamFrame(stream_id, |
+ request_priority, |
+ RST_STREAM_REFUSED_STREAM, |
+ "push stream request received when going away"); |
+ return false; |
+ } |
+ |
+ if (associated_stream_id == 0) { |
+ // In SPDY4 0 stream id in PUSH_PROMISE frame leads to framer error and |
+ // session going away. We should never get here. |
+ CHECK_GT(SPDY4, GetProtocolVersion()); |
+ std::string description = base::StringPrintf( |
+ "Received invalid associated stream id %d for pushed stream %d", |
+ associated_stream_id, |
+ stream_id); |
+ EnqueueResetStreamFrame( |
+ stream_id, request_priority, RST_STREAM_REFUSED_STREAM, description); |
+ return false; |
+ } |
+ |
+ streams_pushed_count_++; |
+ |
+ // TODO(mbelshe): DCHECK that this is a GET method? |
+ |
+ // Verify that the response had a URL for us. |
+ GURL gurl = GetUrlFromHeaderBlock(headers, GetProtocolVersion(), true); |
+ if (!gurl.is_valid()) { |
+ EnqueueResetStreamFrame(stream_id, |
+ request_priority, |
+ RST_STREAM_PROTOCOL_ERROR, |
+ "Pushed stream url was invalid: " + gurl.spec()); |
+ return false; |
+ } |
+ |
+ // Verify we have a valid stream association. |
+ ActiveStreamMap::iterator associated_it = |
+ active_streams_.find(associated_stream_id); |
+ if (associated_it == active_streams_.end()) { |
+ EnqueueResetStreamFrame( |
+ stream_id, |
+ request_priority, |
+ RST_STREAM_INVALID_STREAM, |
+ base::StringPrintf("Received push for inactive associated stream %d", |
+ associated_stream_id)); |
+ return false; |
+ } |
+ |
+ // Check that the pushed stream advertises the same origin as its associated |
+ // stream. Bypass this check if and only if this session is with a SPDY proxy |
+ // that is trusted explicitly via the --trusted-spdy-proxy switch. |
+ if (trusted_spdy_proxy_.Equals(host_port_pair())) { |
+ // Disallow pushing of HTTPS content. |
+ if (gurl.SchemeIs("https")) { |
+ EnqueueResetStreamFrame( |
+ stream_id, |
+ request_priority, |
+ RST_STREAM_REFUSED_STREAM, |
+ base::StringPrintf("Rejected push of Cross Origin HTTPS content %d", |
+ associated_stream_id)); |
+ } |
+ } else { |
+ GURL associated_url(associated_it->second.stream->GetUrlFromHeaders()); |
+ if (associated_url.GetOrigin() != gurl.GetOrigin()) { |
+ EnqueueResetStreamFrame( |
+ stream_id, |
+ request_priority, |
+ RST_STREAM_REFUSED_STREAM, |
+ base::StringPrintf("Rejected Cross Origin Push Stream %d", |
+ associated_stream_id)); |
+ return false; |
+ } |
+ } |
+ |
+ // There should not be an existing pushed stream with the same path. |
+ PushedStreamMap::iterator pushed_it = |
+ unclaimed_pushed_streams_.lower_bound(gurl); |
+ if (pushed_it != unclaimed_pushed_streams_.end() && |
+ pushed_it->first == gurl) { |
+ EnqueueResetStreamFrame( |
+ stream_id, |
+ request_priority, |
+ RST_STREAM_PROTOCOL_ERROR, |
+ "Received duplicate pushed stream with url: " + gurl.spec()); |
+ return false; |
+ } |
+ |
+ scoped_ptr<SpdyStream> stream(new SpdyStream(SPDY_PUSH_STREAM, |
+ GetWeakPtr(), |
+ gurl, |
+ request_priority, |
+ stream_initial_send_window_size_, |
+ stream_initial_recv_window_size_, |
+ net_log_)); |
+ stream->set_stream_id(stream_id); |
+ |
+ // In spdy4/http2 PUSH_PROMISE arrives on associated stream. |
+ if (associated_it != active_streams_.end() && GetProtocolVersion() >= SPDY4) { |
+ associated_it->second.stream->IncrementRawReceivedBytes( |
+ last_compressed_frame_len_); |
+ } else { |
+ stream->IncrementRawReceivedBytes(last_compressed_frame_len_); |
+ } |
+ |
+ last_compressed_frame_len_ = 0; |
+ |
+ DeleteExpiredPushedStreams(); |
+ PushedStreamMap::iterator inserted_pushed_it = |
+ unclaimed_pushed_streams_.insert( |
+ pushed_it, |
+ std::make_pair(gurl, PushedStreamInfo(stream_id, time_func_()))); |
+ DCHECK(inserted_pushed_it != pushed_it); |
+ |
+ InsertActivatedStream(stream.Pass()); |
+ |
+ ActiveStreamMap::iterator active_it = active_streams_.find(stream_id); |
+ if (active_it == active_streams_.end()) { |
+ NOTREACHED(); |
+ return false; |
+ } |
+ |
+ active_it->second.stream->OnPushPromiseHeadersReceived(headers); |
+ DCHECK(active_it->second.stream->IsReservedRemote()); |
+ return true; |
+} |
+ |
void SpdySession::OnPushPromise(SpdyStreamId stream_id, |
SpdyStreamId promised_stream_id, |
const SpdyHeaderBlock& headers) { |
- // TODO(akalin): Handle PUSH_PROMISE frames. |
+ CHECK(in_io_loop_); |
+ |
+ if (net_log_.IsLogging()) { |
+ net_log_.AddEvent(NetLog::TYPE_SPDY_SESSION_RECV_PUSH_PROMISE, |
+ base::Bind(&NetLogSpdyPushPromiseReceivedCallback, |
+ &headers, |
+ stream_id, |
+ promised_stream_id)); |
+ } |
+ |
+ // Any priority will do. |
+ // TODO(baranovich): pass parent stream id priority? |
+ if (!TryCreatePushStream(promised_stream_id, stream_id, 0, headers)) |
+ return; |
+ |
+ base::StatsCounter push_requests("spdy.pushed_streams"); |
+ push_requests.Increment(); |
} |
void SpdySession::SendStreamWindowUpdate(SpdyStreamId stream_id, |