1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "net/spdy/spdy_proxy_client_socket.h"
7 #include <algorithm> // min
9 #include "base/logging.h"
10 #include "base/string_util.h"
11 #include "googleurl/src/gurl.h"
12 #include "net/base/auth.h"
13 #include "net/base/io_buffer.h"
14 #include "net/base/net_util.h"
15 #include "net/http/http_auth_cache.h"
16 #include "net/http/http_auth_handler_factory.h"
17 #include "net/http/http_net_log_params.h"
18 #include "net/http/http_proxy_utils.h"
19 #include "net/http/http_response_headers.h"
20 #include "net/spdy/spdy_http_utils.h"
24 SpdyProxyClientSocket::SpdyProxyClientSocket(
25 SpdyStream
* spdy_stream
,
26 const std::string
& user_agent
,
27 const HostPortPair
& endpoint
,
29 const HostPortPair
& proxy_server
,
30 HttpAuthCache
* auth_cache
,
31 HttpAuthHandlerFactory
* auth_handler_factory
)
32 : ALLOW_THIS_IN_INITIALIZER_LIST(
33 io_callback_(this, &SpdyProxyClientSocket::OnIOComplete
)),
34 next_state_(STATE_DISCONNECTED
),
35 spdy_stream_(spdy_stream
),
37 write_callback_(NULL
),
40 new HttpAuthController(HttpAuth::AUTH_PROXY
,
41 GURL("https://" + proxy_server
.ToString()),
43 auth_handler_factory
)),
46 write_bytes_outstanding_(0),
47 eof_has_been_read_(false),
48 net_log_(spdy_stream
->net_log()) {
49 request_
.method
= "CONNECT";
51 if (!user_agent
.empty())
52 request_
.extra_headers
.SetHeader(HttpRequestHeaders::kUserAgent
,
54 spdy_stream_
->SetDelegate(this);
55 was_ever_used_
= spdy_stream_
->WasEverUsed();
58 SpdyProxyClientSocket::~SpdyProxyClientSocket() {
62 const HttpResponseInfo
* SpdyProxyClientSocket::GetConnectResponseInfo() const {
63 return response_
.headers
? &response_
: NULL
;
66 HttpStream
* SpdyProxyClientSocket::CreateConnectResponseStream() {
67 DCHECK(response_stream_
.get());
68 return response_stream_
.release();
71 // Sends a SYN_STREAM frame to the proxy with a CONNECT request
72 // for the specified endpoint. Waits for the server to send back
73 // a SYN_REPLY frame. OK will be returned if the status is 200.
74 // ERR_TUNNEL_CONNECTION_FAILED will be returned for any other status.
75 // In any of these cases, Read() may be called to retrieve the HTTP
76 // response body. Any other return values should be considered fatal.
77 // TODO(rch): handle 407 proxy auth requested correctly, perhaps
78 // by creating a new stream for the subsequent request.
79 // TODO(rch): create a more appropriate error code to disambiguate
80 // the HTTPS Proxy tunnel failure from an HTTP Proxy tunnel failure.
81 int SpdyProxyClientSocket::Connect(CompletionCallback
* callback
) {
82 DCHECK(!read_callback_
);
83 if (next_state_
== STATE_OPEN
)
86 DCHECK_EQ(STATE_DISCONNECTED
, next_state_
);
87 next_state_
= STATE_GENERATE_AUTH_TOKEN
;
90 if (rv
== ERR_IO_PENDING
)
91 read_callback_
= callback
;
95 void SpdyProxyClientSocket::Disconnect() {
98 read_callback_
= NULL
;
100 write_buffer_len_
= 0;
101 write_bytes_outstanding_
= 0;
102 write_callback_
= NULL
;
104 next_state_
= STATE_DISCONNECTED
;
107 // This will cause OnClose to be invoked, which takes care of
108 // cleaning up all the internal state.
109 spdy_stream_
->Cancel();
112 bool SpdyProxyClientSocket::IsConnected() const {
113 return next_state_
== STATE_OPEN
|| next_state_
== STATE_CLOSED
;
116 bool SpdyProxyClientSocket::IsConnectedAndIdle() const {
117 return IsConnected() && spdy_stream_
.get() != NULL
&&
118 !spdy_stream_
->is_idle();
121 const BoundNetLog
& SpdyProxyClientSocket::NetLog() const {
125 void SpdyProxyClientSocket::SetSubresourceSpeculation() {
126 // TODO(rch): what should this implementation be?
129 void SpdyProxyClientSocket::SetOmniboxSpeculation() {
130 // TODO(rch): what should this implementation be?
133 bool SpdyProxyClientSocket::WasEverUsed() const {
134 return was_ever_used_
|| (spdy_stream_
&& spdy_stream_
->WasEverUsed());
137 bool SpdyProxyClientSocket::UsingTCPFastOpen() const {
141 int64
SpdyProxyClientSocket::NumBytesRead() const {
145 base::TimeDelta
SpdyProxyClientSocket::GetConnectTimeMicros() const {
146 return base::TimeDelta::FromMicroseconds(-1);
149 int SpdyProxyClientSocket::Read(IOBuffer
* buf
, int buf_len
,
150 CompletionCallback
* callback
) {
151 DCHECK(!read_callback_
);
152 DCHECK(!user_buffer_
);
154 if (next_state_
== STATE_DISCONNECTED
)
155 return ERR_SOCKET_NOT_CONNECTED
;
157 if (!spdy_stream_
&& read_buffer_
.empty()) {
158 if (eof_has_been_read_
)
159 return ERR_CONNECTION_CLOSED
;
160 eof_has_been_read_
= true;
164 DCHECK(next_state_
== STATE_OPEN
|| next_state_
== STATE_CLOSED
);
166 user_buffer_
= new DrainableIOBuffer(buf
, buf_len
);
167 int result
= PopulateUserReadBuffer();
170 read_callback_
= callback
;
171 return ERR_IO_PENDING
;
177 int SpdyProxyClientSocket::PopulateUserReadBuffer() {
179 return ERR_IO_PENDING
;
181 while (!read_buffer_
.empty() && user_buffer_
->BytesRemaining() > 0) {
182 scoped_refptr
<DrainableIOBuffer
> data
= read_buffer_
.front();
183 const int bytes_to_copy
= std::min(user_buffer_
->BytesRemaining(),
184 data
->BytesRemaining());
185 memcpy(user_buffer_
->data(), data
->data(), bytes_to_copy
);
186 user_buffer_
->DidConsume(bytes_to_copy
);
187 if (data
->BytesRemaining() == bytes_to_copy
) {
188 // Consumed all data from this buffer
189 read_buffer_
.pop_front();
191 data
->DidConsume(bytes_to_copy
);
195 return user_buffer_
->BytesConsumed();
198 int SpdyProxyClientSocket::Write(IOBuffer
* buf
, int buf_len
,
199 CompletionCallback
* callback
) {
200 DCHECK(!write_callback_
);
201 if (next_state_
== STATE_DISCONNECTED
)
202 return ERR_SOCKET_NOT_CONNECTED
;
205 return ERR_CONNECTION_CLOSED
;
207 write_bytes_outstanding_
= buf_len
;
208 if (buf_len
<= kMaxSpdyFrameChunkSize
) {
209 int rv
= spdy_stream_
->WriteStreamData(buf
, buf_len
, spdy::DATA_FLAG_NONE
);
210 if (rv
== ERR_IO_PENDING
) {
211 write_callback_
= callback
;
212 write_buffer_len_
= buf_len
;
217 // Since a SPDY Data frame can only include kMaxSpdyFrameChunkSize bytes
218 // we need to send multiple data frames
219 for (int i
= 0; i
< buf_len
; i
+= kMaxSpdyFrameChunkSize
) {
220 int len
= std::min(kMaxSpdyFrameChunkSize
, buf_len
- i
);
221 scoped_refptr
<DrainableIOBuffer
> iobuf(new DrainableIOBuffer(buf
, i
+ len
));
223 int rv
= spdy_stream_
->WriteStreamData(iobuf
, len
, spdy::DATA_FLAG_NONE
);
225 write_bytes_outstanding_
-= rv
;
226 } else if (rv
!= ERR_IO_PENDING
) {
230 if (write_bytes_outstanding_
> 0) {
231 write_callback_
= callback
;
232 write_buffer_len_
= buf_len
;
233 return ERR_IO_PENDING
;
239 bool SpdyProxyClientSocket::SetReceiveBufferSize(int32 size
) {
240 // Since this StreamSocket sits on top of a shared SpdySession, it
241 // is not safe for callers to set change this underlying socket.
245 bool SpdyProxyClientSocket::SetSendBufferSize(int32 size
) {
246 // Since this StreamSocket sits on top of a shared SpdySession, it
247 // is not safe for callers to set change this underlying socket.
251 int SpdyProxyClientSocket::GetPeerAddress(AddressList
* address
) const {
253 return ERR_SOCKET_NOT_CONNECTED
;
254 return spdy_stream_
->GetPeerAddress(address
);
257 int SpdyProxyClientSocket::GetLocalAddress(IPEndPoint
* address
) const {
259 return ERR_SOCKET_NOT_CONNECTED
;
260 return spdy_stream_
->GetLocalAddress(address
);
263 void SpdyProxyClientSocket::OnIOComplete(int result
) {
264 DCHECK_NE(STATE_DISCONNECTED
, next_state_
);
265 int rv
= DoLoop(result
);
266 if (rv
!= ERR_IO_PENDING
) {
267 CompletionCallback
* c
= read_callback_
;
268 read_callback_
= NULL
;
273 int SpdyProxyClientSocket::DoLoop(int last_io_result
) {
274 DCHECK_NE(next_state_
, STATE_DISCONNECTED
);
275 int rv
= last_io_result
;
277 State state
= next_state_
;
278 next_state_
= STATE_DISCONNECTED
;
280 case STATE_GENERATE_AUTH_TOKEN
:
282 rv
= DoGenerateAuthToken();
284 case STATE_GENERATE_AUTH_TOKEN_COMPLETE
:
285 rv
= DoGenerateAuthTokenComplete(rv
);
287 case STATE_SEND_REQUEST
:
290 NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST
, NULL
);
291 rv
= DoSendRequest();
293 case STATE_SEND_REQUEST_COMPLETE
:
294 net_log_
.EndEventWithNetErrorCode(
295 NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_SEND_REQUEST
, rv
);
296 rv
= DoSendRequestComplete(rv
);
298 case STATE_READ_REPLY_COMPLETE
:
299 rv
= DoReadReplyComplete(rv
);
300 net_log_
.EndEventWithNetErrorCode(
301 NetLog::TYPE_HTTP_TRANSACTION_TUNNEL_READ_HEADERS
, rv
);
304 NOTREACHED() << "bad state";
308 } while (rv
!= ERR_IO_PENDING
&& next_state_
!= STATE_DISCONNECTED
&&
309 next_state_
!= STATE_OPEN
);
313 int SpdyProxyClientSocket::DoGenerateAuthToken() {
314 next_state_
= STATE_GENERATE_AUTH_TOKEN_COMPLETE
;
315 return auth_
->MaybeGenerateAuthToken(&request_
, &io_callback_
, net_log_
);
318 int SpdyProxyClientSocket::DoGenerateAuthTokenComplete(int result
) {
319 DCHECK_NE(ERR_IO_PENDING
, result
);
321 next_state_
= STATE_SEND_REQUEST
;
325 int SpdyProxyClientSocket::DoSendRequest() {
326 next_state_
= STATE_SEND_REQUEST_COMPLETE
;
328 // Add Proxy-Authentication header if necessary.
329 HttpRequestHeaders authorization_headers
;
330 if (auth_
->HaveAuth()) {
331 auth_
->AddAuthorizationHeader(&authorization_headers
);
334 std::string request_line
;
335 HttpRequestHeaders request_headers
;
336 BuildTunnelRequest(request_
, authorization_headers
, endpoint_
, &request_line
,
338 if (net_log_
.IsLoggingAllEvents()) {
340 NetLog::TYPE_HTTP_TRANSACTION_SEND_TUNNEL_HEADERS
,
341 make_scoped_refptr(new NetLogHttpRequestParameter(
342 request_line
, request_headers
)));
345 request_
.extra_headers
.MergeFrom(request_headers
);
346 linked_ptr
<spdy::SpdyHeaderBlock
> headers(new spdy::SpdyHeaderBlock());
347 CreateSpdyHeadersFromHttpRequest(request_
, request_headers
, headers
.get(),
349 // Reset the URL to be the endpoint of the connection
350 (*headers
)["url"] = endpoint_
.ToString();
351 headers
->erase("scheme");
352 spdy_stream_
->set_spdy_headers(headers
);
354 return spdy_stream_
->SendRequest(true);
357 int SpdyProxyClientSocket::DoSendRequestComplete(int result
) {
361 // Wait for SYN_REPLY frame from the server
362 next_state_
= STATE_READ_REPLY_COMPLETE
;
363 return ERR_IO_PENDING
;
366 int SpdyProxyClientSocket::DoReadReplyComplete(int result
) {
367 // We enter this method directly from DoSendRequestComplete, since
368 // we are notified by a callback when the SYN_REPLY frame arrives
373 // Require the "HTTP/1.x" status line for SSL CONNECT.
374 if (response_
.headers
->GetParsedHttpVersion() < HttpVersion(1, 0))
375 return ERR_TUNNEL_CONNECTION_FAILED
;
377 next_state_
= STATE_OPEN
;
378 if (net_log_
.IsLoggingAllEvents()) {
380 NetLog::TYPE_HTTP_TRANSACTION_READ_TUNNEL_RESPONSE_HEADERS
,
381 make_scoped_refptr(new NetLogHttpResponseParameter(response_
.headers
)));
384 if (response_
.headers
->response_code() == 200) {
386 } else if (response_
.headers
->response_code() == 407) {
387 return ERR_TUNNEL_CONNECTION_FAILED
;
389 // Immediately hand off our SpdyStream to a newly created SpdyHttpStream
390 // so that any subsequent SpdyFrames are processed in the context of
391 // the HttpStream, not the socket.
392 DCHECK(spdy_stream_
);
393 SpdyStream
* stream
= spdy_stream_
;
395 response_stream_
.reset(new SpdyHttpStream(NULL
, false));
396 response_stream_
->InitializeWithExistingStream(stream
);
397 next_state_
= STATE_DISCONNECTED
;
398 return ERR_HTTPS_PROXY_TUNNEL_RESPONSE
;
402 // SpdyStream::Delegate methods:
403 // Called when SYN frame has been sent.
404 // Returns true if no more data to be sent after SYN frame.
405 bool SpdyProxyClientSocket::OnSendHeadersComplete(int status
) {
406 DCHECK_EQ(next_state_
, STATE_SEND_REQUEST_COMPLETE
);
408 OnIOComplete(status
);
410 // We return true here so that we send |spdy_stream_| into
411 // STATE_OPEN (ala WebSockets).
415 int SpdyProxyClientSocket::OnSendBody() {
416 // Because we use |spdy_stream_| via STATE_OPEN (ala WebSockets)
417 // OnSendBody() should never be called.
419 return ERR_UNEXPECTED
;
422 int SpdyProxyClientSocket::OnSendBodyComplete(int /*status*/, bool* /*eof*/) {
423 // Because we use |spdy_stream_| via STATE_OPEN (ala WebSockets)
424 // OnSendBodyComplete() should never be called.
426 return ERR_UNEXPECTED
;
429 int SpdyProxyClientSocket::OnResponseReceived(
430 const spdy::SpdyHeaderBlock
& response
,
431 base::Time response_time
,
433 // If we've already received the reply, existing headers are too late.
434 // TODO(mbelshe): figure out a way to make HEADERS frames useful after the
436 if (next_state_
!= STATE_READ_REPLY_COMPLETE
)
440 int rv
= SpdyHeadersToHttpResponse(response
, &response_
);
441 if (rv
== ERR_INCOMPLETE_SPDY_HEADERS
)
442 return rv
; // More headers are coming.
444 OnIOComplete(status
);
448 // Called when data is received.
449 void SpdyProxyClientSocket::OnDataReceived(const char* data
, int length
) {
451 // Save the received data.
452 scoped_refptr
<IOBuffer
> io_buffer(new IOBuffer(length
));
453 memcpy(io_buffer
->data(), data
, length
);
454 read_buffer_
.push_back(
455 make_scoped_refptr(new DrainableIOBuffer(io_buffer
, length
)));
458 if (read_callback_
) {
459 int rv
= PopulateUserReadBuffer();
460 CompletionCallback
* c
= read_callback_
;
461 read_callback_
= NULL
;
467 void SpdyProxyClientSocket::OnDataSent(int length
) {
468 DCHECK(write_callback_
);
470 write_bytes_outstanding_
-= length
;
472 DCHECK_GE(write_bytes_outstanding_
, 0);
474 if (write_bytes_outstanding_
== 0) {
475 int rv
= write_buffer_len_
;
476 write_buffer_len_
= 0;
477 write_bytes_outstanding_
= 0;
478 CompletionCallback
* c
= write_callback_
;
479 write_callback_
= NULL
;
484 void SpdyProxyClientSocket::OnClose(int status
) {
485 DCHECK(spdy_stream_
);
486 was_ever_used_
= spdy_stream_
->WasEverUsed();
489 bool connecting
= next_state_
!= STATE_DISCONNECTED
&&
490 next_state_
< STATE_OPEN
;
491 if (next_state_
== STATE_OPEN
)
492 next_state_
= STATE_CLOSED
;
494 next_state_
= STATE_DISCONNECTED
;
496 CompletionCallback
* write_callback
= write_callback_
;
497 write_callback_
= NULL
;
498 write_buffer_len_
= 0;
499 write_bytes_outstanding_
= 0;
501 // If we're in the middle of connecting, we need to make sure
502 // we invoke the connect callback.
504 DCHECK(read_callback_
);
505 CompletionCallback
* read_callback
= read_callback_
;
506 read_callback_
= NULL
;
507 read_callback
->Run(status
);
508 } else if (read_callback_
) {
509 // If we have a read_callback, the we need to make sure we call it back
510 OnDataReceived(NULL
, 0);
513 write_callback
->Run(ERR_CONNECTION_CLOSED
);
516 void SpdyProxyClientSocket::set_chunk_callback(ChunkCallback
* /*callback*/) {