Index: net/spdy/spdy_proxy_client_socket.cc |
=================================================================== |
--- net/spdy/spdy_proxy_client_socket.cc (revision 0) |
+++ net/spdy/spdy_proxy_client_socket.cc (revision 0) |
@@ -0,0 +1,437 @@ |
+// Copyright (c) 2010 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "net/spdy/spdy_proxy_client_socket.h" |
+ |
+#include <algorithm> // min |
+ |
+#include "base/logging.h" |
+#include "base/string_util.h" |
+#include "googleurl/src/gurl.h" |
+#include "net/base/auth.h" |
+#include "net/base/io_buffer.h" |
+#include "net/base/net_util.h" |
+#include "net/http/http_auth_cache.h" |
+#include "net/http/http_auth_handler_factory.h" |
+#include "net/http/http_net_log_params.h" |
+#include "net/http/http_proxy_utils.h" |
+#include "net/spdy/spdy_http_utils.h" |
+ |
+namespace net { |
+ |
+SpdyProxyClientSocket::SpdyProxyClientSocket( |
+ SpdyStream* spdy_stream, |
+ const std::string& user_agent, |
+ const HostPortPair& endpoint, |
+ const GURL& url, |
+ const HostPortPair& proxy_server, |
+ HttpAuthCache* auth_cache, |
+ HttpAuthHandlerFactory* auth_handler_factory) |
+ : ALLOW_THIS_IN_INITIALIZER_LIST( |
+ io_callback_(this, &SpdyProxyClientSocket::OnIOComplete)), |
+ next_state_(STATE_NONE), |
+ spdy_stream_(spdy_stream), |
+ read_callback_(NULL), |
+ write_callback_(NULL), |
+ endpoint_(endpoint), |
+ auth_( |
+ new HttpAuthController(HttpAuth::AUTH_PROXY, |
+ GURL("http://" + proxy_server.ToString()), |
+ auth_cache, |
+ auth_handler_factory)), |
+ user_buffer_(NULL), |
+ write_buffer_len_(0), |
+ write_bytes_outstanding_(0), |
+ eof_has_been_read_(false), |
+ net_log_(spdy_stream->net_log()) { |
+ request_.method = "CONNECT"; |
+ request_.url = url; |
+ if (!user_agent.empty()) |
+ request_.extra_headers.SetHeader(HttpRequestHeaders::kUserAgent, |
+ user_agent); |
+ spdy_stream_->SetDelegate(this); |
+ was_ever_used_ = spdy_stream_->WasEverUsed(); |
+} |
+ |
+SpdyProxyClientSocket::~SpdyProxyClientSocket() { |
+ Disconnect(); |
+} |
+ |
+// Sends a SYN_STREAM frame to the proxy with a CONNECT request |
+// for the specified endpoint. Waits for the server to send back |
+// a SYN_REPLY frame. OK will be returned if the status is 200. |
+// ERR_TUNNEL_CONNECTION_FAILED will be returned for any other status. |
+// In any of these cases, Read() may be called to retrieve the HTTP |
+// response body. Any other return values should be considered fatal. |
+// TODO(rch): handle 407 proxy auth requested correctly, perhaps |
+// by creating a new stream for the subsequent request. |
+// TODO(rch): create a more appropriate error code to disambiguate |
+// the HTTPS Proxy tunnel failure from an HTTP Proxy tunnel failure. |
+int SpdyProxyClientSocket::Connect(CompletionCallback* callback) { |
+ DCHECK(!read_callback_); |
+ if (next_state_ == STATE_DONE) |
+ return OK; |
+ |
+ DCHECK_EQ(STATE_NONE, next_state_); |
+ next_state_ = STATE_GENERATE_AUTH_TOKEN; |
+ |
+ int rv = DoLoop(OK); |
+ if (rv == ERR_IO_PENDING) |
+ read_callback_ = callback; |
+ return rv; |
+} |
+ |
+void SpdyProxyClientSocket::Disconnect() { |
+ next_state_ = STATE_NONE; |
+ if (spdy_stream_) |
+ // This will cause OnClose to be invoked, which takes care of |
+ // cleaning up all the internal state. |
+ spdy_stream_->Cancel(); |
+} |
+ |
+bool SpdyProxyClientSocket::IsConnected() const { |
+ return next_state_ == STATE_DONE && spdy_stream_ != NULL && |
+ !spdy_stream_->closed(); |
+} |
+ |
+bool SpdyProxyClientSocket::IsConnectedAndIdle() const { |
+ return IsConnected() && !spdy_stream_->is_idle(); |
+} |
+ |
+void SpdyProxyClientSocket::SetSubresourceSpeculation() { |
+ // TODO(rch): what should this implementation be? |
+} |
+ |
+void SpdyProxyClientSocket::SetOmniboxSpeculation() { |
+ // TODO(rch): what should this implementation be? |
+} |
+ |
+bool SpdyProxyClientSocket::WasEverUsed() const { |
+ return was_ever_used_ || (spdy_stream_ && spdy_stream_->WasEverUsed()); |
+} |
+ |
+int SpdyProxyClientSocket::Read(IOBuffer* buf, int buf_len, |
+ CompletionCallback* callback) { |
+ DCHECK(!read_callback_); |
+ DCHECK(!user_buffer_); |
+ |
+ if (!spdy_stream_) { |
+ if (eof_has_been_read_) |
+ return ERR_CONNECTION_CLOSED; |
+ eof_has_been_read_ = true; |
+ return 0; |
+ } |
+ |
+ DCHECK(next_state_ == STATE_DONE); |
+ DCHECK(buf); |
+ user_buffer_ = new DrainableIOBuffer(buf, buf_len); |
+ int result = PopulateUserReadBuffer(); |
+ if (result == 0) { |
+ DCHECK(callback); |
+ read_callback_ = callback; |
+ return ERR_IO_PENDING; |
+ } |
+ user_buffer_ = NULL; |
+ return result; |
+} |
+ |
+int SpdyProxyClientSocket::PopulateUserReadBuffer() { |
+ if (!user_buffer_) |
+ return ERR_IO_PENDING; |
+ |
+ while (!read_buffer_.empty() && user_buffer_->BytesRemaining() > 0) { |
+ scoped_refptr<DrainableIOBuffer> data = read_buffer_.front(); |
+ const int bytes_to_copy = std::min(user_buffer_->BytesRemaining(), |
+ data->size()); |
+ memcpy(user_buffer_->data(), data->data(), bytes_to_copy); |
+ user_buffer_->DidConsume(bytes_to_copy); |
+ if (bytes_to_copy == data->size()) { |
+ // Consumed all data from this buffer |
+ read_buffer_.pop_front(); |
+ } else { |
+ data->DidConsume(bytes_to_copy); |
+ } |
+ } |
+ |
+ return user_buffer_->BytesConsumed(); |
+} |
+ |
+int SpdyProxyClientSocket::Write(IOBuffer* buf, int buf_len, |
+ CompletionCallback* callback) { |
+ DCHECK(!write_callback_); |
+ if (!spdy_stream_) |
+ return ERR_CONNECTION_CLOSED; |
+ |
+ write_bytes_outstanding_= buf_len; |
+ if (buf_len <= kMaxSpdyFrameChunkSize) { |
+ int rv = spdy_stream_->WriteStreamData(buf, buf_len, spdy::DATA_FLAG_NONE); |
+ if (rv == ERR_IO_PENDING) { |
+ write_callback_ = callback; |
+ write_buffer_len_ = buf_len; |
+ } |
+ return rv; |
+ } |
+ |
+ // Since a SPDY Data frame can only include kMaxSpdyFrameChunkSize bytes |
+ // we need to send multiple data frames |
+ for (int i = 0; i < buf_len; i += kMaxSpdyFrameChunkSize) { |
+ int len = std::min(kMaxSpdyFrameChunkSize, buf_len - i); |
+ scoped_refptr<DrainableIOBuffer> iobuf(new DrainableIOBuffer(buf, i + len)); |
+ iobuf->SetOffset(i); |
+ int rv = spdy_stream_->WriteStreamData(iobuf, len, spdy::DATA_FLAG_NONE); |
+ if (rv > 0) { |
+ write_bytes_outstanding_ -= rv; |
+ } else if (rv != ERR_IO_PENDING) { |
+ return rv; |
+ } |
+ } |
+ if (write_bytes_outstanding_ > 0) { |
+ write_callback_ = callback; |
+ write_buffer_len_ = buf_len; |
+ return ERR_IO_PENDING; |
+ } else { |
+ return buf_len; |
+ } |
+} |
+ |
+bool SpdyProxyClientSocket::SetReceiveBufferSize(int32 size) { |
+ // Since this ClientSocket sits on top of a shared SpdySession, it |
+ // is not safe for callers to set change this underlying socket. |
+ return false; |
+} |
+ |
+bool SpdyProxyClientSocket::SetSendBufferSize(int32 size) { |
+ // Since this ClientSocket sits on top of a shared SpdySession, it |
+ // is not safe for callers to set change this underlying socket. |
+ return false; |
+} |
+ |
+int SpdyProxyClientSocket::GetPeerAddress(AddressList* address) const { |
+ if (!IsConnected()) |
+ return ERR_UNEXPECTED; |
+ return spdy_stream_->GetPeerAddress(address); |
+} |
+ |
+void SpdyProxyClientSocket::OnIOComplete(int result) { |
+ DCHECK_NE(STATE_NONE, next_state_); |
+ int rv = DoLoop(result); |
+ if (rv != ERR_IO_PENDING) { |
+ CompletionCallback* c = read_callback_; |
+ read_callback_ = NULL; |
+ c->Run(rv); |
+ } |
+} |
+ |
+int SpdyProxyClientSocket::DoLoop(int last_io_result) { |
+ DCHECK_NE(next_state_, STATE_NONE); |
+ int rv = last_io_result; |
+ do { |
+ State state = next_state_; |
+ next_state_ = STATE_NONE; |
+ switch (state) { |
+ case STATE_GENERATE_AUTH_TOKEN: |
+ DCHECK_EQ(OK, rv); |
+ rv = DoGenerateAuthToken(); |
+ break; |
+ case STATE_GENERATE_AUTH_TOKEN_COMPLETE: |
+ rv = DoGenerateAuthTokenComplete(rv); |
+ break; |
+ case STATE_SEND_REQUEST: |
+ DCHECK_EQ(OK, rv); |
+ net_log_.BeginEvent(NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST, |
+ NULL); |
+ rv = DoSendRequest(); |
+ break; |
+ case STATE_SEND_REQUEST_COMPLETE: |
+ rv = DoSendRequestComplete(rv); |
+ net_log_.EndEvent(NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST, |
+ NULL); |
+ break; |
+ case STATE_READ_REPLY_COMPLETE: |
+ rv = DoReadReplyComplete(rv); |
+ net_log_.EndEvent(NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_READ_HEADERS, |
+ NULL); |
+ break; |
+ default: |
+ NOTREACHED() << "bad state"; |
+ rv = ERR_UNEXPECTED; |
+ break; |
+ } |
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE && |
+ next_state_ != STATE_DONE); |
+ return rv; |
+} |
+ |
+int SpdyProxyClientSocket::DoGenerateAuthToken() { |
+ next_state_ = STATE_GENERATE_AUTH_TOKEN_COMPLETE; |
+ return auth_->MaybeGenerateAuthToken(&request_, &io_callback_, net_log_); |
+} |
+ |
+int SpdyProxyClientSocket::DoGenerateAuthTokenComplete(int result) { |
+ DCHECK_NE(ERR_IO_PENDING, result); |
+ if (result == OK) |
+ next_state_ = STATE_SEND_REQUEST; |
+ return result; |
+} |
+ |
+int SpdyProxyClientSocket::DoSendRequest() { |
+ next_state_ = STATE_SEND_REQUEST_COMPLETE; |
+ |
+ // Add Proxy-Authentication header if necessary. |
+ HttpRequestHeaders authorization_headers; |
+ if (auth_->HaveAuth()) { |
+ auth_->AddAuthorizationHeader(&authorization_headers); |
+ } |
+ |
+ std::string request_line; |
+ HttpRequestHeaders request_headers; |
+ BuildTunnelRequest(request_, authorization_headers, endpoint_, &request_line, |
+ &request_headers); |
+ if (net_log_.IsLoggingAll()) { |
+ net_log_.AddEvent( |
+ NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS, |
+ new NetLogHttpRequestParameter( |
+ request_line, request_headers)); |
+ } |
+ |
+ request_.extra_headers.MergeFrom(request_headers); |
+ linked_ptr<spdy::SpdyHeaderBlock> headers(new spdy::SpdyHeaderBlock()); |
+ CreateSpdyHeadersFromHttpRequest(request_, headers.get(), true); |
+ // Reset the URL to be the endpoint of the connection |
+ (*headers)["url"] = endpoint_.ToString(); |
+ headers->erase("scheme"); |
+ spdy_stream_->set_spdy_headers(headers); |
+ |
+ return spdy_stream_->SendRequest(true); |
+} |
+ |
+int SpdyProxyClientSocket::DoSendRequestComplete(int result) { |
+ if (result < 0) |
+ return result; |
+ |
+ // Wait for SYN_REPLY frame from the server |
+ next_state_ = STATE_READ_REPLY_COMPLETE; |
+ return ERR_IO_PENDING; |
+} |
+ |
+int SpdyProxyClientSocket::DoReadReplyComplete(int result) { |
+ // We enter this method directly from DoSendRequestComplete, since |
+ // we are notified by a callback when the SYN_REPLY frame arrives |
+ |
+ if (result < 0) |
+ return result; |
+ |
+ next_state_ = STATE_DONE; |
+ // Require the "HTTP/1.x" status line for SSL CONNECT. |
+ if (response_.headers->GetParsedHttpVersion() < HttpVersion(1, 0)) |
+ return ERR_TUNNEL_CONNECTION_FAILED; |
+ |
+ if (net_log_.IsLoggingAll()) { |
+ net_log_.AddEvent( |
+ NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS, |
+ new NetLogHttpResponseParameter(response_.headers)); |
+ } |
+ |
+ if (response_.headers->response_code() == 200) |
+ return OK; |
+ else |
+ return ERR_TUNNEL_CONNECTION_FAILED; |
+} |
+ |
+// SpdyStream::Delegate methods: |
+// Called when SYN frame has been sent. |
+// Returns true if no more data to be sent after SYN frame. |
+bool SpdyProxyClientSocket::OnSendHeadersComplete(int status) { |
+ DCHECK_EQ(next_state_, STATE_SEND_REQUEST_COMPLETE); |
+ |
+ OnIOComplete(status); |
+ |
+ // We return true here so that we send |spdy_stream_| into |
+ // STATE_OPEN (ala WebSockets). |
+ return true; |
+} |
+ |
+int SpdyProxyClientSocket::OnSendBody() { |
+ // Because we use |spdy_stream_| via STATE_OPEN (ala WebSockets) |
+ // OnSendBody() should never be called. |
+ NOTREACHED(); |
+ return ERR_UNEXPECTED; |
+} |
+ |
+bool SpdyProxyClientSocket::OnSendBodyComplete(int status) { |
+ // Because we use |spdy_stream_| via STATE_OPEN (ala WebSockets) |
+ // OnSendBodyComplete() should never be called. |
+ NOTREACHED(); |
+ return false; |
+} |
+ |
+int SpdyProxyClientSocket::OnResponseReceived( |
+ const spdy::SpdyHeaderBlock& response, |
+ base::Time response_time, |
+ int status) { |
+ // Save the response |
+ SpdyHeadersToHttpResponse(response, &response_); |
+ |
+ DCHECK_EQ(next_state_, STATE_READ_REPLY_COMPLETE); |
+ |
+ OnIOComplete(status); |
+ |
+ return OK; |
+} |
+ |
+// Called when data is received. |
+void SpdyProxyClientSocket::OnDataReceived(const char* data, int length) { |
+ if (length > 0) { |
+ // Save the received data. |
+ scoped_refptr<IOBuffer> io_buffer = new IOBuffer(length); |
+ memcpy(io_buffer->data(), data, length); |
+ read_buffer_.push_back(new DrainableIOBuffer(io_buffer, length)); |
+ } |
+ |
+ if (read_callback_) { |
+ int rv = PopulateUserReadBuffer(); |
+ CompletionCallback* c = read_callback_; |
+ read_callback_ = NULL; |
+ user_buffer_ = NULL; |
+ c->Run(rv); |
+ } |
+} |
+ |
+void SpdyProxyClientSocket::OnDataSent(int length) { |
+ DCHECK(write_callback_); |
+ |
+ write_bytes_outstanding_ -= length; |
+ |
+ DCHECK_GE(write_bytes_outstanding_, 0); |
+ |
+ if (write_bytes_outstanding_ == 0) { |
+ int rv = write_buffer_len_; |
+ write_buffer_len_ = 0; |
+ write_bytes_outstanding_ = 0; |
+ CompletionCallback* c = write_callback_; |
+ write_callback_ = NULL; |
+ c->Run(rv); |
+ } |
+} |
+ |
+void SpdyProxyClientSocket::OnClose(int status) { |
+ DCHECK(spdy_stream_); |
+ // If we're in the middle of connecting, we need to make sure |
+ // we invoke the connect callback. |
+ CompletionCallback* connect_callback = NULL; |
+ if (next_state_ != STATE_NONE && next_state_ != STATE_DONE) { |
+ DCHECK(read_callback_); |
+ connect_callback = read_callback_; |
+ } |
+ was_ever_used_ = spdy_stream_->WasEverUsed(); |
+ spdy_stream_ = NULL; |
+ read_callback_ = NULL; |
+ write_callback_ = NULL; |
+ user_buffer_ = NULL; |
+ read_buffer_.empty(); |
+ if (connect_callback) |
+ connect_callback->Run(status); |
+} |
+ |
+} // namespace net |
Property changes on: net/spdy/spdy_proxy_client_socket.cc |
___________________________________________________________________ |
Added: svn:eol-style |
+ LF |