From 7642b5aed675a2b99007a833d58b705b493b5ef4 Mon Sep 17 00:00:00 2001 From: "rch@chromium.org" Date: Wed, 1 Sep 2010 20:55:17 +0000 Subject: Add support for speaking SPDY to an HTTPS proxy. Currently only http urls are supported. BUG=29625 TEST=HttpNetworkTransactionTest.HttpsProxySpdyGet Review URL: http://codereview.chromium.org/3259006 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@58236 0039d316-1c4b-4281-b951-d872f2087c98 --- net/http/http_network_transaction_unittest.cc | 59 +++++++++++++++++++++++++++ net/http/http_proxy_client_socket.cc | 11 ++++- net/http/http_proxy_client_socket.h | 9 +++- net/http/http_proxy_client_socket_pool.cc | 11 ++++- net/http/http_proxy_client_socket_pool.h | 1 + net/http/http_stream_request.cc | 43 ++++++++++++++++--- 6 files changed, 124 insertions(+), 10 deletions(-) (limited to 'net/http') diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc index 5d21eba..d0ed59b 100644 --- a/net/http/http_network_transaction_unittest.cc +++ b/net/http/http_network_transaction_unittest.cc @@ -1722,6 +1722,65 @@ TEST_F(HttpNetworkTransactionTest, HttpsProxyGet) { EXPECT_TRUE(response->auth_challenge.get() == NULL); } +// Test a SPDY get through an HTTPS Proxy. +TEST_F(HttpNetworkTransactionTest, HttpsProxySpdyGet) { + // Configure against https proxy server "proxy:70". + SessionDependencies session_deps(CreateFixedProxyService("https://proxy:70")); + CapturingBoundNetLog log(CapturingNetLog::kUnbounded); + session_deps.net_log = log.bound().net_log(); + scoped_refptr session(CreateSession(&session_deps)); + + scoped_ptr trans(new HttpNetworkTransaction(session)); + + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://www.google.com/"); + request.load_flags = 0; + + // fetch http://www.google.com/ via SPDY + scoped_ptr req(ConstructSpdyGet(NULL, 0, false, 1, LOWEST, + false)); + MockWrite spdy_writes[] = { CreateMockWrite(*req) }; + + scoped_ptr resp(ConstructSpdyGetSynReply(NULL, 0, 1)); + scoped_ptr data(ConstructSpdyBodyFrame(1, true)); + MockRead spdy_reads[] = { + CreateMockRead(*resp), + CreateMockRead(*data), + MockRead(true, 0, 0), + }; + + scoped_refptr spdy_data( + new DelayedSocketData( + 1, // wait for one write to finish before reading. + spdy_reads, arraysize(spdy_reads), + spdy_writes, arraysize(spdy_writes))); + session_deps.socket_factory.AddSocketDataProvider(spdy_data); + + SSLSocketDataProvider ssl(true, OK); + ssl.next_proto_status = SSLClientSocket::kNextProtoNegotiated; + ssl.next_proto = "spdy/2"; + ssl.was_npn_negotiated = true; + session_deps.socket_factory.AddSSLSocketDataProvider(&ssl); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, &callback1, log.bound()); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response != NULL); + ASSERT_TRUE(response->headers != NULL); + EXPECT_EQ("HTTP/1.1 200 OK", response->headers->GetStatusLine()); + + std::string response_data; + ASSERT_EQ(OK, ReadTransaction(trans.get(), &response_data)); + EXPECT_EQ(net::kUploadData, response_data); +} + // Test the challenge-response-retry sequence through an HTTPS Proxy TEST_F(HttpNetworkTransactionTest, HttpsProxyAuthRetry) { // Configure against https proxy server "proxy:70". diff --git a/net/http/http_proxy_client_socket.cc b/net/http/http_proxy_client_socket.cc index 7ed80a5..eccd652 100644 --- a/net/http/http_proxy_client_socket.cc +++ b/net/http/http_proxy_client_socket.cc @@ -53,7 +53,8 @@ HttpProxyClientSocket::HttpProxyClientSocket( ClientSocketHandle* transport_socket, const GURL& request_url, const std::string& user_agent, const HostPortPair& endpoint, const HostPortPair& proxy_server, - const scoped_refptr& session, bool tunnel) + const scoped_refptr& session, bool tunnel, + bool using_spdy) : ALLOW_THIS_IN_INITIALIZER_LIST( io_callback_(this, &HttpProxyClientSocket::OnIOComplete)), next_state_(STATE_NONE), @@ -65,6 +66,7 @@ HttpProxyClientSocket::HttpProxyClientSocket( GURL("http://" + proxy_server.ToString()), session) : NULL), tunnel_(tunnel), + using_spdy_(using_spdy), net_log_(transport_socket->socket()->NetLog()) { // Synthesize the bits of a request that we actually use. request_.url = request_url; @@ -83,7 +85,12 @@ int HttpProxyClientSocket::Connect(CompletionCallback* callback) { DCHECK(transport_->socket()); DCHECK(!user_callback_); - if (!tunnel_) + // TODO(rch): figure out the right way to set up a tunnel with SPDY. + // This approach sends the complete HTTPS request to the proxy + // which allows the proxy to see "private" data. Instead, we should + // create an SSL tunnel to the origin server using the CONNECT method + // inside a single SPDY stream. + if (using_spdy_ || !tunnel_) next_state_ = STATE_DONE; if (next_state_ == STATE_DONE) return OK; diff --git a/net/http/http_proxy_client_socket.h b/net/http/http_proxy_client_socket.h index 488fbdc..adfcc11 100644 --- a/net/http/http_proxy_client_socket.h +++ b/net/http/http_proxy_client_socket.h @@ -39,7 +39,8 @@ class HttpProxyClientSocket : public ClientSocket { const HostPortPair& endpoint, const HostPortPair& proxy_server, const scoped_refptr& session, - bool tunnel); + bool tunnel, + bool using_spdy); // On destruction Disconnect() is called. virtual ~HttpProxyClientSocket(); @@ -57,6 +58,10 @@ class HttpProxyClientSocket : public ClientSocket { return auth_; } + bool using_spdy() { + return using_spdy_; + } + // ClientSocket methods: // Authenticates to the Http Proxy and then passes data freely. @@ -140,6 +145,8 @@ class HttpProxyClientSocket : public ClientSocket { const HostPortPair endpoint_; scoped_refptr auth_; const bool tunnel_; + // If true, then the connection to the proxy is a SPDY connection. + const bool using_spdy_; std::string request_headers_; diff --git a/net/http/http_proxy_client_socket_pool.cc b/net/http/http_proxy_client_socket_pool.cc index 34bc0bb..f11ad30 100644 --- a/net/http/http_proxy_client_socket_pool.cc +++ b/net/http/http_proxy_client_socket_pool.cc @@ -16,6 +16,7 @@ #include "net/socket/client_socket_handle.h" #include "net/socket/client_socket_pool_base.h" #include "net/socket/tcp_client_socket_pool.h" +#include "net/socket/ssl_client_socket.h" namespace net { @@ -67,7 +68,8 @@ HttpProxyConnectJob::HttpProxyConnectJob( ssl_pool_(ssl_pool), resolver_(host_resolver), ALLOW_THIS_IN_INITIALIZER_LIST( - callback_(this, &HttpProxyConnectJob::OnIOComplete)) { + callback_(this, &HttpProxyConnectJob::OnIOComplete)), + using_spdy_(false) { } HttpProxyConnectJob::~HttpProxyConnectJob() {} @@ -182,6 +184,10 @@ int HttpProxyConnectJob::DoSSLConnectComplete(int result) { return result; } + SSLClientSocket* ssl = + static_cast(transport_socket_handle_->socket()); + using_spdy_ = ssl->was_spdy_negotiated(); + // Reset the timer to just the length of time allowed for HttpProxy handshake // so that a fast SSL connection plus a slow HttpProxy failure doesn't take // longer to timeout than it should. @@ -204,7 +210,8 @@ int HttpProxyConnectJob::DoHttpProxyConnect() { params_->user_agent(), params_->endpoint(), proxy_server, params_->session(), - params_->tunnel())); + params_->tunnel(), + using_spdy_)); int result = transport_socket_->Connect(&callback_); // Clear the circular reference to HttpNetworkSession (|params_| reference diff --git a/net/http/http_proxy_client_socket_pool.h b/net/http/http_proxy_client_socket_pool.h index 8a16e22..20c0f26 100644 --- a/net/http/http_proxy_client_socket_pool.h +++ b/net/http/http_proxy_client_socket_pool.h @@ -132,6 +132,7 @@ class HttpProxyConnectJob : public ConnectJob { CompletionCallbackImpl callback_; scoped_ptr transport_socket_handle_; scoped_ptr transport_socket_; + bool using_spdy_; DISALLOW_COPY_AND_ASSIGN(HttpProxyConnectJob); }; diff --git a/net/http/http_stream_request.cc b/net/http/http_stream_request.cc index 6a53a72..4d6895c 100644 --- a/net/http/http_stream_request.cc +++ b/net/http/http_stream_request.cc @@ -427,6 +427,17 @@ int HttpStreamRequest::DoInitConnection() { next_state_ = STATE_CREATE_STREAM; return OK; } + // Check next if we have a spdy session for this proxy. If so, then go + // straight to using that. + if (proxy_info()->is_https()) { + HostPortProxyPair proxy(proxy_info()->proxy_server().host_port_pair(), + proxy_info()->proxy_server()); + if (session_->spdy_session_pool()->HasSession(proxy)) { + using_spdy_ = true; + next_state_ = STATE_CREATE_STREAM; + return OK; + } + } // Build the string used to uniquely identify connections of this type. // Determine the host and port to connect to. @@ -569,6 +580,14 @@ int HttpStreamRequest::DoInitConnectionComplete(int result) { } if (force_spdy_over_ssl_ && force_spdy_always_) using_spdy_ = true; + } else if (proxy_info()->is_https() && connection_->socket() && + result == OK) { + HttpProxyClientSocket* proxy_socket = + static_cast(connection_->socket()); + if (proxy_socket->using_spdy()) { + was_npn_negotiated_ = true; + using_spdy_ = true; + } } // We may be using spdy without SSL @@ -654,15 +673,29 @@ int HttpStreamRequest::DoCreateStream() { CHECK(!stream_.get()); + bool direct = true; const scoped_refptr spdy_pool = session_->spdy_session_pool(); scoped_refptr spdy_session; - HostPortProxyPair pair(endpoint_, proxy_info()->proxy_server()); - if (session_->spdy_session_pool()->HasSession(pair)) { + const ProxyServer& proxy_server = proxy_info()->proxy_server(); + HostPortProxyPair pair(endpoint_, proxy_server); + if (spdy_pool->HasSession(pair)) { + // We have a SPDY session to the origin server. This might be a direct + // connection, or it might be a SPDY session through an HTTP or HTTPS proxy. spdy_session = - session_->spdy_session_pool()->Get(pair, session_, net_log_); - } else { + spdy_pool->Get(pair, session_, net_log_); + } else if (proxy_info()->is_https()) { + // If we don't have a direct SPDY session, and we're using an HTTPS + // proxy, then we might have a SPDY session to the proxy + pair = HostPortProxyPair(proxy_server.host_port_pair(), proxy_server); + if (spdy_pool->HasSession(pair)) { + spdy_session = spdy_pool->Get(pair, session_, net_log_); + } + direct = false; + } + + if (!spdy_session.get()) { // SPDY can be negotiated using the TLS next protocol negotiation (NPN) // extension, or just directly using SSL. Either way, |connection_| must // contain an SSLClientSocket. @@ -677,7 +710,7 @@ int HttpStreamRequest::DoCreateStream() { if (spdy_session->IsClosed()) return ERR_CONNECTION_CLOSED; - SpdyHttpStream* stream = new SpdyHttpStream(spdy_session); + SpdyHttpStream* stream = new SpdyHttpStream(spdy_session, direct); stream_.reset(new HttpStreamHandle(NULL, stream)); return OK; } -- cgit v1.1