OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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. | |
4 | |
5 #include "net/websockets/websocket_job.h" | |
6 | |
7 #include <algorithm> | |
8 | |
9 #include "base/bind.h" | |
10 #include "base/lazy_instance.h" | |
11 #include "net/base/io_buffer.h" | |
12 #include "net/base/net_errors.h" | |
13 #include "net/base/net_log.h" | |
14 #include "net/cookies/cookie_store.h" | |
15 #include "net/http/http_network_session.h" | |
16 #include "net/http/http_transaction_factory.h" | |
17 #include "net/http/http_util.h" | |
18 #include "net/spdy/spdy_session.h" | |
19 #include "net/spdy/spdy_session_pool.h" | |
20 #include "net/url_request/url_request_context.h" | |
21 #include "net/websockets/websocket_handshake_handler.h" | |
22 #include "net/websockets/websocket_net_log_params.h" | |
23 #include "net/websockets/websocket_throttle.h" | |
24 #include "url/gurl.h" | |
25 | |
26 static const int kMaxPendingSendAllowed = 32768; // 32 kilobytes. | |
27 | |
28 namespace { | |
29 | |
30 // lower-case header names. | |
31 const char* const kCookieHeaders[] = { | |
32 "cookie", "cookie2" | |
33 }; | |
34 const char* const kSetCookieHeaders[] = { | |
35 "set-cookie", "set-cookie2" | |
36 }; | |
37 | |
38 net::SocketStreamJob* WebSocketJobFactory( | |
39 const GURL& url, net::SocketStream::Delegate* delegate, | |
40 net::URLRequestContext* context, net::CookieStore* cookie_store) { | |
41 net::WebSocketJob* job = new net::WebSocketJob(delegate); | |
42 job->InitSocketStream(new net::SocketStream(url, job, context, cookie_store)); | |
43 return job; | |
44 } | |
45 | |
46 class WebSocketJobInitSingleton { | |
47 private: | |
48 friend struct base::DefaultLazyInstanceTraits<WebSocketJobInitSingleton>; | |
49 WebSocketJobInitSingleton() { | |
50 net::SocketStreamJob::RegisterProtocolFactory("ws", WebSocketJobFactory); | |
51 net::SocketStreamJob::RegisterProtocolFactory("wss", WebSocketJobFactory); | |
52 } | |
53 }; | |
54 | |
55 static base::LazyInstance<WebSocketJobInitSingleton> g_websocket_job_init = | |
56 LAZY_INSTANCE_INITIALIZER; | |
57 | |
58 } // anonymous namespace | |
59 | |
60 namespace net { | |
61 | |
62 // static | |
63 void WebSocketJob::EnsureInit() { | |
64 g_websocket_job_init.Get(); | |
65 } | |
66 | |
67 WebSocketJob::WebSocketJob(SocketStream::Delegate* delegate) | |
68 : delegate_(delegate), | |
69 state_(INITIALIZED), | |
70 waiting_(false), | |
71 handshake_request_(new WebSocketHandshakeRequestHandler), | |
72 handshake_response_(new WebSocketHandshakeResponseHandler), | |
73 started_to_send_handshake_request_(false), | |
74 handshake_request_sent_(0), | |
75 response_cookies_save_index_(0), | |
76 spdy_protocol_version_(0), | |
77 save_next_cookie_running_(false), | |
78 callback_pending_(false), | |
79 weak_ptr_factory_(this), | |
80 weak_ptr_factory_for_send_pending_(this) { | |
81 } | |
82 | |
83 WebSocketJob::~WebSocketJob() { | |
84 DCHECK_EQ(CLOSED, state_); | |
85 DCHECK(!delegate_); | |
86 DCHECK(!socket_.get()); | |
87 } | |
88 | |
89 void WebSocketJob::Connect() { | |
90 DCHECK(socket_.get()); | |
91 DCHECK_EQ(state_, INITIALIZED); | |
92 state_ = CONNECTING; | |
93 socket_->Connect(); | |
94 } | |
95 | |
96 bool WebSocketJob::SendData(const char* data, int len) { | |
97 switch (state_) { | |
98 case INITIALIZED: | |
99 return false; | |
100 | |
101 case CONNECTING: | |
102 return SendHandshakeRequest(data, len); | |
103 | |
104 case OPEN: | |
105 { | |
106 scoped_refptr<IOBufferWithSize> buffer = new IOBufferWithSize(len); | |
107 memcpy(buffer->data(), data, len); | |
108 if (current_send_buffer_.get() || !send_buffer_queue_.empty()) { | |
109 send_buffer_queue_.push_back(buffer); | |
110 return true; | |
111 } | |
112 current_send_buffer_ = new DrainableIOBuffer(buffer.get(), len); | |
113 return SendDataInternal(current_send_buffer_->data(), | |
114 current_send_buffer_->BytesRemaining()); | |
115 } | |
116 | |
117 case CLOSING: | |
118 case CLOSED: | |
119 return false; | |
120 } | |
121 return false; | |
122 } | |
123 | |
124 void WebSocketJob::Close() { | |
125 if (state_ == CLOSED) | |
126 return; | |
127 | |
128 state_ = CLOSING; | |
129 if (current_send_buffer_.get()) { | |
130 // Will close in SendPending. | |
131 return; | |
132 } | |
133 state_ = CLOSED; | |
134 CloseInternal(); | |
135 } | |
136 | |
137 void WebSocketJob::RestartWithAuth(const AuthCredentials& credentials) { | |
138 state_ = CONNECTING; | |
139 socket_->RestartWithAuth(credentials); | |
140 } | |
141 | |
142 void WebSocketJob::DetachDelegate() { | |
143 state_ = CLOSED; | |
144 WebSocketThrottle::GetInstance()->RemoveFromQueue(this); | |
145 | |
146 scoped_refptr<WebSocketJob> protect(this); | |
147 weak_ptr_factory_.InvalidateWeakPtrs(); | |
148 weak_ptr_factory_for_send_pending_.InvalidateWeakPtrs(); | |
149 | |
150 delegate_ = NULL; | |
151 if (socket_.get()) | |
152 socket_->DetachDelegate(); | |
153 socket_ = NULL; | |
154 if (!callback_.is_null()) { | |
155 waiting_ = false; | |
156 callback_.Reset(); | |
157 Release(); // Balanced with OnStartOpenConnection(). | |
158 } | |
159 } | |
160 | |
161 int WebSocketJob::OnStartOpenConnection( | |
162 SocketStream* socket, const CompletionCallback& callback) { | |
163 DCHECK(callback_.is_null()); | |
164 state_ = CONNECTING; | |
165 | |
166 addresses_ = socket->address_list(); | |
167 if (!WebSocketThrottle::GetInstance()->PutInQueue(this)) { | |
168 return ERR_WS_THROTTLE_QUEUE_TOO_LARGE; | |
169 } | |
170 | |
171 if (delegate_) { | |
172 int result = delegate_->OnStartOpenConnection(socket, callback); | |
173 DCHECK_EQ(OK, result); | |
174 } | |
175 if (waiting_) { | |
176 // PutInQueue() may set |waiting_| true for throttling. In this case, | |
177 // Wakeup() will be called later. | |
178 callback_ = callback; | |
179 AddRef(); // Balanced when callback_ is cleared. | |
180 return ERR_IO_PENDING; | |
181 } | |
182 return TrySpdyStream(); | |
183 } | |
184 | |
185 void WebSocketJob::OnConnected( | |
186 SocketStream* socket, int max_pending_send_allowed) { | |
187 if (state_ == CLOSED) | |
188 return; | |
189 DCHECK_EQ(CONNECTING, state_); | |
190 if (delegate_) | |
191 delegate_->OnConnected(socket, max_pending_send_allowed); | |
192 } | |
193 | |
194 void WebSocketJob::OnSentData(SocketStream* socket, int amount_sent) { | |
195 DCHECK_NE(INITIALIZED, state_); | |
196 DCHECK_GT(amount_sent, 0); | |
197 if (state_ == CLOSED) | |
198 return; | |
199 if (state_ == CONNECTING) { | |
200 OnSentHandshakeRequest(socket, amount_sent); | |
201 return; | |
202 } | |
203 if (delegate_) { | |
204 DCHECK(state_ == OPEN || state_ == CLOSING); | |
205 if (!current_send_buffer_.get()) { | |
206 VLOG(1) | |
207 << "OnSentData current_send_buffer=NULL amount_sent=" << amount_sent; | |
208 return; | |
209 } | |
210 current_send_buffer_->DidConsume(amount_sent); | |
211 if (current_send_buffer_->BytesRemaining() > 0) | |
212 return; | |
213 | |
214 // We need to report amount_sent of original buffer size, instead of | |
215 // amount sent to |socket|. | |
216 amount_sent = current_send_buffer_->size(); | |
217 DCHECK_GT(amount_sent, 0); | |
218 current_send_buffer_ = NULL; | |
219 if (!weak_ptr_factory_for_send_pending_.HasWeakPtrs()) { | |
220 base::MessageLoopForIO::current()->PostTask( | |
221 FROM_HERE, | |
222 base::Bind(&WebSocketJob::SendPending, | |
223 weak_ptr_factory_for_send_pending_.GetWeakPtr())); | |
224 } | |
225 delegate_->OnSentData(socket, amount_sent); | |
226 } | |
227 } | |
228 | |
229 void WebSocketJob::OnReceivedData( | |
230 SocketStream* socket, const char* data, int len) { | |
231 DCHECK_NE(INITIALIZED, state_); | |
232 if (state_ == CLOSED) | |
233 return; | |
234 if (state_ == CONNECTING) { | |
235 OnReceivedHandshakeResponse(socket, data, len); | |
236 return; | |
237 } | |
238 DCHECK(state_ == OPEN || state_ == CLOSING); | |
239 if (delegate_ && len > 0) | |
240 delegate_->OnReceivedData(socket, data, len); | |
241 } | |
242 | |
243 void WebSocketJob::OnClose(SocketStream* socket) { | |
244 state_ = CLOSED; | |
245 WebSocketThrottle::GetInstance()->RemoveFromQueue(this); | |
246 | |
247 scoped_refptr<WebSocketJob> protect(this); | |
248 weak_ptr_factory_.InvalidateWeakPtrs(); | |
249 | |
250 SocketStream::Delegate* delegate = delegate_; | |
251 delegate_ = NULL; | |
252 socket_ = NULL; | |
253 if (!callback_.is_null()) { | |
254 waiting_ = false; | |
255 callback_.Reset(); | |
256 Release(); // Balanced with OnStartOpenConnection(). | |
257 } | |
258 if (delegate) | |
259 delegate->OnClose(socket); | |
260 } | |
261 | |
262 void WebSocketJob::OnAuthRequired( | |
263 SocketStream* socket, AuthChallengeInfo* auth_info) { | |
264 if (delegate_) | |
265 delegate_->OnAuthRequired(socket, auth_info); | |
266 } | |
267 | |
268 void WebSocketJob::OnSSLCertificateError( | |
269 SocketStream* socket, const SSLInfo& ssl_info, bool fatal) { | |
270 if (delegate_) | |
271 delegate_->OnSSLCertificateError(socket, ssl_info, fatal); | |
272 } | |
273 | |
274 void WebSocketJob::OnError(const SocketStream* socket, int error) { | |
275 if (delegate_ && error != ERR_PROTOCOL_SWITCHED) | |
276 delegate_->OnError(socket, error); | |
277 } | |
278 | |
279 void WebSocketJob::OnCreatedSpdyStream(int result) { | |
280 DCHECK(spdy_websocket_stream_.get()); | |
281 DCHECK(socket_.get()); | |
282 DCHECK_NE(ERR_IO_PENDING, result); | |
283 | |
284 if (state_ == CLOSED) { | |
285 result = ERR_ABORTED; | |
286 } else if (result == OK) { | |
287 state_ = CONNECTING; | |
288 result = ERR_PROTOCOL_SWITCHED; | |
289 } else { | |
290 spdy_websocket_stream_.reset(); | |
291 } | |
292 | |
293 CompleteIO(result); | |
294 } | |
295 | |
296 void WebSocketJob::OnSentSpdyHeaders() { | |
297 DCHECK_NE(INITIALIZED, state_); | |
298 if (state_ != CONNECTING) | |
299 return; | |
300 size_t original_length = handshake_request_->original_length(); | |
301 handshake_request_.reset(); | |
302 if (delegate_) | |
303 delegate_->OnSentData(socket_.get(), original_length); | |
304 } | |
305 | |
306 void WebSocketJob::OnSpdyResponseHeadersUpdated( | |
307 const SpdyHeaderBlock& response_headers) { | |
308 DCHECK_NE(INITIALIZED, state_); | |
309 if (state_ != CONNECTING) | |
310 return; | |
311 // TODO(toyoshim): Fallback to non-spdy connection? | |
312 handshake_response_->ParseResponseHeaderBlock(response_headers, | |
313 challenge_, | |
314 spdy_protocol_version_); | |
315 | |
316 SaveCookiesAndNotifyHeadersComplete(); | |
317 } | |
318 | |
319 void WebSocketJob::OnSentSpdyData(size_t bytes_sent) { | |
320 DCHECK_NE(INITIALIZED, state_); | |
321 DCHECK_NE(CONNECTING, state_); | |
322 if (state_ == CLOSED) | |
323 return; | |
324 if (!spdy_websocket_stream_.get()) | |
325 return; | |
326 OnSentData(socket_.get(), static_cast<int>(bytes_sent)); | |
327 } | |
328 | |
329 void WebSocketJob::OnReceivedSpdyData(scoped_ptr<SpdyBuffer> buffer) { | |
330 DCHECK_NE(INITIALIZED, state_); | |
331 DCHECK_NE(CONNECTING, state_); | |
332 if (state_ == CLOSED) | |
333 return; | |
334 if (!spdy_websocket_stream_.get()) | |
335 return; | |
336 if (buffer) { | |
337 OnReceivedData( | |
338 socket_.get(), buffer->GetRemainingData(), buffer->GetRemainingSize()); | |
339 } else { | |
340 OnReceivedData(socket_.get(), NULL, 0); | |
341 } | |
342 } | |
343 | |
344 void WebSocketJob::OnCloseSpdyStream() { | |
345 spdy_websocket_stream_.reset(); | |
346 OnClose(socket_.get()); | |
347 } | |
348 | |
349 bool WebSocketJob::SendHandshakeRequest(const char* data, int len) { | |
350 DCHECK_EQ(state_, CONNECTING); | |
351 if (started_to_send_handshake_request_) | |
352 return false; | |
353 if (!handshake_request_->ParseRequest(data, len)) | |
354 return false; | |
355 | |
356 AddCookieHeaderAndSend(); | |
357 return true; | |
358 } | |
359 | |
360 void WebSocketJob::AddCookieHeaderAndSend() { | |
361 bool allow = true; | |
362 if (delegate_ && !delegate_->CanGetCookies(socket_.get(), GetURLForCookies())) | |
363 allow = false; | |
364 | |
365 if (socket_.get() && delegate_ && state_ == CONNECTING) { | |
366 handshake_request_->RemoveHeaders(kCookieHeaders, | |
367 arraysize(kCookieHeaders)); | |
368 if (allow && socket_->cookie_store()) { | |
369 // Add cookies, including HttpOnly cookies. | |
370 CookieOptions cookie_options; | |
371 cookie_options.set_include_httponly(); | |
372 socket_->cookie_store()->GetCookiesWithOptionsAsync( | |
373 GetURLForCookies(), cookie_options, | |
374 base::Bind(&WebSocketJob::LoadCookieCallback, | |
375 weak_ptr_factory_.GetWeakPtr())); | |
376 } else { | |
377 DoSendData(); | |
378 } | |
379 } | |
380 } | |
381 | |
382 void WebSocketJob::LoadCookieCallback(const std::string& cookie) { | |
383 if (!cookie.empty()) | |
384 // TODO(tyoshino): Sending cookie means that connection doesn't need | |
385 // PRIVACY_MODE_ENABLED as cookies may be server-bound and channel id | |
386 // wouldn't negatively affect privacy anyway. Need to restart connection | |
387 // or refactor to determine cookie status prior to connecting. | |
388 handshake_request_->AppendHeaderIfMissing("Cookie", cookie); | |
389 DoSendData(); | |
390 } | |
391 | |
392 void WebSocketJob::DoSendData() { | |
393 if (spdy_websocket_stream_.get()) { | |
394 scoped_ptr<SpdyHeaderBlock> headers(new SpdyHeaderBlock); | |
395 handshake_request_->GetRequestHeaderBlock( | |
396 socket_->url(), headers.get(), &challenge_, spdy_protocol_version_); | |
397 spdy_websocket_stream_->SendRequest(headers.Pass()); | |
398 } else { | |
399 const std::string& handshake_request = | |
400 handshake_request_->GetRawRequest(); | |
401 handshake_request_sent_ = 0; | |
402 socket_->net_log()->AddEvent( | |
403 NetLog::TYPE_WEB_SOCKET_SEND_REQUEST_HEADERS, | |
404 base::Bind(&NetLogWebSocketHandshakeCallback, &handshake_request)); | |
405 socket_->SendData(handshake_request.data(), | |
406 handshake_request.size()); | |
407 } | |
408 // Just buffered in |handshake_request_|. | |
409 started_to_send_handshake_request_ = true; | |
410 } | |
411 | |
412 void WebSocketJob::OnSentHandshakeRequest( | |
413 SocketStream* socket, int amount_sent) { | |
414 DCHECK_EQ(state_, CONNECTING); | |
415 handshake_request_sent_ += amount_sent; | |
416 DCHECK_LE(handshake_request_sent_, handshake_request_->raw_length()); | |
417 if (handshake_request_sent_ >= handshake_request_->raw_length()) { | |
418 // handshake request has been sent. | |
419 // notify original size of handshake request to delegate. | |
420 // Reset the handshake_request_ first in case this object is deleted by the | |
421 // delegate. | |
422 size_t original_length = handshake_request_->original_length(); | |
423 handshake_request_.reset(); | |
424 if (delegate_) | |
425 delegate_->OnSentData(socket, original_length); | |
426 } | |
427 } | |
428 | |
429 void WebSocketJob::OnReceivedHandshakeResponse( | |
430 SocketStream* socket, const char* data, int len) { | |
431 DCHECK_EQ(state_, CONNECTING); | |
432 if (handshake_response_->HasResponse()) { | |
433 // If we already has handshake response, received data should be frame | |
434 // data, not handshake message. | |
435 received_data_after_handshake_.insert( | |
436 received_data_after_handshake_.end(), data, data + len); | |
437 return; | |
438 } | |
439 | |
440 size_t response_length = handshake_response_->ParseRawResponse(data, len); | |
441 if (!handshake_response_->HasResponse()) { | |
442 // not yet. we need more data. | |
443 return; | |
444 } | |
445 // handshake message is completed. | |
446 std::string raw_response = handshake_response_->GetRawResponse(); | |
447 socket_->net_log()->AddEvent( | |
448 NetLog::TYPE_WEB_SOCKET_READ_RESPONSE_HEADERS, | |
449 base::Bind(&NetLogWebSocketHandshakeCallback, &raw_response)); | |
450 if (len - response_length > 0) { | |
451 // If we received extra data, it should be frame data. | |
452 DCHECK(received_data_after_handshake_.empty()); | |
453 received_data_after_handshake_.assign(data + response_length, data + len); | |
454 } | |
455 SaveCookiesAndNotifyHeadersComplete(); | |
456 } | |
457 | |
458 void WebSocketJob::SaveCookiesAndNotifyHeadersComplete() { | |
459 // handshake message is completed. | |
460 DCHECK(handshake_response_->HasResponse()); | |
461 | |
462 // Extract cookies from the handshake response into a temporary vector. | |
463 response_cookies_.clear(); | |
464 response_cookies_save_index_ = 0; | |
465 | |
466 handshake_response_->GetHeaders( | |
467 kSetCookieHeaders, arraysize(kSetCookieHeaders), &response_cookies_); | |
468 | |
469 // Now, loop over the response cookies, and attempt to persist each. | |
470 SaveNextCookie(); | |
471 } | |
472 | |
473 void WebSocketJob::NotifyHeadersComplete() { | |
474 // Remove cookie headers, with malformed headers preserved. | |
475 // Actual handshake should be done in Blink. | |
476 handshake_response_->RemoveHeaders( | |
477 kSetCookieHeaders, arraysize(kSetCookieHeaders)); | |
478 std::string handshake_response = handshake_response_->GetResponse(); | |
479 handshake_response_.reset(); | |
480 std::vector<char> received_data(handshake_response.begin(), | |
481 handshake_response.end()); | |
482 received_data.insert(received_data.end(), | |
483 received_data_after_handshake_.begin(), | |
484 received_data_after_handshake_.end()); | |
485 received_data_after_handshake_.clear(); | |
486 | |
487 state_ = OPEN; | |
488 | |
489 DCHECK(!received_data.empty()); | |
490 if (delegate_) | |
491 delegate_->OnReceivedData( | |
492 socket_.get(), &received_data.front(), received_data.size()); | |
493 | |
494 WebSocketThrottle::GetInstance()->RemoveFromQueue(this); | |
495 } | |
496 | |
497 void WebSocketJob::SaveNextCookie() { | |
498 if (!socket_.get() || !delegate_ || state_ != CONNECTING) | |
499 return; | |
500 | |
501 callback_pending_ = false; | |
502 save_next_cookie_running_ = true; | |
503 | |
504 if (socket_->cookie_store()) { | |
505 GURL url_for_cookies = GetURLForCookies(); | |
506 | |
507 CookieOptions options; | |
508 options.set_include_httponly(); | |
509 | |
510 // Loop as long as SetCookieWithOptionsAsync completes synchronously. Since | |
511 // CookieMonster's asynchronous operation APIs queue the callback to run it | |
512 // on the thread where the API was called, there won't be race. I.e. unless | |
513 // the callback is run synchronously, it won't be run in parallel with this | |
514 // method. | |
515 while (!callback_pending_ && | |
516 response_cookies_save_index_ < response_cookies_.size()) { | |
517 std::string cookie = response_cookies_[response_cookies_save_index_]; | |
518 response_cookies_save_index_++; | |
519 | |
520 if (!delegate_->CanSetCookie( | |
521 socket_.get(), url_for_cookies, cookie, &options)) | |
522 continue; | |
523 | |
524 callback_pending_ = true; | |
525 socket_->cookie_store()->SetCookieWithOptionsAsync( | |
526 url_for_cookies, cookie, options, | |
527 base::Bind(&WebSocketJob::OnCookieSaved, | |
528 weak_ptr_factory_.GetWeakPtr())); | |
529 } | |
530 } | |
531 | |
532 save_next_cookie_running_ = false; | |
533 | |
534 if (callback_pending_) | |
535 return; | |
536 | |
537 response_cookies_.clear(); | |
538 response_cookies_save_index_ = 0; | |
539 | |
540 NotifyHeadersComplete(); | |
541 } | |
542 | |
543 void WebSocketJob::OnCookieSaved(bool cookie_status) { | |
544 // Tell the caller of SetCookieWithOptionsAsync() that this completion | |
545 // callback is invoked. | |
546 // - If the caller checks callback_pending earlier than this callback, the | |
547 // caller exits to let this method continue iteration. | |
548 // - Otherwise, the caller continues iteration. | |
549 callback_pending_ = false; | |
550 | |
551 // Resume SaveNextCookie if the caller of SetCookieWithOptionsAsync() exited | |
552 // the loop. Otherwise, return. | |
553 if (save_next_cookie_running_) | |
554 return; | |
555 | |
556 SaveNextCookie(); | |
557 } | |
558 | |
559 GURL WebSocketJob::GetURLForCookies() const { | |
560 GURL url = socket_->url(); | |
561 std::string scheme = socket_->is_secure() ? "https" : "http"; | |
562 url::Replacements<char> replacements; | |
563 replacements.SetScheme(scheme.c_str(), url::Component(0, scheme.length())); | |
564 return url.ReplaceComponents(replacements); | |
565 } | |
566 | |
567 const AddressList& WebSocketJob::address_list() const { | |
568 return addresses_; | |
569 } | |
570 | |
571 int WebSocketJob::TrySpdyStream() { | |
572 if (!socket_.get()) | |
573 return ERR_FAILED; | |
574 | |
575 // Check if we have a SPDY session available. | |
576 HttpTransactionFactory* factory = | |
577 socket_->context()->http_transaction_factory(); | |
578 if (!factory) | |
579 return OK; | |
580 scoped_refptr<HttpNetworkSession> session = factory->GetSession(); | |
581 if (!session.get() || !session->params().enable_websocket_over_spdy) | |
582 return OK; | |
583 SpdySessionPool* spdy_pool = session->spdy_session_pool(); | |
584 PrivacyMode privacy_mode = socket_->privacy_mode(); | |
585 const SpdySessionKey key(HostPortPair::FromURL(socket_->url()), | |
586 socket_->proxy_server(), privacy_mode); | |
587 // Forbid wss downgrade to SPDY without SSL. | |
588 // TODO(toyoshim): Does it realize the same policy with HTTP? | |
589 base::WeakPtr<SpdySession> spdy_session = | |
590 spdy_pool->FindAvailableSession(key, *socket_->net_log()); | |
591 if (!spdy_session) | |
592 return OK; | |
593 | |
594 SSLInfo ssl_info; | |
595 bool was_npn_negotiated; | |
596 NextProto protocol_negotiated = kProtoUnknown; | |
597 bool use_ssl = spdy_session->GetSSLInfo( | |
598 &ssl_info, &was_npn_negotiated, &protocol_negotiated); | |
599 if (socket_->is_secure() && !use_ssl) | |
600 return OK; | |
601 | |
602 // Create SpdyWebSocketStream. | |
603 spdy_protocol_version_ = spdy_session->GetProtocolVersion(); | |
604 spdy_websocket_stream_.reset(new SpdyWebSocketStream(spdy_session, this)); | |
605 | |
606 int result = spdy_websocket_stream_->InitializeStream( | |
607 socket_->url(), MEDIUM, *socket_->net_log()); | |
608 if (result == OK) { | |
609 OnConnected(socket_.get(), kMaxPendingSendAllowed); | |
610 return ERR_PROTOCOL_SWITCHED; | |
611 } | |
612 if (result != ERR_IO_PENDING) { | |
613 spdy_websocket_stream_.reset(); | |
614 return OK; | |
615 } | |
616 | |
617 return ERR_IO_PENDING; | |
618 } | |
619 | |
620 void WebSocketJob::SetWaiting() { | |
621 waiting_ = true; | |
622 } | |
623 | |
624 bool WebSocketJob::IsWaiting() const { | |
625 return waiting_; | |
626 } | |
627 | |
628 void WebSocketJob::Wakeup() { | |
629 if (!waiting_) | |
630 return; | |
631 waiting_ = false; | |
632 DCHECK(!callback_.is_null()); | |
633 base::MessageLoopForIO::current()->PostTask( | |
634 FROM_HERE, | |
635 base::Bind(&WebSocketJob::RetryPendingIO, | |
636 weak_ptr_factory_.GetWeakPtr())); | |
637 } | |
638 | |
639 void WebSocketJob::RetryPendingIO() { | |
640 int result = TrySpdyStream(); | |
641 | |
642 // In the case of ERR_IO_PENDING, CompleteIO() will be called from | |
643 // OnCreatedSpdyStream(). | |
644 if (result != ERR_IO_PENDING) | |
645 CompleteIO(result); | |
646 } | |
647 | |
648 void WebSocketJob::CompleteIO(int result) { | |
649 // |callback_| may be null if OnClose() or DetachDelegate() was called. | |
650 if (!callback_.is_null()) { | |
651 CompletionCallback callback = callback_; | |
652 callback_.Reset(); | |
653 callback.Run(result); | |
654 Release(); // Balanced with OnStartOpenConnection(). | |
655 } | |
656 } | |
657 | |
658 bool WebSocketJob::SendDataInternal(const char* data, int length) { | |
659 if (spdy_websocket_stream_.get()) | |
660 return ERR_IO_PENDING == spdy_websocket_stream_->SendData(data, length); | |
661 if (socket_.get()) | |
662 return socket_->SendData(data, length); | |
663 return false; | |
664 } | |
665 | |
666 void WebSocketJob::CloseInternal() { | |
667 if (spdy_websocket_stream_.get()) | |
668 spdy_websocket_stream_->Close(); | |
669 if (socket_.get()) | |
670 socket_->Close(); | |
671 } | |
672 | |
673 void WebSocketJob::SendPending() { | |
674 if (current_send_buffer_.get()) | |
675 return; | |
676 | |
677 // Current buffer has been sent. Try next if any. | |
678 if (send_buffer_queue_.empty()) { | |
679 // No more data to send. | |
680 if (state_ == CLOSING) | |
681 CloseInternal(); | |
682 return; | |
683 } | |
684 | |
685 scoped_refptr<IOBufferWithSize> next_buffer = send_buffer_queue_.front(); | |
686 send_buffer_queue_.pop_front(); | |
687 current_send_buffer_ = | |
688 new DrainableIOBuffer(next_buffer.get(), next_buffer->size()); | |
689 SendDataInternal(current_send_buffer_->data(), | |
690 current_send_buffer_->BytesRemaining()); | |
691 } | |
692 | |
693 } // namespace net | |
OLD | NEW |