diff options
Diffstat (limited to 'net/spdy/spdy_http_stream.cc')
-rw-r--r-- | net/spdy/spdy_http_stream.cc | 289 |
1 files changed, 246 insertions, 43 deletions
diff --git a/net/spdy/spdy_http_stream.cc b/net/spdy/spdy_http_stream.cc index a8979d5..bb22b9d 100644 --- a/net/spdy/spdy_http_stream.cc +++ b/net/spdy/spdy_http_stream.cc @@ -8,39 +8,187 @@ #include "base/logging.h" #include "base/message_loop.h" +#include "base/string_util.h" +#include "net/base/load_flags.h" #include "net/http/http_request_info.h" #include "net/http/http_response_info.h" #include "net/spdy/spdy_session.h" +namespace { + +// 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, + net::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 net::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 net::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"; + + net::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 & net::LOAD_BYPASS_CACHE) { + (*headers)["pragma"] = "no-cache"; + (*headers)["cache-control"] = "no-cache"; + } else if (info.load_flags & net::LOAD_VALIDATE_CACHE) { + (*headers)["cache-control"] = "max-age=0"; + } +} + +} // anonymous namespace + namespace net { -SpdyHttpStream::SpdyHttpStream( - SpdySession* session, spdy::SpdyStreamId stream_id, bool pushed) - : SpdyStream(session, stream_id, pushed), +SpdyHttpStream::SpdyHttpStream(const scoped_refptr<SpdyStream>& stream) + : ALLOW_THIS_IN_INITIALIZER_LIST(read_callback_factory_(this)), + stream_(stream), + response_info_(NULL), download_finished_(false), user_callback_(NULL), user_buffer_len_(0), buffered_read_callback_pending_(false), - more_read_data_pending_(false) {} + more_read_data_pending_(false) { + CHECK(stream_.get()); + stream_->SetDelegate(this); +} SpdyHttpStream::~SpdyHttpStream() { - DLOG(INFO) << "Deleting SpdyHttpStream for stream " << stream_id(); + stream_->DetachDelegate(); +} + +void SpdyHttpStream::InitializeRequest( + const HttpRequestInfo& request_info, + base::Time request_time, + UploadDataStream* upload_data) { + request_info_ = request_info; + linked_ptr<spdy::SpdyHeaderBlock> headers(new spdy::SpdyHeaderBlock); + CreateSpdyHeadersFromHttpRequest(request_info_, headers.get()); + stream_->set_spdy_headers(headers); + + stream_->SetRequestTime(request_time); + // This should only get called in the case of a request occuring + // during server push that has already begun but hasn't finished, + // so we set the response's request time to be the actual one + if (response_info_) + response_info_->request_time = request_time; + + CHECK(!request_body_stream_.get()); + if (upload_data) { + if (upload_data->size()) + request_body_stream_.reset(upload_data); + else + delete upload_data; + } +} + +const HttpResponseInfo* SpdyHttpStream::GetResponseInfo() const { + return response_info_; } uint64 SpdyHttpStream::GetUploadProgress() const { - if (!request_body_stream()) + if (!request_body_stream_.get()) return 0; - return request_body_stream()->position(); + return request_body_stream_->position(); } int SpdyHttpStream::ReadResponseHeaders(CompletionCallback* callback) { - DCHECK(is_idle()); + DCHECK(stream_->is_idle()); // Note: The SpdyStream may have already received the response headers, so // this call may complete synchronously. CHECK(callback); - int result = DoReadResponseHeaders(); + if (stream_->response_complete()) + return stream_->response_status(); + + int result = stream_->DoReadResponseHeaders(); if (result == ERR_IO_PENDING) { CHECK(!user_callback_); user_callback_ = callback; @@ -50,11 +198,10 @@ int SpdyHttpStream::ReadResponseHeaders(CompletionCallback* callback) { int SpdyHttpStream::ReadResponseBody( IOBuffer* buf, int buf_len, CompletionCallback* callback) { - DCHECK(is_idle()); CHECK(buf); CHECK(buf_len); CHECK(callback); - CHECK(!cancelled()); + DCHECK(stream_->is_idle()); // If we have data buffered, complete the IO immediately. if (!response_body_.empty()) { @@ -77,8 +224,8 @@ int SpdyHttpStream::ReadResponseBody( bytes_read += bytes_to_copy; } return bytes_read; - } else if (response_complete()) { - return response_status(); + } else if (stream_->response_complete()) { + return stream_->response_status(); } CHECK(!user_callback_); @@ -91,14 +238,38 @@ int SpdyHttpStream::ReadResponseBody( return ERR_IO_PENDING; } -int SpdyHttpStream::SendRequest(UploadDataStream* upload_data, - HttpResponseInfo* response, +int SpdyHttpStream::SendRequest(HttpResponseInfo* response, CompletionCallback* callback) { CHECK(callback); - CHECK(!cancelled()); + CHECK(!stream_->cancelled()); CHECK(response); - int result = DoSendRequest(upload_data, response); + if (stream_->response_complete()) { + if (stream_->response_status() == OK) + return ERR_FAILED; + else + return stream_->response_status(); + } + + // SendRequest can be called in two cases. + // + // a) A client initiated request. In this case, |response_info_| should be + // NULL to start with. + // b) A client request which matches a response that the server has already + // pushed. In this case, the value of |*push_response_info_| is copied + // over to the new response object |*response|. |push_response_info_| is + // deleted, and |response_info_| is reset |response|. + if (push_response_info_.get()) { + *response = *push_response_info_; + push_response_info_.reset(); + response_info_ = NULL; + } + + DCHECK_EQ(static_cast<HttpResponseInfo*>(NULL), response_info_); + response_info_ = response; + + bool has_upload_data = request_body_stream_.get() != NULL; + int result = stream_->DoSendRequest(has_upload_data); if (result == ERR_IO_PENDING) { CHECK(!user_callback_); user_callback_ = callback; @@ -108,22 +279,58 @@ int SpdyHttpStream::SendRequest(UploadDataStream* upload_data, void SpdyHttpStream::Cancel() { user_callback_ = NULL; - DoCancel(); + stream_->Cancel(); +} + +bool SpdyHttpStream::OnSendHeadersComplete(int status) { + return request_body_stream_.get() == NULL; +} + +int SpdyHttpStream::OnSendBody() { + CHECK(request_body_stream_.get()); + int buf_len = static_cast<int>(request_body_stream_->buf_len()); + if (!buf_len) + return OK; + return stream_->WriteStreamData(request_body_stream_->buf(), buf_len); +} + +bool SpdyHttpStream::OnSendBodyComplete(int status) { + CHECK(request_body_stream_.get()); + request_body_stream_->DidConsume(status); + return request_body_stream_->eof(); } -int SpdyHttpStream::OnResponseReceived(const HttpResponseInfo& response) { - int rv = DoOnResponseReceived(response); +int SpdyHttpStream::OnResponseReceived(const spdy::SpdyHeaderBlock& response, + base::Time response_time, + int status) { + if (!response_info_) { + DCHECK(stream_->pushed()); + push_response_info_.reset(new HttpResponseInfo); + response_info_ = push_response_info_.get(); + } + + if (!SpdyHeadersToHttpResponse(response, response_info_)) { + status = ERR_INVALID_RESPONSE; + } else { + stream_->GetSSLInfo(&response_info_->ssl_info, + &response_info_->was_npn_negotiated); + response_info_->request_time = stream_->GetRequestTime(); + response_info_->vary_data.Init(request_info_, *response_info_->headers); + // 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 (user_callback_) - DoCallback(rv); - return rv; + DoCallback(status); + return status; } -bool SpdyHttpStream::OnDataReceived(const char* data, int length) { - bool result = DoOnDataReceived(data, length); +void SpdyHttpStream::OnDataReceived(const char* data, int length) { // Note that data may be received for a SpdyStream prior to the user calling // ReadResponseBody(), therefore user_buffer_ may be NULL. This may often // happen for server initiated streams. - if (result && !response_complete()) { + if (length > 0 && !stream_->response_complete()) { // Save the received data. IOBufferWithSize* io_buffer = new IOBufferWithSize(length); memcpy(io_buffer->data(), data, length); @@ -135,23 +342,15 @@ bool SpdyHttpStream::OnDataReceived(const char* data, int length) { ScheduleBufferedReadCallback(); } } - return result; -} - -void SpdyHttpStream::OnWriteComplete(int status) { - DoOnWriteComplete(status); } void SpdyHttpStream::OnClose(int status) { + bool invoked_callback = false; if (status == net::OK) { - download_finished_ = true; - set_response_complete(true); - // We need to complete any pending buffered read now. - DoBufferedReadCallback(); + invoked_callback = DoBufferedReadCallback(); } - DoOnClose(status); - if (user_callback_) + if (!invoked_callback && user_callback_) DoCallback(status); } @@ -166,15 +365,16 @@ void SpdyHttpStream::ScheduleBufferedReadCallback() { more_read_data_pending_ = false; buffered_read_callback_pending_ = true; const int kBufferTimeMs = 1; - MessageLoop::current()->PostDelayedTask(FROM_HERE, NewRunnableMethod( - this, &SpdyHttpStream::DoBufferedReadCallback), kBufferTimeMs); + MessageLoop::current()->PostDelayedTask(FROM_HERE, read_callback_factory_. + NewRunnableMethod(&SpdyHttpStream::DoBufferedReadCallback), + kBufferTimeMs); } // Checks to see if we should wait for more buffered data before notifying // the caller. Returns true if we should wait, false otherwise. bool SpdyHttpStream::ShouldWaitForMoreBufferedData() const { // If the response is complete, there is no point in waiting. - if (response_complete()) + if (stream_->response_complete()) return false; int bytes_buffered = 0; @@ -187,20 +387,21 @@ bool SpdyHttpStream::ShouldWaitForMoreBufferedData() const { return bytes_buffered < user_buffer_len_; } -void SpdyHttpStream::DoBufferedReadCallback() { +bool SpdyHttpStream::DoBufferedReadCallback() { + read_callback_factory_.RevokeAll(); buffered_read_callback_pending_ = false; // If the transaction is cancelled or errored out, we don't need to complete // the read. - if (response_status() != OK || cancelled()) - return; + if (!stream_ || stream_->response_status() != OK || stream_->cancelled()) + return false; // When more_read_data_pending_ is true, it means that more data has // arrived since we started waiting. Wait a little longer and continue // to buffer. if (more_read_data_pending_ && ShouldWaitForMoreBufferedData()) { ScheduleBufferedReadCallback(); - return; + return false; } int rv = 0; @@ -210,7 +411,9 @@ void SpdyHttpStream::DoBufferedReadCallback() { user_buffer_ = NULL; user_buffer_len_ = 0; DoCallback(rv); + return true; } + return false; } void SpdyHttpStream::DoCallback(int rv) { |