diff options
Diffstat (limited to 'net/spdy/spdy_session.cc')
-rw-r--r-- | net/spdy/spdy_session.cc | 253 |
1 files changed, 201 insertions, 52 deletions
diff --git a/net/spdy/spdy_session.cc b/net/spdy/spdy_session.cc index e670fd3..9e474b4 100644 --- a/net/spdy/spdy_session.cc +++ b/net/spdy/spdy_session.cc @@ -19,10 +19,14 @@ #include "net/base/net_log.h" #include "net/base/net_util.h" #include "net/http/http_network_session.h" +#include "net/http/http_request_info.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_response_info.h" #include "net/socket/client_socket.h" #include "net/socket/client_socket_factory.h" #include "net/socket/ssl_client_socket.h" #include "net/spdy/spdy_frame_builder.h" +#include "net/spdy/spdy_http_stream.h" #include "net/spdy/spdy_protocol.h" #include "net/spdy/spdy_settings_storage.h" #include "net/spdy/spdy_stream.h" @@ -62,6 +66,112 @@ const int kReadBufferSize = 2 * 1024; const int kReadBufferSize = 8 * 1024; #endif +// Convert a SpdyHeaderBlock into an HttpResponseInfo. +// |headers| input parameter with the SpdyHeaderBlock. +// |info| output parameter for the HttpResponseInfo. +// Returns true if successfully converted. False if there was a failure +// or if the SpdyHeaderBlock was invalid. +bool SpdyHeadersToHttpResponse(const spdy::SpdyHeaderBlock& headers, + HttpResponseInfo* response) { + std::string version; + std::string status; + + // The "status" and "version" headers are required. + spdy::SpdyHeaderBlock::const_iterator it; + it = headers.find("status"); + if (it == headers.end()) { + LOG(ERROR) << "SpdyHeaderBlock without status header."; + return false; + } + status = it->second; + + // Grab the version. If not provided by the server, + it = headers.find("version"); + if (it == headers.end()) { + LOG(ERROR) << "SpdyHeaderBlock without version header."; + return false; + } + version = it->second; + + response->response_time = base::Time::Now(); + + std::string raw_headers(version); + raw_headers.push_back(' '); + raw_headers.append(status); + raw_headers.push_back('\0'); + for (it = headers.begin(); it != headers.end(); ++it) { + // For each value, if the server sends a NUL-separated + // list of values, we separate that back out into + // individual headers for each value in the list. + // e.g. + // Set-Cookie "foo\0bar" + // becomes + // Set-Cookie: foo\0 + // Set-Cookie: bar\0 + std::string value = it->second; + size_t start = 0; + size_t end = 0; + do { + end = value.find('\0', start); + std::string tval; + if (end != value.npos) + tval = value.substr(start, (end - start)); + else + tval = value.substr(start); + raw_headers.append(it->first); + raw_headers.push_back(':'); + raw_headers.append(tval); + raw_headers.push_back('\0'); + start = end + 1; + } while (end != value.npos); + } + + response->headers = new HttpResponseHeaders(raw_headers); + response->was_fetched_via_spdy = true; + return true; +} + +// Create a SpdyHeaderBlock for a Spdy SYN_STREAM Frame from +// a HttpRequestInfo block. +void CreateSpdyHeadersFromHttpRequest( + const HttpRequestInfo& info, spdy::SpdyHeaderBlock* headers) { + // TODO(willchan): It's not really necessary to convert from + // HttpRequestHeaders to spdy::SpdyHeaderBlock. + + static const char kHttpProtocolVersion[] = "HTTP/1.1"; + + HttpRequestHeaders::Iterator it(info.extra_headers); + + while (it.GetNext()) { + std::string name = StringToLowerASCII(it.name()); + if (headers->find(name) == headers->end()) { + (*headers)[name] = it.value(); + } else { + std::string new_value = (*headers)[name]; + new_value.append(1, '\0'); // +=() doesn't append 0's + new_value += it.value(); + (*headers)[name] = new_value; + } + } + + // TODO(mbelshe): Add Proxy headers here. (See http_network_transaction.cc) + // TODO(mbelshe): Add authentication headers here. + + (*headers)["method"] = info.method; + (*headers)["url"] = info.url.spec(); + (*headers)["version"] = kHttpProtocolVersion; + if (!info.referrer.is_empty()) + (*headers)["referer"] = info.referrer.spec(); + + // Honor load flags that impact proxy caches. + if (info.load_flags & LOAD_BYPASS_CACHE) { + (*headers)["pragma"] = "no-cache"; + (*headers)["cache-control"] = "no-cache"; + } else if (info.load_flags & LOAD_VALIDATE_CACHE) { + (*headers)["cache-control"] = "max-age=0"; + } +} + void AdjustSocketBufferSizes(ClientSocket* socket) { // Adjust socket buffer sizes. // SPDY uses one socket, and we want a really big buffer. @@ -233,20 +343,42 @@ net::Error SpdySession::Connect(const std::string& group_name, return static_cast<net::Error>(rv); } -scoped_refptr<SpdyStream> SpdySession::GetPushStream( - const GURL& url, +scoped_refptr<SpdyHttpStream> SpdySession::GetOrCreateStream( + const HttpRequestInfo& request, + const UploadDataStream* upload_data, const BoundNetLog& stream_net_log) { + const GURL& url = request.url; const std::string& path = url.PathForRequest(); - scoped_refptr<SpdyStream> stream = GetActivePushStream(path); - if (stream) { - DCHECK(streams_pushed_and_claimed_count_ < streams_pushed_count_); - streams_pushed_and_claimed_count_++; - return stream; + scoped_refptr<SpdyHttpStream> stream; + + // Check if we have a push stream for this path. + if (request.method == "GET") { + // Only HTTP will push a stream. + scoped_refptr<SpdyHttpStream> stream = GetPushStream(path); + if (stream) { + DCHECK(streams_pushed_and_claimed_count_ < streams_pushed_count_); + // Update the request time + stream->SetRequestTime(base::Time::Now()); + // Change the request info, updating the response's request time too + stream->SetRequestInfo(request); + const HttpResponseInfo* response = stream->GetResponseInfo(); + if (response && response->headers->HasHeader("vary")) { + // TODO(ahendrickson) -- What is the right thing to do if the server + // pushes data with a vary field? + void* iter = NULL; + std::string value; + response->headers->EnumerateHeader(&iter, "vary", &value); + LOG(ERROR) << "SpdyStream: " + << "Received pushed stream ID " << stream->stream_id() + << "with vary field value '" << value << "'"; + } + streams_pushed_and_claimed_count_++; + return stream; + } } // Check if we have a pending push stream for this url. - // Note that we shouldn't have a pushed stream for non-GET method. PendingStreamMap::iterator it; it = pending_streams_.find(path); if (it != pending_streams_.end()) { @@ -255,73 +387,63 @@ scoped_refptr<SpdyStream> SpdySession::GetPushStream( // Server will assign a stream id when the push stream arrives. Use 0 for // now. net_log_.AddEvent(NetLog::TYPE_SPDY_STREAM_ADOPTED_PUSH_STREAM, NULL); - stream = new SpdyStream(this, 0, true); + stream = new SpdyHttpStream(this, 0, true); + stream->SetRequestInfo(request); stream->set_path(path); stream->set_net_log(stream_net_log); it->second = stream; return stream; } - return NULL; -} - -const scoped_refptr<SpdyStream>& SpdySession::CreateStream( - const GURL& url, - RequestPriority priority, - const BoundNetLog& stream_net_log) { - const std::string& path = url.PathForRequest(); const spdy::SpdyStreamId stream_id = GetNewStreamId(); - scoped_refptr<SpdyStream> stream(new SpdyStream(this, stream_id, false)); - - stream->set_priority(priority); + // If we still don't have a stream, activate one now. + stream = new SpdyHttpStream(this, stream_id, false); + stream->SetRequestInfo(request); + stream->set_priority(request.priority); stream->set_path(path); stream->set_net_log(stream_net_log); ActivateStream(stream); UMA_HISTOGRAM_CUSTOM_COUNTS("Net.SpdyPriorityCount", - static_cast<int>(priority), 0, 10, 11); + static_cast<int>(request.priority), 0, 10, 11); LOG(INFO) << "SpdyStream: Creating stream " << stream_id << " for " << url; + // TODO(mbelshe): Optimize memory allocations - DCHECK(priority >= SPDY_PRIORITY_HIGHEST && - priority <= SPDY_PRIORITY_LOWEST); + DCHECK(request.priority >= SPDY_PRIORITY_HIGHEST && + request.priority <= SPDY_PRIORITY_LOWEST); - DCHECK_EQ(active_streams_[stream_id].get(), stream.get()); - return active_streams_[stream_id]; -} + // Convert from HttpRequestHeaders to Spdy Headers. + linked_ptr<spdy::SpdyHeaderBlock> headers(new spdy::SpdyHeaderBlock); + CreateSpdyHeadersFromHttpRequest(request, headers.get()); -int SpdySession::WriteSynStream( - spdy::SpdyStreamId stream_id, - RequestPriority priority, - spdy::SpdyControlFlags flags, - const linked_ptr<spdy::SpdyHeaderBlock>& headers) { - // Find our stream - if (!IsStreamActive(stream_id)) - return ERR_INVALID_SPDY_STREAM; - const scoped_refptr<SpdyStream>& stream = active_streams_[stream_id]; - CHECK_EQ(stream->stream_id(), stream_id); + spdy::SpdyControlFlags flags = spdy::CONTROL_FLAG_NONE; + if (!request.upload_data || !upload_data->size()) + flags = spdy::CONTROL_FLAG_FIN; + // Create a SYN_STREAM packet and add to the output queue. scoped_ptr<spdy::SpdySynStreamControlFrame> syn_frame( - spdy_framer_.CreateSynStream(stream_id, 0, priority, flags, false, + spdy_framer_.CreateSynStream(stream_id, 0, request.priority, flags, false, headers.get())); - QueueFrame(syn_frame.get(), priority, stream); + QueueFrame(syn_frame.get(), request.priority, stream); static StatsCounter spdy_requests("spdy.requests"); spdy_requests.Increment(); + + LOG(INFO) << "FETCHING: " << request.url.spec(); streams_initiated_count_++; LOG(INFO) << "SPDY SYN_STREAM HEADERS ----------------------------------"; DumpSpdyHeaders(*headers); - const BoundNetLog& log = stream->net_log(); - if (log.HasListener()) { - log.AddEvent( + if (stream_net_log.HasListener()) { + stream_net_log.AddEvent( NetLog::TYPE_SPDY_STREAM_SYN_STREAM, new NetLogSpdySynParameter(headers, flags, stream_id)); } - return ERR_IO_PENDING; + return stream; } int SpdySession::WriteStreamData(spdy::SpdyStreamId stream_id, @@ -510,6 +632,7 @@ void SpdySession::OnWriteComplete(int result) { // We only notify the stream when we've fully written the pending frame. if (!in_flight_write_.buffer()->BytesRemaining()) { + scoped_refptr<SpdyStream> stream = in_flight_write_.stream(); if (stream) { // Report the number of bytes written to the caller, but exclude the // frame size overhead. NOTE: if this frame was compressed the @@ -746,7 +869,7 @@ void SpdySession::DeleteStream(spdy::SpdyStreamId id, int status) { // Remove the stream from pushed_streams_ and active_streams_. ActivePushedStreamList::iterator it; for (it = pushed_streams_.begin(); it != pushed_streams_.end(); ++it) { - scoped_refptr<SpdyStream> curr = *it; + scoped_refptr<SpdyHttpStream> curr = *it; if (id == curr->stream_id()) { pushed_streams_.erase(it); break; @@ -772,13 +895,13 @@ void SpdySession::RemoveFromPool() { } } -scoped_refptr<SpdyStream> SpdySession::GetActivePushStream( +scoped_refptr<SpdyHttpStream> SpdySession::GetPushStream( const std::string& path) { static StatsCounter used_push_streams("spdy.claimed_push_streams"); LOG(INFO) << "Looking for push stream: " << path; - scoped_refptr<SpdyStream> stream; + scoped_refptr<SpdyHttpStream> stream; // We just walk a linear list here. ActivePushedStreamList::iterator it; @@ -796,15 +919,12 @@ scoped_refptr<SpdyStream> SpdySession::GetActivePushStream( return NULL; } -bool SpdySession::GetSSLInfo(SSLInfo* ssl_info, bool* was_npn_negotiated) { +void SpdySession::GetSSLInfo(SSLInfo* ssl_info) { if (is_secure_) { SSLClientSocket* ssl_socket = reinterpret_cast<SSLClientSocket*>(connection_->socket()); ssl_socket->GetSSLInfo(ssl_info); - *was_npn_negotiated = ssl_socket->wasNpnNegotiated(); - return true; } - return false; } void SpdySession::OnError(spdy::SpdyFramer* framer) { @@ -829,9 +949,29 @@ void SpdySession::OnStreamFrameData(spdy::SpdyStreamId stream_id, bool SpdySession::Respond(const spdy::SpdyHeaderBlock& headers, const scoped_refptr<SpdyStream> stream) { + // TODO(mbelshe): For now we convert from our nice hash map back + // to a string of headers; this is because the HttpResponseInfo + // is a bit rigid for its http (non-spdy) design. + HttpResponseInfo response; + // TODO(ahendrickson): This is recorded after the entire SYN_STREAM control + // frame has been received and processed. Move to framer? + response.response_time = base::Time::Now(); int rv = OK; - rv = stream->OnResponseReceived(headers); + if (SpdyHeadersToHttpResponse(headers, &response)) { + GetSSLInfo(&response.ssl_info); + response.request_time = stream->GetRequestTime(); + response.vary_data.Init(*stream->GetRequestInfo(), *response.headers); + if (is_secure_) { + SSLClientSocket* ssl_socket = + reinterpret_cast<SSLClientSocket*>(connection_->socket()); + response.was_npn_negotiated = ssl_socket->wasNpnNegotiated(); + } + rv = stream->OnResponseReceived(response); + } else { + rv = ERR_INVALID_RESPONSE; + } + if (rv < 0) { DCHECK_NE(rv, ERR_IO_PENDING); const spdy::SpdyStreamId stream_id = stream->stream_id(); @@ -878,7 +1018,7 @@ void SpdySession::OnSyn(const spdy::SpdySynStreamControlFrame& frame, } // Only HTTP push a stream. - scoped_refptr<SpdyStream> stream; + scoped_refptr<SpdyHttpStream> stream; // Check if we already have a delegate awaiting this stream. PendingStreamMap::iterator it; @@ -901,7 +1041,7 @@ void SpdySession::OnSyn(const spdy::SpdySynStreamControlFrame& frame, stream_id)); } } else { - stream = new SpdyStream(this, stream_id, true); + stream = new SpdyHttpStream(this, stream_id, true); if (net_log_.HasListener()) { net_log_.AddEvent( @@ -910,6 +1050,15 @@ void SpdySession::OnSyn(const spdy::SpdySynStreamControlFrame& frame, headers, static_cast<spdy::SpdyControlFlags>(frame.flags()), stream_id)); } + + // A new HttpResponseInfo object needs to be generated so the call to + // OnResponseReceived below has something to fill in. + // When a SpdyNetworkTransaction is created for this resource, the + // response_info is copied over and this version is destroyed. + // + // TODO(cbentzel): Minimize allocations and copies of HttpResponseInfo + // object. Should it just be part of SpdyStream? + stream->SetPushResponse(new HttpResponseInfo()); } pushed_streams_.push_back(stream); |