diff options
author | rch@chromium.org <rch@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-09-28 03:28:27 +0000 |
---|---|---|
committer | rch@chromium.org <rch@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-09-28 03:28:27 +0000 |
commit | 3ee0489049bb17df3ec74213dbcc8e74d927d7c0 (patch) | |
tree | 475fbaafb8ecd58b4eaf5272b002752f62a7d4a3 | |
parent | e2150328d75a1ae7a7684a7e5fda90842886812d (diff) | |
download | chromium_src-3ee0489049bb17df3ec74213dbcc8e74d927d7c0.zip chromium_src-3ee0489049bb17df3ec74213dbcc8e74d927d7c0.tar.gz chromium_src-3ee0489049bb17df3ec74213dbcc8e74d927d7c0.tar.bz2 |
Add a new class SpdyProxyClientSocket which implements ClientSocket
by sending a CONNECT request via a SPDY SYN_STREAM frame to a SPDY proxy,
and then reading/writing data to/from SPDY Data frames.
BUG=29625
TEST=none
Review URL: http://codereview.chromium.org/3432009
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@60747 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | net/http/http_proxy_client_socket.cc | 33 | ||||
-rw-r--r-- | net/http/http_proxy_utils.cc | 39 | ||||
-rw-r--r-- | net/http/http_proxy_utils.h | 28 | ||||
-rw-r--r-- | net/net.gyp | 5 | ||||
-rw-r--r-- | net/spdy/spdy_framer.h | 10 | ||||
-rw-r--r-- | net/spdy/spdy_http_utils.cc | 2 | ||||
-rw-r--r-- | net/spdy/spdy_proxy_client_socket.cc | 437 | ||||
-rw-r--r-- | net/spdy/spdy_proxy_client_socket.h | 183 | ||||
-rw-r--r-- | net/spdy/spdy_proxy_client_socket_unittest.cc | 894 | ||||
-rw-r--r-- | net/spdy/spdy_session.h | 10 | ||||
-rw-r--r-- | net/spdy/spdy_stream.cc | 7 | ||||
-rw-r--r-- | net/spdy/spdy_stream.h | 7 |
12 files changed, 1620 insertions, 35 deletions
diff --git a/net/http/http_proxy_client_socket.cc b/net/http/http_proxy_client_socket.cc index ccc5d5d..ddefe8e 100644 --- a/net/http/http_proxy_client_socket.cc +++ b/net/http/http_proxy_client_socket.cc @@ -14,42 +14,13 @@ #include "net/base/net_util.h" #include "net/http/http_net_log_params.h" #include "net/http/http_network_session.h" +#include "net/http/http_proxy_utils.h" #include "net/http/http_request_info.h" #include "net/http/http_stream_parser.h" #include "net/socket/client_socket_handle.h" namespace net { -namespace { - -// The HTTP CONNECT method for establishing a tunnel connection is documented -// in draft-luotonen-web-proxy-tunneling-01.txt and RFC 2817, Sections 5.2 and -// 5.3. -void BuildTunnelRequest(const HttpRequestInfo* request_info, - const HttpRequestHeaders& authorization_headers, - const HostPortPair& endpoint, - std::string* request_line, - HttpRequestHeaders* request_headers) { - // RFC 2616 Section 9 says the Host request-header field MUST accompany all - // HTTP/1.1 requests. Add "Proxy-Connection: keep-alive" for compat with - // HTTP/1.0 proxies such as Squid (required for NTLM authentication). - *request_line = base::StringPrintf( - "CONNECT %s HTTP/1.1\r\n", endpoint.ToString().c_str()); - request_headers->SetHeader(HttpRequestHeaders::kHost, - GetHostAndOptionalPort(request_info->url)); - request_headers->SetHeader(HttpRequestHeaders::kProxyConnection, - "keep-alive"); - - std::string user_agent; - if (request_info->extra_headers.GetHeader(HttpRequestHeaders::kUserAgent, - &user_agent)) - request_headers->SetHeader(HttpRequestHeaders::kUserAgent, user_agent); - - request_headers->MergeFrom(authorization_headers); -} - -} // namespace - HttpProxyClientSocket::HttpProxyClientSocket( ClientSocketHandle* transport_socket, const GURL& request_url, @@ -358,7 +329,7 @@ int HttpProxyClientSocket::DoSendRequest() { auth_->AddAuthorizationHeader(&authorization_headers); std::string request_line; HttpRequestHeaders request_headers; - BuildTunnelRequest(&request_, authorization_headers, endpoint_, + BuildTunnelRequest(request_, authorization_headers, endpoint_, &request_line, &request_headers); if (net_log_.IsLoggingAll()) { net_log_.AddEvent( diff --git a/net/http/http_proxy_utils.cc b/net/http/http_proxy_utils.cc new file mode 100644 index 0000000..3e4fb8b --- /dev/null +++ b/net/http/http_proxy_utils.cc @@ -0,0 +1,39 @@ +// 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/http/http_proxy_utils.h" + +#include "base/string_util.h" +#include "googleurl/src/gurl.h" +#include "net/base/host_port_pair.h" +#include "net/base/net_util.h" +#include "net/http/http_request_info.h" + +namespace net { + +void BuildTunnelRequest( + const HttpRequestInfo& request_info, + const HttpRequestHeaders& auth_headers, + const HostPortPair& endpoint, + std::string* request_line, + HttpRequestHeaders* request_headers) { + // RFC 2616 Section 9 says the Host request-header field MUST accompany all + // HTTP/1.1 requests. Add "Proxy-Connection: keep-alive" for compat with + // HTTP/1.0 proxies such as Squid (required for NTLM authentication). + *request_line = StringPrintf( + "CONNECT %s HTTP/1.1\r\n", endpoint.ToString().c_str()); + request_headers->SetHeader(HttpRequestHeaders::kHost, + GetHostAndOptionalPort(request_info.url)); + request_headers->SetHeader(HttpRequestHeaders::kProxyConnection, + "keep-alive"); + + std::string user_agent; + if (request_info.extra_headers.GetHeader(HttpRequestHeaders::kUserAgent, + &user_agent)) + request_headers->SetHeader(HttpRequestHeaders::kUserAgent, user_agent); + + request_headers->MergeFrom(auth_headers); +} + +} // namespace net diff --git a/net/http/http_proxy_utils.h b/net/http/http_proxy_utils.h new file mode 100644 index 0000000..9d18fc8 --- /dev/null +++ b/net/http/http_proxy_utils.h @@ -0,0 +1,28 @@ +// 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. + +#ifndef NET_HTTP_HTTP_PROXY_UTILS_H_ +#define NET_HTTP_HTTP_PROXY_UTILS_H_ +#pragma once + +#include <string> + +namespace net { + +struct HttpRequestInfo; +class HttpRequestHeaders; +class HostPortPair; + +// The HTTP CONNECT method for establishing a tunnel connection is documented +// in draft-luotonen-web-proxy-tunneling-01.txt and RFC 2817, Sections 5.2 and +// 5.3. +void BuildTunnelRequest(const HttpRequestInfo& request_info, + const HttpRequestHeaders& auth_headers, + const HostPortPair& endpoint, + std::string* request_line, + HttpRequestHeaders* request_headers); + +} // namespace net + +#endif // NET_HTTP_HTTP_PROXY_UTILS_H_ diff --git a/net/net.gyp b/net/net.gyp index 06105fa..b93ce86 100644 --- a/net/net.gyp +++ b/net/net.gyp @@ -453,6 +453,8 @@ 'http/http_proxy_client_socket.h', 'http/http_proxy_client_socket_pool.cc', 'http/http_proxy_client_socket_pool.h', + 'http/http_proxy_utils.cc', + 'http/http_proxy_utils.h', 'http/http_util.cc', 'http/http_util_icu.cc', 'http/http_util.h', @@ -570,6 +572,8 @@ 'spdy/spdy_io_buffer.cc', 'spdy/spdy_io_buffer.h', 'spdy/spdy_protocol.h', + 'spdy/spdy_proxy_client_socket.cc', + 'spdy/spdy_proxy_client_socket.h', 'spdy/spdy_session.cc', 'spdy/spdy_session.h', 'spdy/spdy_session_pool.cc', @@ -845,6 +849,7 @@ 'spdy/spdy_http_stream_unittest.cc', 'spdy/spdy_network_transaction_unittest.cc', 'spdy/spdy_protocol_test.cc', + 'spdy/spdy_proxy_client_socket_unittest.cc', 'spdy/spdy_session_unittest.cc', 'spdy/spdy_stream_unittest.cc', 'spdy/spdy_test_util.cc', diff --git a/net/spdy/spdy_framer.h b/net/spdy/spdy_framer.h index e6d0e15..00ddac0 100644 --- a/net/spdy/spdy_framer.h +++ b/net/spdy/spdy_framer.h @@ -26,10 +26,11 @@ typedef struct z_stream_s z_stream; // Forward declaration for zlib. namespace net { class HttpNetworkLayer; class HttpNetworkTransactionTest; +class SpdyHttpStreamTest; class SpdyNetworkTransactionTest; +class SpdyProxyClientSocketTest; class SpdySessionTest; class SpdyStreamTest; -class SpdyHttpStreamTest; } namespace spdy { @@ -248,11 +249,12 @@ class SpdyFramer { protected: FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, DataCompression); FRIEND_TEST_ALL_PREFIXES(SpdyFramerTest, UnclosedStreamDataCompressors); - friend class net::SpdyNetworkTransactionTest; - friend class net::HttpNetworkTransactionTest; friend class net::HttpNetworkLayer; // This is temporary for the server. - friend class net::SpdySessionTest; + friend class net::HttpNetworkTransactionTest; friend class net::SpdyHttpStreamTest; + friend class net::SpdyNetworkTransactionTest; + friend class net::SpdyProxyClientSocketTest; + friend class net::SpdySessionTest; friend class net::SpdyStreamTest; friend class test::TestSpdyVisitor; friend void test::FramerSetEnableCompressionHelper(SpdyFramer* framer, diff --git a/net/spdy/spdy_http_utils.cc b/net/spdy/spdy_http_utils.cc index d2fdd8fa..b2ef551 100644 --- a/net/spdy/spdy_http_utils.cc +++ b/net/spdy/spdy_http_utils.cc @@ -4,6 +4,8 @@ #include "net/spdy/spdy_http_utils.h" +#include <string> + #include "base/string_number_conversions.h" #include "base/string_util.h" #include "base/time.h" diff --git a/net/spdy/spdy_proxy_client_socket.cc b/net/spdy/spdy_proxy_client_socket.cc new file mode 100644 index 0000000..e282d76 --- /dev/null +++ b/net/spdy/spdy_proxy_client_socket.cc @@ -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 diff --git a/net/spdy/spdy_proxy_client_socket.h b/net/spdy/spdy_proxy_client_socket.h new file mode 100644 index 0000000..30062b0 --- /dev/null +++ b/net/spdy/spdy_proxy_client_socket.h @@ -0,0 +1,183 @@ +// 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. + +#ifndef NET_SPDY_SPDY_PROXY_CLIENT_SOCKET_H_ +#define NET_SPDY_SPDY_PROXY_CLIENT_SOCKET_H_ +#pragma once + +#include <string> +#include <list> + +#include "base/basictypes.h" +#include "base/ref_counted.h" +#include "net/base/completion_callback.h" +#include "net/base/host_port_pair.h" +#include "net/base/net_log.h" +#include "net/http/http_auth_controller.h" +#include "net/http/http_request_headers.h" +#include "net/http/http_request_info.h" +#include "net/http/http_response_info.h" +#include "net/socket/client_socket.h" +#include "net/spdy/spdy_protocol.h" +#include "net/spdy/spdy_session.h" +#include "net/spdy/spdy_stream.h" + + +class GURL; + +namespace net { + +class AddressList; +class ClientSocketHandle; +class HttpStream; +class IOBuffer; +class SpdySession; +class SpdyStream; + +class SpdyProxyClientSocket : public ClientSocket, public SpdyStream::Delegate { + public: + // Create a socket on top of the |spdy_stream| by sending a SYN_STREAM + // CONNECT frame for |endpoint|. After the SYN_REPLY is received, + // any data read/written to the socket will be transferred in data + // frames. + 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); + + + // On destruction Disconnect() is called. + virtual ~SpdyProxyClientSocket(); + + const scoped_refptr<HttpAuthController>& auth_controller() { + return auth_; + } + + const HttpResponseInfo* GetConnectResponseInfo() const { + return response_.headers ? &response_ : NULL; + } + + // ClientSocket methods: + + virtual int Connect(CompletionCallback* callback); + virtual void Disconnect(); + virtual bool IsConnected() const; + virtual bool IsConnectedAndIdle() const; + virtual const BoundNetLog& NetLog() const { return net_log_; } + virtual void SetSubresourceSpeculation(); + virtual void SetOmniboxSpeculation(); + virtual bool WasEverUsed() const; + + // Socket methods: + + virtual int Read(IOBuffer* buf, int buf_len, CompletionCallback* callback); + virtual int Write(IOBuffer* buf, int buf_len, CompletionCallback* callback); + + virtual bool SetReceiveBufferSize(int32 size); + virtual bool SetSendBufferSize(int32 size); + + virtual int GetPeerAddress(AddressList* address) const; + + // SpdyStream::Delegate methods: + + // Called when SYN frame has been sent. + // Returns true if no more data to be sent after SYN frame. + virtual bool OnSendHeadersComplete(int status); + + // Called when stream is ready to send data. + // Returns network error code. OK when it successfully sent data. + virtual int OnSendBody(); + + // Called when data has been sent. |status| indicates network error + // or number of bytes has been sent. + // Returns true if no more data to be sent. + virtual bool OnSendBodyComplete(int status); + + // Called when SYN_STREAM or SYN_REPLY received. |status| indicates network + // error. Returns network error code. + virtual int OnResponseReceived(const spdy::SpdyHeaderBlock& response, + base::Time response_time, + int status); + + // Called when data is received. + virtual void OnDataReceived(const char* data, int length); + + // Called when data is sent. + virtual void OnDataSent(int length); + + // Called when SpdyStream is closed. + virtual void OnClose(int status); + + private: + enum State { + STATE_NONE, + STATE_GENERATE_AUTH_TOKEN, + STATE_GENERATE_AUTH_TOKEN_COMPLETE, + STATE_SEND_REQUEST, + STATE_SEND_REQUEST_COMPLETE, + STATE_READ_REPLY_COMPLETE, + STATE_DONE, + }; + + void OnIOComplete(int result); + + int DoLoop(int last_io_result); + int DoGenerateAuthToken(); + int DoGenerateAuthTokenComplete(int result); + int DoSendRequest(); + int DoSendRequestComplete(int result); + int DoReadReplyComplete(int result); + + // Populates |user_buffer_| with as much read data as possible + // and returns the number of bytes read. + int PopulateUserReadBuffer(); + + CompletionCallbackImpl<SpdyProxyClientSocket> io_callback_; + State next_state_; + + // Pointer to the SPDY Stream that this sits on top of. + scoped_refptr<SpdyStream> spdy_stream_; + + // Stores the callback to the layer above, called on completing Read() or + // Connect(). + CompletionCallback* read_callback_; + // Stores the callback to the layer above, called on completing Write(). + CompletionCallback* write_callback_; + + // CONNECT request and response. + HttpRequestInfo request_; + HttpResponseInfo response_; + + // The hostname and port of the endpoint. This is not necessarily the one + // specified by the URL, due to Alternate-Protocol or fixed testing ports. + const HostPortPair endpoint_; + scoped_refptr<HttpAuthController> auth_; + + // We buffer the response body as it arrives asynchronously from the stream. + std::list<scoped_refptr<DrainableIOBuffer> > read_buffer_; + + // User provided buffer for the Read() response. + scoped_refptr<DrainableIOBuffer> user_buffer_; + + // User specified number of bytes to be written. + int write_buffer_len_; + // Number of bytes written which have not been confirmed + int write_bytes_outstanding_; + + // True if read has ever returned zero for eof. + bool eof_has_been_read_; + // True if the transport socket has ever sent data. + bool was_ever_used_; + + const BoundNetLog net_log_; + + DISALLOW_COPY_AND_ASSIGN(SpdyProxyClientSocket); +}; + +} // namespace net + +#endif // NET_SPDY_SPDY_PROXY_CLIENT_SOCKET_H_ diff --git a/net/spdy/spdy_proxy_client_socket_unittest.cc b/net/spdy/spdy_proxy_client_socket_unittest.cc new file mode 100644 index 0000000..edde8a5 --- /dev/null +++ b/net/spdy/spdy_proxy_client_socket_unittest.cc @@ -0,0 +1,894 @@ +// 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 "base/utf_string_conversions.h" +#include "net/base/address_list.h" +#include "net/base/net_log.h" +#include "net/base/net_log_unittest.h" +#include "net/base/mock_host_resolver.h" +#include "net/base/test_completion_callback.h" +#include "net/base/winsock_init.h" +#include "net/http/http_response_info.h" +#include "net/http/http_response_headers.h" +#include "net/socket/client_socket_factory.h" +#include "net/socket/tcp_client_socket.h" +#include "net/socket/socket_test_util.h" +#include "net/spdy/spdy_protocol.h" +#include "net/spdy/spdy_session_pool.h" +#include "net/spdy/spdy_test_util.h" +#include "testing/platform_test.h" +#include "testing/gtest/include/gtest/gtest.h" + +//----------------------------------------------------------------------------- + +namespace { + +static const char kUrl[] = "https://www.google.com/"; +static const char kOriginHost[] = "www.google.com"; +static const int kOriginPort = 443; +static const char kOriginHostPort[] = "www.google.com:443"; +static const char kProxyUrl[] = "http://myproxy:6121/"; +static const char kProxyHost[] = "myproxy"; +static const int kProxyPort = 6121; +static const char kUserAgent[] = "Mozilla/1.0"; + +static const int kStreamId = 1; + +static const char kMsg1[] = "\0hello!\xff"; +static const int kLen1 = 8; +static const char kMsg2[] = "\012345678\0"; +static const int kLen2 = 10; +static const char kMsg3[] = "bye!"; +static const int kLen3 = 4; +static const char kMsg33[] = "bye!bye!"; +static const int kLen33 = kLen3 + kLen3; + +} // anonymous namespace + +namespace net { + +class SpdyProxyClientSocketTest : public PlatformTest { + public: + SpdyProxyClientSocketTest(); + + virtual void TearDown(); + + protected: + void Initialize(MockRead* reads, size_t reads_count, MockWrite* writes, + size_t writes_count); + spdy::SpdyFrame* ConstructConnectRequestFrame(); + spdy::SpdyFrame* ConstructConnectAuthRequestFrame(); + spdy::SpdyFrame* ConstructConnectReplyFrame(); + spdy::SpdyFrame* ConstructConnectAuthReplyFrame(); + spdy::SpdyFrame* ConstructConnectErrorReplyFrame(); + spdy::SpdyFrame* ConstructBodyFrame(const char* data, int length); + scoped_refptr<IOBufferWithSize> CreateBuffer(const char* data, int size); + void AssertConnectSucceeds(); + void AssertConnectionEstablished(); + void AssertSyncReadEquals(const char* data, int len); + void AssertAsyncReadEquals(const char* data, int len); + void AssertAsyncWriteSucceeds(const char* data, int len); + void AssertAsyncWriteWithReadsSucceeds(const char* data, int len, + int num_reads); + + void AddAuthToCache() { + const string16 kFoo(ASCIIToUTF16("foo")); + const string16 kBar(ASCIIToUTF16("bar")); + session_->auth_cache()->Add(GURL(kProxyUrl), "MyRealm1", "Basic", + "Basic realm=MyRealm1", kFoo, kBar, "/"); + } + + scoped_ptr<SpdyProxyClientSocket> sock_; + TestCompletionCallback callback_; + + private: + scoped_refptr<HttpNetworkSession> session_; + scoped_refptr<OrderedSocketData> data_; + SpdySessionDependencies session_deps_; + MockConnect connect_data_; + scoped_refptr<SpdySession> spdy_session_; + scoped_refptr<SpdyStream> spdy_stream_; + spdy::SpdyFramer framer_; + + std::string user_agent_; + GURL url_; + HostPortPair proxy_host_port_; + HostPortPair endpoint_host_port_pair_; + ProxyServer proxy_; + HostPortProxyPair endpoint_host_port_proxy_pair_; + scoped_refptr<TCPSocketParams> tcp_params_; + + DISALLOW_COPY_AND_ASSIGN(SpdyProxyClientSocketTest); +}; + +SpdyProxyClientSocketTest::SpdyProxyClientSocketTest() + : sock_(NULL), + callback_(), + session_(NULL), + data_(NULL), + session_deps_(), + connect_data_(false, OK), + spdy_session_(NULL), + spdy_stream_(NULL), + framer_(), + user_agent_(kUserAgent), + url_(kUrl), + proxy_host_port_(kProxyHost, kProxyPort), + endpoint_host_port_pair_(kOriginHost, kOriginPort), + proxy_(ProxyServer::SCHEME_HTTPS, proxy_host_port_), + endpoint_host_port_proxy_pair_(endpoint_host_port_pair_, proxy_), + tcp_params_(new TCPSocketParams(proxy_host_port_, LOWEST, url_, false)) { +} + +void SpdyProxyClientSocketTest::TearDown() { + if (session_ != NULL) + session_->spdy_session_pool()->CloseAllSessions(); + + spdy::SpdyFramer::set_enable_compression_default(true); + // Empty the current queue. + MessageLoop::current()->RunAllPending(); + PlatformTest::TearDown(); +} + +void SpdyProxyClientSocketTest::Initialize(MockRead* reads, + size_t reads_count, + MockWrite* writes, + size_t writes_count) { + data_ = new OrderedSocketData(reads, reads_count, writes, writes_count); + data_->set_connect_data(connect_data_); + + session_deps_.socket_factory->AddSocketDataProvider(data_.get()); + session_deps_.host_resolver->set_synchronous_mode(true); + + session_ = SpdySessionDependencies::SpdyCreateSession(&session_deps_); + SpdySession::SetSSLMode(false); + spdy::SpdyFramer::set_enable_compression_default(false); + + // Creates a new spdy session + spdy_session_ = + session_->spdy_session_pool()->Get(endpoint_host_port_proxy_pair_, + session_->mutable_spdy_settings(), + BoundNetLog()); + + // Perform the TCP connect + scoped_ptr<ClientSocketHandle> connection(new ClientSocketHandle); + EXPECT_EQ(OK, + connection->Init(endpoint_host_port_pair_.ToString(), tcp_params_, + LOWEST, NULL, session_->tcp_socket_pool(), + BoundNetLog())); + spdy_session_->InitializeWithSocket(connection.release(), false, OK); + + // Create the SPDY Stream + ASSERT_EQ( + OK, + spdy_session_->CreateStream(url_, LOWEST, &spdy_stream_, BoundNetLog(), + NULL)); + + // Create the SpdyProxyClientSocket + sock_.reset( + new SpdyProxyClientSocket(spdy_stream_, user_agent_, + endpoint_host_port_pair_, url_, + proxy_host_port_, session_->auth_cache(), + session_->http_auth_handler_factory())); +} + +scoped_refptr<IOBufferWithSize> SpdyProxyClientSocketTest::CreateBuffer( + const char* data, int size) { + scoped_refptr<IOBufferWithSize> buf(new IOBufferWithSize(size)); + memcpy(buf->data(), data, size); + return buf; +} + +void SpdyProxyClientSocketTest::AssertConnectSucceeds() { + ASSERT_EQ(ERR_IO_PENDING, sock_->Connect(&callback_)); + ASSERT_EQ(OK, callback_.WaitForResult()); +} + +void SpdyProxyClientSocketTest::AssertConnectionEstablished() { + const HttpResponseInfo* response = sock_->GetConnectResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_EQ(200, response->headers->response_code()); + ASSERT_EQ("Connection Established", response->headers->GetStatusText()); +} + +void SpdyProxyClientSocketTest::AssertSyncReadEquals(const char* data, + int len) { + scoped_refptr<IOBuffer> buf(new IOBuffer(len)); + ASSERT_EQ(len, sock_->Read(buf, len, NULL)); + ASSERT_EQ(std::string(data, len), std::string(buf->data(), len)); + ASSERT_TRUE(sock_->IsConnected()); +} + +void SpdyProxyClientSocketTest::AssertAsyncReadEquals(const char* data, + int len) { + // Issue the read, which will be completed asynchronously + scoped_refptr<IOBuffer> buf(new IOBuffer(len)); + ASSERT_EQ(ERR_IO_PENDING, sock_->Read(buf, len, &callback_)); + EXPECT_TRUE(sock_->IsConnected()); + + // Dummy write to un-block the read + AssertAsyncWriteSucceeds(kMsg2, kLen2); + EXPECT_TRUE(sock_->IsConnected()); + + // Now the read will return + EXPECT_EQ(len, callback_.WaitForResult()); + ASSERT_EQ(std::string(data, len), std::string(buf->data(), len)); +} + +void SpdyProxyClientSocketTest::AssertAsyncWriteSucceeds(const char* data, + int len) { + AssertAsyncWriteWithReadsSucceeds(data, len, 0); +} + +void SpdyProxyClientSocketTest::AssertAsyncWriteWithReadsSucceeds( + const char* data, int len, int num_reads) { + scoped_refptr<IOBufferWithSize> buf(CreateBuffer(data, len)); + + TestCompletionCallback callback; + EXPECT_EQ(ERR_IO_PENDING, sock_->Write(buf, buf->size(), &callback)); + + // Dummy reads to un-block the writes + for (int i = 0; i < num_reads; i++) { + AssertSyncReadEquals(kMsg2, kLen2); + } + + callback.WaitForResult(); +} + +// Constructs a standard SPDY SYN_STREAM frame for a CONNECT request. +spdy::SpdyFrame* SpdyProxyClientSocketTest::ConstructConnectRequestFrame() { + const SpdyHeaderInfo kSynStartHeader = { + spdy::SYN_STREAM, + kStreamId, + 0, + net::ConvertRequestPriorityToSpdyPriority(LOWEST), + spdy::CONTROL_FLAG_NONE, + false, + spdy::INVALID, + NULL, + 0, + spdy::DATA_FLAG_NONE + }; + const char* const kConnectHeaders[] = { + "method", "CONNECT", + "url", kOriginHostPort, + "host", kOriginHost, + "user-agent", kUserAgent, + "version", "HTTP/1.1", + "proxy-connection", "keep-alive", + }; + return ConstructSpdyPacket( + kSynStartHeader, NULL, 0, kConnectHeaders, arraysize(kConnectHeaders)/2); +} + +// Constructs a SPDY SYN_STREAM frame for a CONNECT request which includes +// Proxy-Authorization headers. +spdy::SpdyFrame* SpdyProxyClientSocketTest::ConstructConnectAuthRequestFrame() { + const SpdyHeaderInfo kSynStartHeader = { + spdy::SYN_STREAM, + kStreamId, + 0, + net::ConvertRequestPriorityToSpdyPriority(LOWEST), + spdy::CONTROL_FLAG_NONE, + false, + spdy::INVALID, + NULL, + 0, + spdy::DATA_FLAG_NONE + }; + const char* const kConnectHeaders[] = { + "method", "CONNECT", + "url", kOriginHostPort, + "host", kOriginHost, + "user-agent", kUserAgent, + "version", "HTTP/1.1", + "proxy-authorization", "Basic Zm9vOmJhcg==", + "proxy-connection", "keep-alive", + }; + return ConstructSpdyPacket( + kSynStartHeader, NULL, 0, kConnectHeaders, arraysize(kConnectHeaders)/2); +} + +// Constructs a standard SPDY SYN_REPLY frame to match the SPDY CONNECT. +spdy::SpdyFrame* SpdyProxyClientSocketTest::ConstructConnectReplyFrame() { + const char* const kStandardReplyHeaders[] = { + "status", "200 Connection Established", + "version", "HTTP/1.1" + }; + return ConstructSpdyControlFrame(NULL, + 0, + false, + kStreamId, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + kStandardReplyHeaders, + arraysize(kStandardReplyHeaders)); +} + +// Constructs a standard SPDY SYN_REPLY frame to match the SPDY CONNECT. +spdy::SpdyFrame* SpdyProxyClientSocketTest::ConstructConnectAuthReplyFrame() { + const char* const kStandardReplyHeaders[] = { + "status", "407 Proxy Authentication Required", + "version", "HTTP/1.1", + "proxy-authenticate", "Basic realm=\"MyRealm1\"", + }; + + return ConstructSpdyControlFrame(NULL, + 0, + false, + kStreamId, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + kStandardReplyHeaders, + arraysize(kStandardReplyHeaders)); +} + +// Constructs a SPDY SYN_REPLY frame with an HTTP 500 error. +spdy::SpdyFrame* SpdyProxyClientSocketTest::ConstructConnectErrorReplyFrame() { + const char* const kStandardReplyHeaders[] = { + "status", "500 Internal Server Error", + "version", "HTTP/1.1", + }; + + return ConstructSpdyControlFrame(NULL, + 0, + false, + kStreamId, + LOWEST, + spdy::SYN_REPLY, + spdy::CONTROL_FLAG_NONE, + kStandardReplyHeaders, + arraysize(kStandardReplyHeaders)); +} + +spdy::SpdyFrame* SpdyProxyClientSocketTest::ConstructBodyFrame(const char* data, + int length) { + return framer_.CreateDataFrame(kStreamId, data, length, spdy::DATA_FLAG_NONE); +} + +// ----------- Connect + +TEST_F(SpdyProxyClientSocketTest, ConnectSendsCorrectRequest) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1), + MockRead(true, 0, 0), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + ASSERT_FALSE(sock_->IsConnected()); + + AssertConnectSucceeds(); + + AssertConnectionEstablished(); +} + +TEST_F(SpdyProxyClientSocketTest, ConnectWithAuthRequested) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectAuthReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1), + MockRead(true, 0, 0), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + EXPECT_EQ(ERR_IO_PENDING, sock_->Connect(&callback_)); + EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, callback_.WaitForResult()); + + const HttpResponseInfo* response = sock_->GetConnectResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_EQ(407, response->headers->response_code()); + ASSERT_EQ("Proxy Authentication Required", + response->headers->GetStatusText()); +} + +TEST_F(SpdyProxyClientSocketTest, ConnectWithAuthCredentials) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectAuthRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1), + MockRead(true, 0, 0), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + AddAuthToCache(); + + EXPECT_EQ(ERR_IO_PENDING, sock_->Connect(&callback_)); + EXPECT_EQ(OK, callback_.WaitForResult()); + + AssertConnectionEstablished(); +} + +TEST_F(SpdyProxyClientSocketTest, ConnectFails) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + MockRead(true, 0, 0), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + ASSERT_FALSE(sock_->IsConnected()); + + ASSERT_EQ(ERR_IO_PENDING, sock_->Connect(&callback_)); + ASSERT_EQ(ERR_CONNECTION_CLOSED, callback_.WaitForResult()); + + ASSERT_FALSE(sock_->IsConnected()); +} + +// ----------- WasEverUsed + +TEST_F(SpdyProxyClientSocketTest, WasEverUsedReturnsCorrectValues) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 0), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 1), + MockRead(true, 0, 0), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + EXPECT_FALSE(sock_->WasEverUsed()); + AssertConnectSucceeds(); + EXPECT_TRUE(sock_->WasEverUsed()); + sock_->Disconnect(); + EXPECT_TRUE(sock_->WasEverUsed()); +} + +// ----------- GetPeerAddress + +TEST_F(SpdyProxyClientSocketTest, GetPeerAddressReturnsCorrectValues) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 1), + MockWrite(true, 0, 3), // EOF + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 2), + MockRead(false, 0, 4), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + net::AddressList addr; + EXPECT_EQ(ERR_UNEXPECTED, sock_->GetPeerAddress(&addr)); + AssertConnectSucceeds(); + EXPECT_TRUE(sock_->IsConnected()); + EXPECT_EQ(OK, sock_->GetPeerAddress(&addr)); + sock_->Disconnect(); + EXPECT_EQ(ERR_UNEXPECTED, sock_->GetPeerAddress(&addr)); +} + +// ----------- Write + +TEST_F(SpdyProxyClientSocketTest, WriteSendsDataInDataFrame) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + scoped_ptr<spdy::SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<spdy::SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); + MockWrite writes[] = { + CreateMockWrite(*conn, 0), + CreateMockWrite(*msg1, 3), + CreateMockWrite(*msg2, 4), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 2), + MockRead(true, 0, 6), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + AssertAsyncWriteSucceeds(kMsg1, kLen1); + AssertAsyncWriteSucceeds(kMsg2, kLen2); +} + +// ----------- Read + +TEST_F(SpdyProxyClientSocketTest, ReadReadsDataInDataFrame) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 1), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<spdy::SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<spdy::SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); + MockRead reads[] = { + CreateMockRead(*resp, 2), + CreateMockRead(*msg1, 3), + CreateMockRead(*msg2, 4), + MockRead(true, 0, 6), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + AssertSyncReadEquals(kMsg1, kLen1); + AssertSyncReadEquals(kMsg2, kLen2); +} + +TEST_F(SpdyProxyClientSocketTest, ReadAuthResponseBody) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 1), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectAuthReplyFrame()); + scoped_ptr<spdy::SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<spdy::SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); + MockRead reads[] = { + CreateMockRead(*resp, 2), + CreateMockRead(*msg1, 3), + CreateMockRead(*msg2, 4), + MockRead(true, 0, 6), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + EXPECT_EQ(ERR_IO_PENDING, sock_->Connect(&callback_)); + EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, callback_.WaitForResult()); + // EXPECT_EQ(ERR_PROXY_AUTH_REQUESTED, callback_.WaitForResult()); + + AssertSyncReadEquals(kMsg1, kLen1); + AssertSyncReadEquals(kMsg2, kLen2); +} + +TEST_F(SpdyProxyClientSocketTest, ReadErrorResponseBody) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 1), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectErrorReplyFrame()); + scoped_ptr<spdy::SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<spdy::SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); + MockRead reads[] = { + CreateMockRead(*resp, 2), + CreateMockRead(*msg1, 3), + CreateMockRead(*msg2, 4), + MockRead(true, 0, 6), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + EXPECT_EQ(ERR_IO_PENDING, sock_->Connect(&callback_)); + EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, callback_.WaitForResult()); + + AssertSyncReadEquals(kMsg1, kLen1); + AssertSyncReadEquals(kMsg2, kLen2); +} + +// ----------- Reads and Writes + +TEST_F(SpdyProxyClientSocketTest, AsyncReadAroundWrite) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + scoped_ptr<spdy::SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); + MockWrite writes[] = { + CreateMockWrite(*conn, 1), + CreateMockWrite(*msg2, 4), // write to un-cork the read + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<spdy::SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<spdy::SpdyFrame> msg3(ConstructBodyFrame(kMsg3, kLen3)); + MockRead reads[] = { + CreateMockRead(*resp, 2), + CreateMockRead(*msg1, 3), // sync read + CreateMockRead(*msg3, 5, true), // async read + MockRead(true, 0, 9), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + AssertSyncReadEquals(kMsg1, kLen1); + + AssertAsyncReadEquals(kMsg3, kLen3); +} + +TEST_F(SpdyProxyClientSocketTest, AsyncWriteAroundRead) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + scoped_ptr<spdy::SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + MockWrite writes[] = { + CreateMockWrite(*conn, 1), + CreateMockWrite(*msg1, 4), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<spdy::SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); + MockRead reads[] = { + CreateMockRead(*resp, 2), + CreateMockRead(*msg2, 3), // sync read (which will un-cork the write) + MockRead(true, 0, 5), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + AssertAsyncWriteWithReadsSucceeds(kMsg1, kLen1, 1); +} + +TEST_F(SpdyProxyClientSocketTest, Mixed) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + scoped_ptr<spdy::SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); + MockWrite writes[] = { + CreateMockWrite(*conn, 1), + CreateMockWrite(*msg2, 4), // write to un-cork the read + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<spdy::SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<spdy::SpdyFrame> msg3(ConstructBodyFrame(kMsg3, kLen3)); + MockRead reads[] = { + CreateMockRead(*resp, 2), + CreateMockRead(*msg1, 3), // sync read + CreateMockRead(*msg3, 5, true), // async read + MockRead(true, 0, 9), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + AssertSyncReadEquals(kMsg1, kLen1); + AssertAsyncReadEquals(kMsg3, kLen3); +} + +TEST_F(SpdyProxyClientSocketTest, MultipleShortReads) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 1), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<spdy::SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<spdy::SpdyFrame> msg3(ConstructBodyFrame(kMsg3, kLen3)); + MockRead reads[] = { + CreateMockRead(*resp, 2), + CreateMockRead(*msg1, 3), // sync read + CreateMockRead(*msg3, 4), // sync read + CreateMockRead(*msg3, 5), // sync read + MockRead(true, 0, 8), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + AssertSyncReadEquals(kMsg1, kLen1); + // The payload from two data frames, each with kMsg3 will be combined + // together into a single read(). + AssertSyncReadEquals(kMsg33, kLen33); +} + +TEST_F(SpdyProxyClientSocketTest, MultipleShortReadsThenMoreRead) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 1), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<spdy::SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<spdy::SpdyFrame> msg3(ConstructBodyFrame(kMsg3, kLen3)); + scoped_ptr<spdy::SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); + MockRead reads[] = { + CreateMockRead(*resp, 2), + CreateMockRead(*msg1, 3), // sync read + CreateMockRead(*msg3, 4), // sync read + CreateMockRead(*msg3, 5), // sync read + CreateMockRead(*msg2, 5), // sync read + MockRead(true, 0, 8), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + AssertSyncReadEquals(kMsg1, kLen1); + // The payload from two data frames, each with kMsg3 will be combined + // together into a single read(). + AssertSyncReadEquals(kMsg33, kLen33); + AssertSyncReadEquals(kMsg2, kLen2); +} + + +TEST_F(SpdyProxyClientSocketTest, LargeSplitRead) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 1), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<spdy::SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + scoped_ptr<spdy::SpdyFrame> msg33(ConstructBodyFrame(kMsg33, kLen33)); + scoped_ptr<spdy::SpdyFrame> msg2(ConstructBodyFrame(kMsg2, kLen2)); + MockRead reads[] = { + CreateMockRead(*resp, 2), + CreateMockRead(*msg1, 3), + CreateMockRead(*msg33, 4), + MockRead(true, 0, 8), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + AssertSyncReadEquals(kMsg1, kLen1); + // The payload from the single large data frame will be read across + // two different reads. + AssertSyncReadEquals(kMsg3, kLen3); + AssertSyncReadEquals(kMsg3, kLen3); +} + +TEST_F(SpdyProxyClientSocketTest, WriteLargeDataSplits) { + std::string chunk_data(kMaxSpdyFrameChunkSize, 'x'); + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + scoped_ptr<spdy::SpdyFrame> chunk(ConstructBodyFrame(chunk_data.data(), + chunk_data.length())); + MockWrite writes[] = { + CreateMockWrite(*conn, 1), + CreateMockWrite(*chunk, 3, false), + CreateMockWrite(*chunk, 4, false), + CreateMockWrite(*chunk, 5, false) + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 2), + MockRead(true, 0, 7), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + std::string big_data(kMaxSpdyFrameChunkSize * 3, 'x'); + AssertAsyncWriteSucceeds(big_data.data(), big_data.length()); +} + +// ----------- Reading/Writing on Closed socket + +// Reading from an already closed socket should return 0 +TEST_F(SpdyProxyClientSocketTest, ReadOnClosedSocketReturnsZero) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 1), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 2), + MockRead(true, 0, 4), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + sock_->Disconnect(); + + ASSERT_EQ(0, sock_->Read(NULL, 1, NULL)); + ASSERT_EQ(ERR_CONNECTION_CLOSED, sock_->Read(NULL, 1, NULL)); + ASSERT_EQ(ERR_CONNECTION_CLOSED, sock_->Read(NULL, 1, NULL)); +} + +// Calling Write() on a closed socket is an error +TEST_F(SpdyProxyClientSocketTest, WriteOnClosedStream) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 1), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + scoped_ptr<spdy::SpdyFrame> msg1(ConstructBodyFrame(kMsg1, kLen1)); + MockRead reads[] = { + CreateMockRead(*resp, 2), + MockRead(false, 0, 3), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + scoped_refptr<IOBufferWithSize> buf(CreateBuffer(kMsg1, kLen1)); + EXPECT_EQ(ERR_CONNECTION_CLOSED, sock_->Write(buf, buf->size(), NULL)); +} + +// ----------- Pending read/write when closed + +// If the socket is closed with a pending Write(), the callback +// should not be called. +TEST_F(SpdyProxyClientSocketTest, DisconnectWithWritePending) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 1), + MockWrite(true, 0, 3), // EOF + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 2), + MockRead(false, 0, 4), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + EXPECT_TRUE(sock_->IsConnected()); + + scoped_refptr<IOBufferWithSize> buf(CreateBuffer(kMsg1, kLen1)); + EXPECT_EQ(ERR_IO_PENDING, sock_->Write(buf, buf->size(), &callback_)); + + sock_->Disconnect(); + + EXPECT_FALSE(sock_->IsConnected()); + EXPECT_FALSE(callback_.have_result()); +} + +// If the socket is closed with a pending Read(), the callback +// should not be called. +TEST_F(SpdyProxyClientSocketTest, DisconnectWithReadPending) { + scoped_ptr<spdy::SpdyFrame> conn(ConstructConnectRequestFrame()); + MockWrite writes[] = { + CreateMockWrite(*conn, 1), + }; + + scoped_ptr<spdy::SpdyFrame> resp(ConstructConnectReplyFrame()); + MockRead reads[] = { + CreateMockRead(*resp, 2), + MockRead(true, 0, 4), // EOF + }; + + Initialize(reads, arraysize(reads), writes, arraysize(writes)); + + AssertConnectSucceeds(); + + EXPECT_TRUE(sock_->IsConnected()); + + scoped_refptr<IOBuffer> buf(new IOBuffer(kLen1)); + ASSERT_EQ(ERR_IO_PENDING, sock_->Read(buf, kLen1, &callback_)); + + sock_->Disconnect(); + + EXPECT_FALSE(sock_->IsConnected()); + EXPECT_FALSE(callback_.have_result()); +} + +} // namespace net diff --git a/net/spdy/spdy_session.h b/net/spdy/spdy_session.h index 2894471..49de547 100644 --- a/net/spdy/spdy_session.h +++ b/net/spdy/spdy_session.h @@ -158,6 +158,12 @@ class SpdySession : public base::RefCounted<SpdySession>, return frames_received_ > 0; } + // Returns true if the underlying transport socket ever had any reads or + // writes. + bool WasEverUsed() const { + return connection_->socket()->WasEverUsed(); + } + void set_in_session_pool(bool val) { in_session_pool_ = val; } // Access to the number of active and pending streams. These are primarily @@ -169,6 +175,10 @@ class SpdySession : public base::RefCounted<SpdySession>, const BoundNetLog& net_log() const { return net_log_; } + int GetPeerAddress(AddressList* address) const { + return connection_->socket()->GetPeerAddress(address); + } + private: friend class base::RefCounted<SpdySession>; FRIEND_TEST_ALL_PREFIXES(SpdySessionTest, GetActivePushStream); diff --git a/net/spdy/spdy_stream.cc b/net/spdy/spdy_stream.cc index 88b009d..102d318 100644 --- a/net/spdy/spdy_stream.cc +++ b/net/spdy/spdy_stream.cc @@ -205,6 +205,13 @@ void SpdyStream::DecreaseRecvWindowSize(int delta_window_size) { session_->ResetStream(stream_id_, spdy::FLOW_CONTROL_ERROR); } +int SpdyStream::GetPeerAddress(AddressList* address) const { + return session_->GetPeerAddress(address); +} + +bool SpdyStream::WasEverUsed() const { + return session_->WasEverUsed(); +} base::Time SpdyStream::GetRequestTime() const { return request_time_; diff --git a/net/spdy/spdy_stream.h b/net/spdy/spdy_stream.h index 1730af5..4e30a60 100644 --- a/net/spdy/spdy_stream.h +++ b/net/spdy/spdy_stream.h @@ -21,6 +21,7 @@ namespace net { +class AddressList; class SpdySession; class SSLCertRequestInfo; class SSLInfo; @@ -129,6 +130,12 @@ class SpdyStream : public base::RefCounted<SpdyStream> { // Decreases |send_window_size_| by the given number of bytes. void DecreaseSendWindowSize(int delta_window_size); + int GetPeerAddress(AddressList* address) const; + + // Returns true if the underlying transport socket ever had any reads or + // writes. + bool WasEverUsed() const; + // Increases |recv_window_size_| by the given number of bytes, also sends // a WINDOW_UPDATE frame. void IncreaseRecvWindowSize(int delta_window_size); |