| // Copyright (c) 2016 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_stream_factory_job_controller.h" | 
 |  | 
 | #include <string> | 
 | #include <utility> | 
 |  | 
 | #include "base/metrics/histogram_functions.h" | 
 | #include "base/metrics/histogram_macros.h" | 
 | #include "base/strings/string_number_conversions.h" | 
 | #include "base/strings/string_util.h" | 
 | #include "base/threading/thread_task_runner_handle.h" | 
 | #include "base/trace_event/memory_usage_estimator.h" | 
 | #include "base/values.h" | 
 | #include "net/base/host_mapping_rules.h" | 
 | #include "net/http/bidirectional_stream_impl.h" | 
 | #include "net/http/transport_security_state.h" | 
 | #include "net/log/net_log.h" | 
 | #include "net/log/net_log_capture_mode.h" | 
 | #include "net/log/net_log_event_type.h" | 
 | #include "net/log/net_log_source.h" | 
 | #include "net/log/net_log_with_source.h" | 
 | #include "net/spdy/spdy_session.h" | 
 | #include "url/url_constants.h" | 
 |  | 
 | namespace net { | 
 |  | 
 | namespace { | 
 |  | 
 | #if defined(STARBOARD) | 
 | const int kDefaultQUICServerPort = 443; | 
 | #endif | 
 |  | 
 | // Returns parameters associated with the proxy resolution. | 
 | std::unique_ptr<base::Value> NetLogHttpStreamJobProxyServerResolved( | 
 |     const ProxyServer& proxy_server, | 
 |     NetLogCaptureMode /* capture_mode */) { | 
 |   std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); | 
 |  | 
 |   dict->SetString("proxy_server", proxy_server.is_valid() | 
 |                                       ? proxy_server.ToPacString() | 
 |                                       : std::string()); | 
 |   return std::move(dict); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | // The maximum time to wait for the alternate job to complete before resuming | 
 | // the main job. | 
 | const int kMaxDelayTimeForMainJobSecs = 3; | 
 |  | 
 | std::unique_ptr<base::Value> NetLogJobControllerCallback( | 
 |     const GURL* url, | 
 |     bool is_preconnect, | 
 |     NetLogCaptureMode /* capture_mode */) { | 
 |   auto dict = std::make_unique<base::DictionaryValue>(); | 
 |   dict->SetString("url", url->possibly_invalid_spec()); | 
 |   dict->SetBoolean("is_preconnect", is_preconnect); | 
 |   return std::move(dict); | 
 | } | 
 |  | 
 | HttpStreamFactory::JobController::JobController( | 
 |     HttpStreamFactory* factory, | 
 |     HttpStreamRequest::Delegate* delegate, | 
 |     HttpNetworkSession* session, | 
 |     JobFactory* job_factory, | 
 |     const HttpRequestInfo& request_info, | 
 |     bool is_preconnect, | 
 |     bool is_websocket, | 
 |     bool enable_ip_based_pooling, | 
 |     bool enable_alternative_services, | 
 |     const SSLConfig& server_ssl_config, | 
 |     const SSLConfig& proxy_ssl_config) | 
 |     : factory_(factory), | 
 |       session_(session), | 
 |       job_factory_(job_factory), | 
 |       request_(nullptr), | 
 |       delegate_(delegate), | 
 |       is_preconnect_(is_preconnect), | 
 |       is_websocket_(is_websocket), | 
 |       enable_ip_based_pooling_(enable_ip_based_pooling), | 
 |       enable_alternative_services_(enable_alternative_services), | 
 |       main_job_net_error_(OK), | 
 |       alternative_job_net_error_(OK), | 
 |       alternative_job_failed_on_default_network_(false), | 
 |       job_bound_(false), | 
 |       main_job_is_blocked_(false), | 
 |       main_job_is_resumed_(false), | 
 |       bound_job_(nullptr), | 
 |       can_start_alternative_proxy_job_(true), | 
 |       next_state_(STATE_RESOLVE_PROXY), | 
 |       proxy_resolve_request_(nullptr), | 
 |       request_info_(request_info), | 
 |       server_ssl_config_(server_ssl_config), | 
 |       proxy_ssl_config_(proxy_ssl_config), | 
 |       num_streams_(0), | 
 |       priority_(IDLE), | 
 |       net_log_( | 
 |           NetLogWithSource::Make(session->net_log(), | 
 |                                  NetLogSourceType::HTTP_STREAM_JOB_CONTROLLER)), | 
 |       ptr_factory_(this) { | 
 |   DCHECK(factory); | 
 |   net_log_.BeginEvent(NetLogEventType::HTTP_STREAM_JOB_CONTROLLER, | 
 |                       base::Bind(&NetLogJobControllerCallback, | 
 |                                  &request_info.url, is_preconnect)); | 
 | } | 
 |  | 
 | HttpStreamFactory::JobController::~JobController() { | 
 |   main_job_.reset(); | 
 |   alternative_job_.reset(); | 
 |   bound_job_ = nullptr; | 
 |   if (proxy_resolve_request_) { | 
 |     DCHECK_EQ(STATE_RESOLVE_PROXY_COMPLETE, next_state_); | 
 |     proxy_resolve_request_.reset(); | 
 |   } | 
 |   net_log_.EndEvent(NetLogEventType::HTTP_STREAM_JOB_CONTROLLER); | 
 | } | 
 |  | 
 | std::unique_ptr<HttpStreamRequest> HttpStreamFactory::JobController::Start( | 
 |     HttpStreamRequest::Delegate* delegate, | 
 |     WebSocketHandshakeStreamBase::CreateHelper* | 
 |         websocket_handshake_stream_create_helper, | 
 |     const NetLogWithSource& source_net_log, | 
 |     HttpStreamRequest::StreamType stream_type, | 
 |     RequestPriority priority) { | 
 |   DCHECK(factory_); | 
 |   DCHECK(!request_); | 
 |  | 
 |   stream_type_ = stream_type; | 
 |   priority_ = priority; | 
 |  | 
 |   auto request = std::make_unique<HttpStreamRequest>( | 
 |       request_info_.url, this, delegate, | 
 |       websocket_handshake_stream_create_helper, source_net_log, stream_type); | 
 |   // Keep a raw pointer but release ownership of HttpStreamRequest instance. | 
 |   request_ = request.get(); | 
 |  | 
 |   // Associates |net_log_| with |source_net_log|. | 
 |   source_net_log.AddEvent(NetLogEventType::HTTP_STREAM_JOB_CONTROLLER_BOUND, | 
 |                           net_log_.source().ToEventParametersCallback()); | 
 |   net_log_.AddEvent(NetLogEventType::HTTP_STREAM_JOB_CONTROLLER_BOUND, | 
 |                     source_net_log.source().ToEventParametersCallback()); | 
 |  | 
 |   RunLoop(OK); | 
 |   return request; | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::Preconnect(int num_streams) { | 
 |   DCHECK(!main_job_); | 
 |   DCHECK(!alternative_job_); | 
 |   DCHECK(is_preconnect_); | 
 |  | 
 |   stream_type_ = HttpStreamRequest::HTTP_STREAM; | 
 |   num_streams_ = num_streams; | 
 |  | 
 |   RunLoop(OK); | 
 | } | 
 |  | 
 | LoadState HttpStreamFactory::JobController::GetLoadState() const { | 
 |   DCHECK(request_); | 
 |   if (next_state_ == STATE_RESOLVE_PROXY_COMPLETE) | 
 |     return proxy_resolve_request_->GetLoadState(); | 
 |   if (bound_job_) | 
 |     return bound_job_->GetLoadState(); | 
 |   if (main_job_) | 
 |     return main_job_->GetLoadState(); | 
 |   if (alternative_job_) | 
 |     return alternative_job_->GetLoadState(); | 
 |   // When proxy resolution fails, there is no job created and | 
 |   // NotifyRequestFailed() is executed one message loop iteration later. | 
 |   return LOAD_STATE_IDLE; | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::OnRequestComplete() { | 
 |   DCHECK(request_); | 
 |  | 
 |   RemoveRequestFromSpdySessionRequestMap(); | 
 |   CancelJobs(); | 
 |   request_ = nullptr; | 
 |   if (bound_job_) { | 
 |     if (bound_job_->job_type() == MAIN) { | 
 |       main_job_.reset(); | 
 |     } else { | 
 |       DCHECK(bound_job_->job_type() == ALTERNATIVE); | 
 |       alternative_job_.reset(); | 
 |     } | 
 |     bound_job_ = nullptr; | 
 |   } | 
 |   MaybeNotifyFactoryOfCompletion(); | 
 | } | 
 |  | 
 | int HttpStreamFactory::JobController::RestartTunnelWithProxyAuth() { | 
 |   DCHECK(bound_job_); | 
 |   return bound_job_->RestartTunnelWithProxyAuth(); | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::SetPriority(RequestPriority priority) { | 
 |   if (main_job_) { | 
 |     main_job_->SetPriority(priority); | 
 |   } | 
 |   if (alternative_job_) { | 
 |     alternative_job_->SetPriority(priority); | 
 |   } | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::OnStreamReadyOnPooledConnection( | 
 |     const SSLConfig& used_ssl_config, | 
 |     const ProxyInfo& proxy_info, | 
 |     std::unique_ptr<HttpStream> stream) { | 
 |   DCHECK(request_->completed()); | 
 |   DCHECK(!is_websocket_); | 
 |   DCHECK_EQ(HttpStreamRequest::HTTP_STREAM, request_->stream_type()); | 
 |  | 
 |   main_job_.reset(); | 
 |   alternative_job_.reset(); | 
 |   ResetErrorStatusForJobs(); | 
 |  | 
 |   factory_->OnStreamReady(proxy_info, request_info_.privacy_mode); | 
 |  | 
 |   delegate_->OnStreamReady(used_ssl_config, proxy_info, std::move(stream)); | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController:: | 
 |     OnBidirectionalStreamImplReadyOnPooledConnection( | 
 |         const SSLConfig& used_ssl_config, | 
 |         const ProxyInfo& used_proxy_info, | 
 |         std::unique_ptr<BidirectionalStreamImpl> stream) { | 
 |   DCHECK(request_->completed()); | 
 |   DCHECK(!is_websocket_); | 
 |   DCHECK_EQ(HttpStreamRequest::BIDIRECTIONAL_STREAM, request_->stream_type()); | 
 |  | 
 |   main_job_.reset(); | 
 |   alternative_job_.reset(); | 
 |   ResetErrorStatusForJobs(); | 
 |  | 
 |   delegate_->OnBidirectionalStreamImplReady(used_ssl_config, used_proxy_info, | 
 |                                             std::move(stream)); | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::OnStreamReady( | 
 |     Job* job, | 
 |     const SSLConfig& used_ssl_config) { | 
 |   DCHECK(job); | 
 |  | 
 |   factory_->OnStreamReady(job->proxy_info(), request_info_.privacy_mode); | 
 |  | 
 |   if (IsJobOrphaned(job)) { | 
 |     // We have bound a job to the associated HttpStreamRequest, |job| has been | 
 |     // orphaned. | 
 |     OnOrphanedJobComplete(job); | 
 |     return; | 
 |   } | 
 |   std::unique_ptr<HttpStream> stream = job->ReleaseStream(); | 
 |   DCHECK(stream); | 
 |  | 
 |   MarkRequestComplete(job->was_alpn_negotiated(), job->negotiated_protocol(), | 
 |                       job->using_spdy()); | 
 |  | 
 |   if (!request_) | 
 |     return; | 
 |   DCHECK(!is_websocket_); | 
 |   DCHECK_EQ(HttpStreamRequest::HTTP_STREAM, request_->stream_type()); | 
 |   OnJobSucceeded(job); | 
 |   DCHECK(request_->completed()); | 
 |   delegate_->OnStreamReady(used_ssl_config, job->proxy_info(), | 
 |                            std::move(stream)); | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::OnBidirectionalStreamImplReady( | 
 |     Job* job, | 
 |     const SSLConfig& used_ssl_config, | 
 |     const ProxyInfo& used_proxy_info) { | 
 |   DCHECK(job); | 
 |  | 
 |   if (IsJobOrphaned(job)) { | 
 |     // We have bound a job to the associated HttpStreamRequest, |job| has been | 
 |     // orphaned. | 
 |     OnOrphanedJobComplete(job); | 
 |     return; | 
 |   } | 
 |  | 
 |   MarkRequestComplete(job->was_alpn_negotiated(), job->negotiated_protocol(), | 
 |                       job->using_spdy()); | 
 |  | 
 |   if (!request_) | 
 |     return; | 
 |   std::unique_ptr<BidirectionalStreamImpl> stream = | 
 |       job->ReleaseBidirectionalStream(); | 
 |   DCHECK(stream); | 
 |   DCHECK(!is_websocket_); | 
 |   DCHECK_EQ(HttpStreamRequest::BIDIRECTIONAL_STREAM, request_->stream_type()); | 
 |  | 
 |   OnJobSucceeded(job); | 
 |   DCHECK(request_->completed()); | 
 |   delegate_->OnBidirectionalStreamImplReady(used_ssl_config, used_proxy_info, | 
 |                                             std::move(stream)); | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::OnWebSocketHandshakeStreamReady( | 
 |     Job* job, | 
 |     const SSLConfig& used_ssl_config, | 
 |     const ProxyInfo& used_proxy_info, | 
 |     std::unique_ptr<WebSocketHandshakeStreamBase> stream) { | 
 |   DCHECK(job); | 
 |   MarkRequestComplete(job->was_alpn_negotiated(), job->negotiated_protocol(), | 
 |                       job->using_spdy()); | 
 |  | 
 |   if (!request_) | 
 |     return; | 
 |   DCHECK(is_websocket_); | 
 |   DCHECK_EQ(HttpStreamRequest::HTTP_STREAM, request_->stream_type()); | 
 |   DCHECK(stream); | 
 |  | 
 |   OnJobSucceeded(job); | 
 |   DCHECK(request_->completed()); | 
 |   delegate_->OnWebSocketHandshakeStreamReady(used_ssl_config, used_proxy_info, | 
 |                                              std::move(stream)); | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::OnStreamFailed( | 
 |     Job* job, | 
 |     int status, | 
 |     const SSLConfig& used_ssl_config) { | 
 |   if (job->job_type() == ALTERNATIVE) { | 
 |     DCHECK_EQ(alternative_job_.get(), job); | 
 |     if (alternative_job_->alternative_proxy_server().is_valid()) { | 
 |       OnAlternativeProxyJobFailed(status); | 
 |     } else { | 
 |       OnAlternativeServiceJobFailed(status); | 
 |     } | 
 |   } else { | 
 |     DCHECK_EQ(main_job_.get(), job); | 
 |     main_job_net_error_ = status; | 
 |   } | 
 |  | 
 |   MaybeResumeMainJob(job, base::TimeDelta()); | 
 |  | 
 |   if (IsJobOrphaned(job)) { | 
 |     // We have bound a job to the associated HttpStreamRequest, |job| has been | 
 |     // orphaned. | 
 |     OnOrphanedJobComplete(job); | 
 |     return; | 
 |   } | 
 |  | 
 |   if (!request_) | 
 |     return; | 
 |   DCHECK_NE(OK, status); | 
 |   DCHECK(job); | 
 |  | 
 |   if (!bound_job_) { | 
 |     if (main_job_ && alternative_job_) { | 
 |       // Hey, we've got other jobs! Maybe one of them will succeed, let's just | 
 |       // ignore this failure. | 
 |       if (job->job_type() == MAIN) { | 
 |         main_job_.reset(); | 
 |       } else { | 
 |         DCHECK(job->job_type() == ALTERNATIVE); | 
 |         alternative_job_.reset(); | 
 |       } | 
 |       return; | 
 |     } else { | 
 |       BindJob(job); | 
 |     } | 
 |   } | 
 |  | 
 |   status = ReconsiderProxyAfterError(job, status); | 
 |   if (next_state_ == STATE_RESOLVE_PROXY_COMPLETE) { | 
 |     if (status == ERR_IO_PENDING) | 
 |       return; | 
 |     DCHECK_EQ(OK, status); | 
 |     RunLoop(status); | 
 |     return; | 
 |   } | 
 |   delegate_->OnStreamFailed(status, *job->net_error_details(), used_ssl_config); | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::OnFailedOnDefaultNetwork(Job* job) { | 
 |   DCHECK_EQ(job->job_type(), ALTERNATIVE); | 
 |   alternative_job_failed_on_default_network_ = true; | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::OnCertificateError( | 
 |     Job* job, | 
 |     int status, | 
 |     const SSLConfig& used_ssl_config, | 
 |     const SSLInfo& ssl_info) { | 
 |   MaybeResumeMainJob(job, base::TimeDelta()); | 
 |  | 
 |   if (IsJobOrphaned(job)) { | 
 |     // We have bound a job to the associated HttpStreamRequest, |job| has been | 
 |     // orphaned. | 
 |     OnOrphanedJobComplete(job); | 
 |     return; | 
 |   } | 
 |  | 
 |   if (!request_) | 
 |     return; | 
 |   DCHECK_NE(OK, status); | 
 |   if (!bound_job_) | 
 |     BindJob(job); | 
 |  | 
 |   delegate_->OnCertificateError(status, used_ssl_config, ssl_info); | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::OnHttpsProxyTunnelResponse( | 
 |     Job* job, | 
 |     const HttpResponseInfo& response_info, | 
 |     const SSLConfig& used_ssl_config, | 
 |     const ProxyInfo& used_proxy_info, | 
 |     std::unique_ptr<HttpStream> stream) { | 
 |   MaybeResumeMainJob(job, base::TimeDelta()); | 
 |  | 
 |   if (IsJobOrphaned(job)) { | 
 |     // We have bound a job to the associated HttpStreamRequest, |job| has been | 
 |     // orphaned. | 
 |     OnOrphanedJobComplete(job); | 
 |     return; | 
 |   } | 
 |  | 
 |   if (!bound_job_) | 
 |     BindJob(job); | 
 |   if (!request_) | 
 |     return; | 
 |   delegate_->OnHttpsProxyTunnelResponse(response_info, used_ssl_config, | 
 |                                         used_proxy_info, std::move(stream)); | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::OnNeedsClientAuth( | 
 |     Job* job, | 
 |     const SSLConfig& used_ssl_config, | 
 |     SSLCertRequestInfo* cert_info) { | 
 |   MaybeResumeMainJob(job, base::TimeDelta()); | 
 |  | 
 |   if (IsJobOrphaned(job)) { | 
 |     // We have bound a job to the associated HttpStreamRequest, |job| has been | 
 |     // orphaned. | 
 |     OnOrphanedJobComplete(job); | 
 |     return; | 
 |   } | 
 |   if (!request_) | 
 |     return; | 
 |   if (!bound_job_) | 
 |     BindJob(job); | 
 |  | 
 |   delegate_->OnNeedsClientAuth(used_ssl_config, cert_info); | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::OnNeedsProxyAuth( | 
 |     Job* job, | 
 |     const HttpResponseInfo& proxy_response, | 
 |     const SSLConfig& used_ssl_config, | 
 |     const ProxyInfo& used_proxy_info, | 
 |     HttpAuthController* auth_controller) { | 
 |   MaybeResumeMainJob(job, base::TimeDelta()); | 
 |  | 
 |   if (IsJobOrphaned(job)) { | 
 |     // We have bound a job to the associated HttpStreamRequest, |job| has been | 
 |     // orphaned. | 
 |     OnOrphanedJobComplete(job); | 
 |     return; | 
 |   } | 
 |  | 
 |   if (!request_) | 
 |     return; | 
 |   if (!bound_job_) | 
 |     BindJob(job); | 
 |   delegate_->OnNeedsProxyAuth(proxy_response, used_ssl_config, used_proxy_info, | 
 |                               auth_controller); | 
 | } | 
 |  | 
 | bool HttpStreamFactory::JobController::OnInitConnection( | 
 |     const ProxyInfo& proxy_info) { | 
 |   return factory_->OnInitConnection(*this, proxy_info, | 
 |                                     request_info_.privacy_mode); | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::OnNewSpdySessionReady( | 
 |     Job* job, | 
 |     const base::WeakPtr<SpdySession>& spdy_session) { | 
 |   DCHECK(job); | 
 |   DCHECK(job->using_spdy()); | 
 |   DCHECK(!is_preconnect_); | 
 |  | 
 |   bool is_job_orphaned = IsJobOrphaned(job); | 
 |  | 
 |   // Cache these values in case the job gets deleted. | 
 |   const SSLConfig used_ssl_config = job->server_ssl_config(); | 
 |   const ProxyInfo used_proxy_info = job->proxy_info(); | 
 |   const bool was_alpn_negotiated = job->was_alpn_negotiated(); | 
 |   const NextProto negotiated_protocol = job->negotiated_protocol(); | 
 |   const bool using_spdy = job->using_spdy(); | 
 |   const NetLogSource source_dependency = job->net_log().source(); | 
 |  | 
 |   // Cache this so we can still use it if the JobController is deleted. | 
 |   SpdySessionPool* spdy_session_pool = session_->spdy_session_pool(); | 
 |  | 
 |   // Notify |request_|. | 
 |   if (!is_preconnect_ && !is_job_orphaned) { | 
 |  | 
 |     DCHECK(request_); | 
 |  | 
 |     // The first case is the usual case. | 
 |     if (!job_bound_) { | 
 |       BindJob(job); | 
 |     } | 
 |  | 
 |     MarkRequestComplete(was_alpn_negotiated, negotiated_protocol, using_spdy); | 
 |  | 
 |     if (is_websocket_) { | 
 |       // TODO(bnc): Re-instate this code when WebSockets over HTTP/2 is | 
 |       // implemented.  https://crbug.com/801564. | 
 |       NOTREACHED(); | 
 |     } else if (job->stream_type() == HttpStreamRequest::BIDIRECTIONAL_STREAM) { | 
 |       std::unique_ptr<BidirectionalStreamImpl> bidirectional_stream_impl = | 
 |           job->ReleaseBidirectionalStream(); | 
 |       DCHECK(bidirectional_stream_impl); | 
 |       delegate_->OnBidirectionalStreamImplReady( | 
 |           used_ssl_config, used_proxy_info, | 
 |           std::move(bidirectional_stream_impl)); | 
 |     } else { | 
 |       std::unique_ptr<HttpStream> stream = job->ReleaseStream(); | 
 |       DCHECK(stream); | 
 |       delegate_->OnStreamReady(used_ssl_config, used_proxy_info, | 
 |                                std::move(stream)); | 
 |     } | 
 |   } | 
 |  | 
 |   // Notify other requests that have the same SpdySessionKey. | 
 |   // |request_| and |bound_job_| might be deleted already. | 
 |   if (spdy_session && spdy_session->IsAvailable()) { | 
 |     spdy_session_pool->OnNewSpdySessionReady( | 
 |         spdy_session, used_ssl_config, used_proxy_info, was_alpn_negotiated, | 
 |         negotiated_protocol, using_spdy, source_dependency); | 
 |   } | 
 |   if (is_job_orphaned) { | 
 |     OnOrphanedJobComplete(job); | 
 |   } | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::OnPreconnectsComplete(Job* job) { | 
 |   DCHECK_EQ(main_job_.get(), job); | 
 |   main_job_.reset(); | 
 |   ResetErrorStatusForJobs(); | 
 |   factory_->OnPreconnectsCompleteInternal(); | 
 |   MaybeNotifyFactoryOfCompletion(); | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::OnOrphanedJobComplete(const Job* job) { | 
 |   if (job->job_type() == MAIN) { | 
 |     DCHECK_EQ(main_job_.get(), job); | 
 |     main_job_.reset(); | 
 |   } else { | 
 |     DCHECK_EQ(alternative_job_.get(), job); | 
 |     alternative_job_.reset(); | 
 |   } | 
 |  | 
 |   MaybeNotifyFactoryOfCompletion(); | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::AddConnectionAttemptsToRequest( | 
 |     Job* job, | 
 |     const ConnectionAttempts& attempts) { | 
 |   if (is_preconnect_ || IsJobOrphaned(job)) | 
 |     return; | 
 |  | 
 |   request_->AddConnectionAttempts(attempts); | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::ResumeMainJobLater( | 
 |     const base::TimeDelta& delay) { | 
 |   net_log_.AddEvent(NetLogEventType::HTTP_STREAM_JOB_DELAYED, | 
 |                     NetLog::Int64Callback("delay", delay.InMilliseconds())); | 
 |   resume_main_job_callback_.Reset( | 
 |       base::BindOnce(&HttpStreamFactory::JobController::ResumeMainJob, | 
 |                      ptr_factory_.GetWeakPtr())); | 
 |   base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( | 
 |       FROM_HERE, resume_main_job_callback_.callback(), delay); | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::ResumeMainJob() { | 
 |   DCHECK(main_job_); | 
 |  | 
 |   if (main_job_is_resumed_) { | 
 |     return; | 
 |   } | 
 |   main_job_is_resumed_ = true; | 
 |   main_job_->net_log().AddEvent( | 
 |       NetLogEventType::HTTP_STREAM_JOB_RESUMED, | 
 |       NetLog::Int64Callback("delay", main_job_wait_time_.InMilliseconds())); | 
 |  | 
 |   main_job_->Resume(); | 
 |   main_job_wait_time_ = base::TimeDelta(); | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::ResetErrorStatusForJobs() { | 
 |   main_job_net_error_ = OK; | 
 |   alternative_job_net_error_ = OK; | 
 |   alternative_job_failed_on_default_network_ = false; | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::MaybeResumeMainJob( | 
 |     Job* job, | 
 |     const base::TimeDelta& delay) { | 
 |   DCHECK(delay == base::TimeDelta() || delay == main_job_wait_time_); | 
 |   DCHECK(job == main_job_.get() || job == alternative_job_.get()); | 
 |  | 
 |   if (job != alternative_job_.get() || !main_job_) | 
 |     return; | 
 |  | 
 |   main_job_is_blocked_ = false; | 
 |  | 
 |   if (!main_job_->is_waiting()) { | 
 |     // There are two cases where the main job is not in WAIT state: | 
 |     //   1) The main job hasn't got to waiting state, do not yet post a task to | 
 |     //      resume since that will happen in ShouldWait(). | 
 |     //   2) The main job has passed waiting state, so the main job does not need | 
 |     //      to be resumed. | 
 |     return; | 
 |   } | 
 |  | 
 |   main_job_wait_time_ = delay; | 
 |  | 
 |   ResumeMainJobLater(main_job_wait_time_); | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::OnConnectionInitialized(Job* job, | 
 |                                                                int rv) { | 
 |   if (rv != OK) { | 
 |     // Resume the main job as there's an error raised in connection | 
 |     // initiation. | 
 |     return MaybeResumeMainJob(job, main_job_wait_time_); | 
 |   } | 
 | } | 
 |  | 
 | bool HttpStreamFactory::JobController::ShouldWait(Job* job) { | 
 |   // The alternative job never waits. | 
 |   if (job == alternative_job_.get()) | 
 |     return false; | 
 |  | 
 |   if (main_job_is_blocked_) | 
 |     return true; | 
 |  | 
 |   if (main_job_wait_time_.is_zero()) | 
 |     return false; | 
 |  | 
 |   ResumeMainJobLater(main_job_wait_time_); | 
 |   return true; | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::SetSpdySessionKey( | 
 |     Job* job, | 
 |     const SpdySessionKey& spdy_session_key) { | 
 |   DCHECK(!job->using_quic()); | 
 |  | 
 |   if (is_preconnect_ || IsJobOrphaned(job)) | 
 |     return; | 
 |  | 
 |   session_->spdy_session_pool()->AddRequestToSpdySessionRequestMap( | 
 |       spdy_session_key, request_); | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController:: | 
 |     RemoveRequestFromSpdySessionRequestMapForJob(Job* job) { | 
 |   DCHECK(!job->using_quic()); | 
 |  | 
 |   if (is_preconnect_ || IsJobOrphaned(job)) | 
 |     return; | 
 |  | 
 |   RemoveRequestFromSpdySessionRequestMap(); | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController:: | 
 |     RemoveRequestFromSpdySessionRequestMap() { | 
 |   DCHECK(request_); | 
 |   session_->spdy_session_pool()->RemoveRequestFromSpdySessionRequestMap( | 
 |       request_); | 
 | } | 
 |  | 
 | const NetLogWithSource* HttpStreamFactory::JobController::GetNetLog() const { | 
 |   return &net_log_; | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::MaybeSetWaitTimeForMainJob( | 
 |     const base::TimeDelta& delay) { | 
 |   if (main_job_is_blocked_) { | 
 |     main_job_wait_time_ = std::min( | 
 |         delay, base::TimeDelta::FromSeconds(kMaxDelayTimeForMainJobSecs)); | 
 |   } | 
 | } | 
 |  | 
 | bool HttpStreamFactory::JobController::HasPendingMainJob() const { | 
 |   return main_job_.get() != nullptr; | 
 | } | 
 |  | 
 | bool HttpStreamFactory::JobController::HasPendingAltJob() const { | 
 |   return alternative_job_.get() != nullptr; | 
 | } | 
 |  | 
 | size_t HttpStreamFactory::JobController::EstimateMemoryUsage() const { | 
 |   return base::trace_event::EstimateMemoryUsage(main_job_) + | 
 |          base::trace_event::EstimateMemoryUsage(alternative_job_); | 
 | } | 
 |  | 
 | WebSocketHandshakeStreamBase::CreateHelper* | 
 | HttpStreamFactory::JobController::websocket_handshake_stream_create_helper() { | 
 |   DCHECK(request_); | 
 |   return request_->websocket_handshake_stream_create_helper(); | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::OnIOComplete(int result) { | 
 |   RunLoop(result); | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::RunLoop(int result) { | 
 |   int rv = DoLoop(result); | 
 |   if (rv == ERR_IO_PENDING) | 
 |     return; | 
 |   if (rv != OK) { | 
 |     // DoLoop can only fail during proxy resolution step which happens before | 
 |     // any jobs are created. Notify |request_| of the failure one message loop | 
 |     // iteration later to avoid re-entrancy. | 
 |     DCHECK(!main_job_); | 
 |     DCHECK(!alternative_job_); | 
 |     base::ThreadTaskRunnerHandle::Get()->PostTask( | 
 |         FROM_HERE, | 
 |         base::Bind(&HttpStreamFactory::JobController::NotifyRequestFailed, | 
 |                    ptr_factory_.GetWeakPtr(), rv)); | 
 |   } | 
 | } | 
 |  | 
 | int HttpStreamFactory::JobController::DoLoop(int rv) { | 
 |   DCHECK_NE(next_state_, STATE_NONE); | 
 |   do { | 
 |     State state = next_state_; | 
 |     next_state_ = STATE_NONE; | 
 |     switch (state) { | 
 |       case STATE_RESOLVE_PROXY: | 
 |         DCHECK_EQ(OK, rv); | 
 |         rv = DoResolveProxy(); | 
 |         break; | 
 |       case STATE_RESOLVE_PROXY_COMPLETE: | 
 |         rv = DoResolveProxyComplete(rv); | 
 |         break; | 
 |       case STATE_CREATE_JOBS: | 
 |         DCHECK_EQ(OK, rv); | 
 |         rv = DoCreateJobs(); | 
 |         break; | 
 |       default: | 
 |         NOTREACHED() << "bad state"; | 
 |         break; | 
 |     } | 
 |   } while (next_state_ != STATE_NONE && rv != ERR_IO_PENDING); | 
 |   return rv; | 
 | } | 
 |  | 
 | int HttpStreamFactory::JobController::DoResolveProxy() { | 
 |   DCHECK(!proxy_resolve_request_); | 
 |   DCHECK(session_); | 
 |  | 
 |   next_state_ = STATE_RESOLVE_PROXY_COMPLETE; | 
 |  | 
 |   if (request_info_.load_flags & LOAD_BYPASS_PROXY) { | 
 |     proxy_info_.UseDirect(); | 
 |     return OK; | 
 |   } | 
 |  | 
 |   HostPortPair destination(HostPortPair::FromURL(request_info_.url)); | 
 |   GURL origin_url = ApplyHostMappingRules(request_info_.url, &destination); | 
 |  | 
 |   CompletionOnceCallback io_callback = | 
 |       base::Bind(&JobController::OnIOComplete, base::Unretained(this)); | 
 |   return session_->proxy_resolution_service()->ResolveProxy( | 
 |       origin_url, request_info_.method, &proxy_info_, std::move(io_callback), | 
 |       &proxy_resolve_request_, net_log_); | 
 | } | 
 |  | 
 | int HttpStreamFactory::JobController::DoResolveProxyComplete(int rv) { | 
 |   DCHECK_NE(ERR_IO_PENDING, rv); | 
 |  | 
 |   proxy_resolve_request_ = nullptr; | 
 |   net_log_.AddEvent( | 
 |       NetLogEventType::HTTP_STREAM_JOB_CONTROLLER_PROXY_SERVER_RESOLVED, | 
 |       base::Bind( | 
 |           &NetLogHttpStreamJobProxyServerResolved, | 
 |           proxy_info_.is_empty() ? ProxyServer() : proxy_info_.proxy_server())); | 
 |  | 
 |   if (rv != OK) | 
 |     return rv; | 
 |   // Remove unsupported proxies from the list. | 
 |   int supported_proxies = ProxyServer::SCHEME_DIRECT | | 
 |                           ProxyServer::SCHEME_HTTP | ProxyServer::SCHEME_HTTPS | | 
 |                           ProxyServer::SCHEME_SOCKS4 | | 
 |                           ProxyServer::SCHEME_SOCKS5; | 
 |   if (session_->IsQuicEnabled()) | 
 |     supported_proxies |= ProxyServer::SCHEME_QUIC; | 
 |   proxy_info_.RemoveProxiesWithoutScheme(supported_proxies); | 
 |  | 
 |   if (proxy_info_.is_empty()) { | 
 |     // No proxies/direct to choose from. | 
 |     return ERR_NO_SUPPORTED_PROXIES; | 
 |   } | 
 |  | 
 |   next_state_ = STATE_CREATE_JOBS; | 
 |   return rv; | 
 | } | 
 |  | 
 | int HttpStreamFactory::JobController::DoCreateJobs() { | 
 |   DCHECK(!main_job_); | 
 |   DCHECK(!alternative_job_); | 
 |  | 
 |   HostPortPair destination(HostPortPair::FromURL(request_info_.url)); | 
 |   GURL origin_url = ApplyHostMappingRules(request_info_.url, &destination); | 
 |  | 
 |   // Create an alternative job if alternative service is set up for this domain. | 
 |   alternative_service_info_ = | 
 |       GetAlternativeServiceInfoFor(request_info_, delegate_, stream_type_); | 
 |   quic::QuicTransportVersion quic_version = quic::QUIC_VERSION_UNSUPPORTED; | 
 |   if (alternative_service_info_.protocol() == kProtoQUIC) { | 
 |     quic_version = | 
 |         SelectQuicVersion(alternative_service_info_.advertised_versions()); | 
 |     DCHECK_NE(quic_version, quic::QUIC_VERSION_UNSUPPORTED); | 
 |   } | 
 |  | 
 |   if (is_preconnect_) { | 
 |     // Due to how the socket pools handle priorities and idle sockets, only IDLE | 
 |     // priority currently makes sense for preconnects. The priority for | 
 |     // preconnects is currently ignored (see RequestSocketsForPool()), but could | 
 |     // be used at some point for proxy resolution or something. | 
 |     if (alternative_service_info_.protocol() != kProtoUnknown) { | 
 |       HostPortPair alternative_destination( | 
 |           alternative_service_info_.host_port_pair()); | 
 |       ignore_result( | 
 |           ApplyHostMappingRules(request_info_.url, &alternative_destination)); | 
 |       main_job_ = job_factory_->CreateAltSvcJob( | 
 |           this, PRECONNECT, session_, request_info_, IDLE, proxy_info_, | 
 |           server_ssl_config_, proxy_ssl_config_, alternative_destination, | 
 |           origin_url, alternative_service_info_.protocol(), quic_version, | 
 |           is_websocket_, enable_ip_based_pooling_, session_->net_log()); | 
 |     } else { | 
 |       main_job_ = job_factory_->CreateMainJob( | 
 |           this, PRECONNECT, session_, request_info_, IDLE, proxy_info_, | 
 |           server_ssl_config_, proxy_ssl_config_, destination, origin_url, | 
 |           is_websocket_, enable_ip_based_pooling_, session_->net_log()); | 
 |     } | 
 |     main_job_->Preconnect(num_streams_); | 
 |     return OK; | 
 |   } | 
 |   main_job_ = job_factory_->CreateMainJob( | 
 |       this, MAIN, session_, request_info_, priority_, proxy_info_, | 
 |       server_ssl_config_, proxy_ssl_config_, destination, origin_url, | 
 |       is_websocket_, enable_ip_based_pooling_, net_log_.net_log()); | 
 |   // Alternative Service can only be set for HTTPS requests while Alternative | 
 |   // Proxy is set for HTTP requests. | 
 |   if (alternative_service_info_.protocol() != kProtoUnknown) { | 
 |     DCHECK(request_info_.url.SchemeIs(url::kHttpsScheme)); | 
 |     DVLOG(1) << "Selected alternative service (host: " | 
 |              << alternative_service_info_.host_port_pair().host() | 
 |              << " port: " << alternative_service_info_.host_port_pair().port() | 
 |              << " version: " << quic_version << ")"; | 
 |  | 
 |     HostPortPair alternative_destination( | 
 |         alternative_service_info_.host_port_pair()); | 
 |     ignore_result( | 
 |         ApplyHostMappingRules(request_info_.url, &alternative_destination)); | 
 |     alternative_job_ = job_factory_->CreateAltSvcJob( | 
 |         this, ALTERNATIVE, session_, request_info_, priority_, proxy_info_, | 
 |         server_ssl_config_, proxy_ssl_config_, alternative_destination, | 
 |         origin_url, alternative_service_info_.protocol(), quic_version, | 
 |         is_websocket_, enable_ip_based_pooling_, net_log_.net_log()); | 
 |  | 
 |     main_job_is_blocked_ = true; | 
 |     alternative_job_->Start(request_->stream_type()); | 
 |   } else { | 
 |     ProxyInfo alternative_proxy_info; | 
 |     if (ShouldCreateAlternativeProxyServerJob(proxy_info_, request_info_.url, | 
 |                                               &alternative_proxy_info)) { | 
 |       DCHECK(!main_job_is_blocked_); | 
 |  | 
 |       alternative_job_ = job_factory_->CreateAltProxyJob( | 
 |           this, ALTERNATIVE, session_, request_info_, priority_, | 
 |           alternative_proxy_info, server_ssl_config_, proxy_ssl_config_, | 
 |           destination, origin_url, alternative_proxy_info.proxy_server(), | 
 |           is_websocket_, enable_ip_based_pooling_, net_log_.net_log()); | 
 |  | 
 |       can_start_alternative_proxy_job_ = false; | 
 |       main_job_is_blocked_ = true; | 
 |       alternative_job_->Start(request_->stream_type()); | 
 |     } | 
 |   } | 
 |   // Even if |alternative_job| has already finished, it will not have notified | 
 |   // the request yet, since we defer that to the next iteration of the | 
 |   // MessageLoop, so starting |main_job_| is always safe. | 
 |   main_job_->Start(request_->stream_type()); | 
 |   return OK; | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::BindJob(Job* job) { | 
 |   DCHECK(request_); | 
 |   DCHECK(job); | 
 |   DCHECK(job == alternative_job_.get() || job == main_job_.get()); | 
 |   DCHECK(!job_bound_); | 
 |   DCHECK(!bound_job_); | 
 |  | 
 |   job_bound_ = true; | 
 |   bound_job_ = job; | 
 |  | 
 |   request_->net_log().AddEvent( | 
 |       NetLogEventType::HTTP_STREAM_REQUEST_BOUND_TO_JOB, | 
 |       job->net_log().source().ToEventParametersCallback()); | 
 |   job->net_log().AddEvent( | 
 |       NetLogEventType::HTTP_STREAM_JOB_BOUND_TO_REQUEST, | 
 |       request_->net_log().source().ToEventParametersCallback()); | 
 |  | 
 |   OrphanUnboundJob(); | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::CancelJobs() { | 
 |   DCHECK(request_); | 
 |   if (job_bound_) | 
 |     return; | 
 |   if (alternative_job_) | 
 |     alternative_job_.reset(); | 
 |   if (main_job_) | 
 |     main_job_.reset(); | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::OrphanUnboundJob() { | 
 |   DCHECK(request_); | 
 |   DCHECK(bound_job_); | 
 |   RemoveRequestFromSpdySessionRequestMap(); | 
 |  | 
 |   if (bound_job_->job_type() == MAIN && alternative_job_) { | 
 |     DCHECK(!is_websocket_); | 
 |     // Allow |alternative_job_| to run to completion, rather than resetting it | 
 |     // to check if there is any broken alternative service to report. | 
 |     // OnOrphanedJobComplete() will clean up |this| when the job completes. | 
 |     alternative_job_->Orphan(); | 
 |     return; | 
 |   } | 
 |  | 
 |   if (bound_job_->job_type() == ALTERNATIVE && main_job_ && | 
 |       !alternative_job_failed_on_default_network_) { | 
 |     // |request_| is bound to the alternative job and the alternative job | 
 |     // succeeds on the default network. This means that the main job | 
 |     // is no longer needed, so cancel it now. Pending ConnectJobs will return | 
 |     // established sockets to socket pools if applicable. | 
 |     // https://crbug.com/757548. | 
 |     // The main job still needs to run if the alternative job succeeds on the | 
 |     // alternate network in order to figure out whether QUIC should be marked as | 
 |     // broken until the default network changes. | 
 |     DCHECK_EQ(OK, alternative_job_net_error_); | 
 |     main_job_.reset(); | 
 |   } | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::OnJobSucceeded(Job* job) { | 
 |   DCHECK(job); | 
 |  | 
 |   if (!bound_job_) { | 
 |     if (main_job_ && alternative_job_) | 
 |       ReportAlternateProtocolUsage(job); | 
 |     BindJob(job); | 
 |     return; | 
 |   } | 
 |   DCHECK(bound_job_); | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::MarkRequestComplete( | 
 |     bool was_alpn_negotiated, | 
 |     NextProto negotiated_protocol, | 
 |     bool using_spdy) { | 
 |   if (request_) | 
 |     request_->Complete(was_alpn_negotiated, negotiated_protocol, using_spdy); | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::OnAlternativeServiceJobFailed( | 
 |     int net_error) { | 
 |   DCHECK_EQ(alternative_job_->job_type(), ALTERNATIVE); | 
 |   DCHECK_NE(OK, net_error); | 
 |   DCHECK_NE(kProtoUnknown, alternative_service_info_.protocol()); | 
 |  | 
 |   alternative_job_net_error_ = net_error; | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::OnAlternativeProxyJobFailed( | 
 |     int net_error) { | 
 |   DCHECK_EQ(alternative_job_->job_type(), ALTERNATIVE); | 
 |   DCHECK_NE(OK, net_error); | 
 |   DCHECK(alternative_job_->alternative_proxy_server().is_valid()); | 
 |   DCHECK(alternative_job_->alternative_proxy_server() == | 
 |          alternative_job_->proxy_info().proxy_server()); | 
 |  | 
 |   base::UmaHistogramSparse("Net.AlternativeProxyFailed", -net_error); | 
 |  | 
 |   // Need to mark alt proxy as broken regardless of whether the job is bound. | 
 |   // The proxy will be marked bad until the proxy retry information is cleared | 
 |   // by an event such as a network change. | 
 |   if (net_error != ERR_NETWORK_CHANGED && | 
 |       net_error != ERR_INTERNET_DISCONNECTED) { | 
 |     session_->proxy_resolution_service()->MarkProxiesAsBadUntil( | 
 |         alternative_job_->proxy_info(), base::TimeDelta::Max(), {}, net_log_); | 
 |   } | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::MaybeReportBrokenAlternativeService() { | 
 |   // If alternative job succeeds on the default network, no brokenness to | 
 |   // report. | 
 |   if (alternative_job_net_error_ == OK && | 
 |       !alternative_job_failed_on_default_network_) | 
 |     return; | 
 |  | 
 |   // No brokenness to report if the main job fails. | 
 |   if (main_job_net_error_ != OK) | 
 |     return; | 
 |  | 
 |   DCHECK(alternative_service_info_.protocol() != kProtoUnknown); | 
 |  | 
 |   if (alternative_job_failed_on_default_network_ && | 
 |       alternative_job_net_error_ == OK) { | 
 |     // Alternative job failed on the default network but succeeds on the | 
 |     // non-default network, mark alternative service broken until the default | 
 |     // network changes. | 
 |     session_->http_server_properties() | 
 |         ->MarkAlternativeServiceBrokenUntilDefaultNetworkChanges( | 
 |             alternative_service_info_.alternative_service()); | 
 |     // Reset error status for Jobs after reporting brokenness. | 
 |     ResetErrorStatusForJobs(); | 
 |     return; | 
 |   } | 
 |  | 
 |   // Report brokenness if alternative job failed. | 
 |   base::UmaHistogramSparse("Net.AlternateServiceFailed", | 
 |                            -alternative_job_net_error_); | 
 |  | 
 |   if (alternative_job_net_error_ == ERR_NETWORK_CHANGED || | 
 |       alternative_job_net_error_ == ERR_INTERNET_DISCONNECTED) { | 
 |     // No need to mark alternative service as broken. | 
 |     // Reset error status for Jobs. | 
 |     ResetErrorStatusForJobs(); | 
 |     return; | 
 |   } | 
 |  | 
 |   HistogramBrokenAlternateProtocolLocation( | 
 |       BROKEN_ALTERNATE_PROTOCOL_LOCATION_HTTP_STREAM_FACTORY_JOB_ALT); | 
 |   session_->http_server_properties()->MarkAlternativeServiceBroken( | 
 |       alternative_service_info_.alternative_service()); | 
 |   // Reset error status for Jobs after reporting brokenness. | 
 |   ResetErrorStatusForJobs(); | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::MaybeNotifyFactoryOfCompletion() { | 
 |   if (!main_job_ && !alternative_job_) { | 
 |     // Both jobs are gone, report brokenness if apply. Error status for Jobs | 
 |     // will be reset after reporting to avoid redundant reporting. | 
 |     MaybeReportBrokenAlternativeService(); | 
 |   } | 
 |  | 
 |   if (!request_ && !main_job_ && !alternative_job_) { | 
 |     DCHECK(!bound_job_); | 
 |     factory_->OnJobControllerComplete(this); | 
 |   } | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::NotifyRequestFailed(int rv) { | 
 |   if (!request_) | 
 |     return; | 
 |   delegate_->OnStreamFailed(rv, NetErrorDetails(), server_ssl_config_); | 
 | } | 
 |  | 
 | GURL HttpStreamFactory::JobController::ApplyHostMappingRules( | 
 |     const GURL& url, | 
 |     HostPortPair* endpoint) { | 
 |   if (session_->params().host_mapping_rules.RewriteHost(endpoint)) { | 
 |     url::Replacements<char> replacements; | 
 |     const std::string port_str = base::UintToString(endpoint->port()); | 
 |     replacements.SetPort(port_str.c_str(), url::Component(0, port_str.size())); | 
 |     replacements.SetHost(endpoint->host().c_str(), | 
 |                          url::Component(0, endpoint->host().size())); | 
 |     return url.ReplaceComponents(replacements); | 
 |   } | 
 |   return url; | 
 | } | 
 |  | 
 | AlternativeServiceInfo | 
 | HttpStreamFactory::JobController::GetAlternativeServiceInfoFor( | 
 |     const HttpRequestInfo& request_info, | 
 |     HttpStreamRequest::Delegate* delegate, | 
 |     HttpStreamRequest::StreamType stream_type) { | 
 |   if (!enable_alternative_services_) | 
 |     return AlternativeServiceInfo(); | 
 |  | 
 |   AlternativeServiceInfo alternative_service_info = | 
 |       GetAlternativeServiceInfoInternal(request_info, delegate, stream_type); | 
 |   AlternativeServiceType type; | 
 |   if (alternative_service_info.protocol() == kProtoUnknown) { | 
 |     type = NO_ALTERNATIVE_SERVICE; | 
 |   } else if (alternative_service_info.protocol() == kProtoQUIC) { | 
 |     if (request_info.url.host_piece() == | 
 |         alternative_service_info.alternative_service().host) { | 
 |       type = QUIC_SAME_DESTINATION; | 
 |     } else { | 
 |       type = QUIC_DIFFERENT_DESTINATION; | 
 |     } | 
 |   } else { | 
 |     if (request_info.url.host_piece() == | 
 |         alternative_service_info.alternative_service().host) { | 
 |       type = NOT_QUIC_SAME_DESTINATION; | 
 |     } else { | 
 |       type = NOT_QUIC_DIFFERENT_DESTINATION; | 
 |     } | 
 |   } | 
 |   UMA_HISTOGRAM_ENUMERATION("Net.AlternativeServiceTypeForRequest", type, | 
 |                             MAX_ALTERNATIVE_SERVICE_TYPE); | 
 |   return alternative_service_info; | 
 | } | 
 |  | 
 | AlternativeServiceInfo | 
 | HttpStreamFactory::JobController::GetAlternativeServiceInfoInternal( | 
 |     const HttpRequestInfo& request_info, | 
 |     HttpStreamRequest::Delegate* delegate, | 
 |     HttpStreamRequest::StreamType stream_type) { | 
 |   GURL original_url = request_info.url; | 
 |  | 
 |   if (!original_url.SchemeIs(url::kHttpsScheme)) | 
 |     return AlternativeServiceInfo(); | 
 |  | 
 |   url::SchemeHostPort origin(original_url); | 
 |   HttpServerProperties& http_server_properties = | 
 |       *session_->http_server_properties(); | 
 |   const AlternativeServiceInfoVector alternative_service_info_vector = | 
 |       http_server_properties.GetAlternativeServiceInfos(origin); | 
 | #if defined(STARBOARD) | 
 |   // This block of code suggests QUIC connection for initial requests to a | 
 |   // stranger host. This method is proven to provide performance benefit while | 
 |   // still enabling Cobalt network module to fall back on TCP connection when | 
 |   // QUIC fails or is too slow. | 
 |   if (alternative_service_info_vector.empty() && session_->IsQuicEnabled() && | 
 |       session_->UseQuicForUnknownOrigin()) { | 
 |     if (origin.port() == kDefaultQUICServerPort) { | 
 |       return AlternativeServiceInfo::CreateQuicAlternativeServiceInfo( | 
 |           AlternativeService(net::kProtoQUIC, origin.host(), | 
 |                              kDefaultQUICServerPort), | 
 |           base::Time::Max(), {quic::QUIC_VERSION_46}); | 
 |     } | 
 |     return AlternativeServiceInfo(); | 
 |   } | 
 | #else | 
 |   if (alternative_service_info_vector.empty()) | 
 |     return AlternativeServiceInfo(); | 
 | #endif | 
 |  | 
 |   bool quic_advertised = false; | 
 |   bool quic_all_broken = true; | 
 |  | 
 |   // First alternative service that is not marked as broken. | 
 |   AlternativeServiceInfo first_alternative_service_info; | 
 |  | 
 |   for (const AlternativeServiceInfo& alternative_service_info : | 
 |        alternative_service_info_vector) { | 
 |     DCHECK(IsAlternateProtocolValid(alternative_service_info.protocol())); | 
 |     if (!quic_advertised && alternative_service_info.protocol() == kProtoQUIC) | 
 |       quic_advertised = true; | 
 |     if (http_server_properties.IsAlternativeServiceBroken( | 
 |             alternative_service_info.alternative_service())) { | 
 |       HistogramAlternateProtocolUsage(ALTERNATE_PROTOCOL_USAGE_BROKEN, false); | 
 |       continue; | 
 |     } | 
 |  | 
 |     // Some shared unix systems may have user home directories (like | 
 |     // http://foo.com/~mike) which allow users to emit headers.  This is a bad | 
 |     // idea already, but with Alternate-Protocol, it provides the ability for a | 
 |     // single user on a multi-user system to hijack the alternate protocol. | 
 |     // These systems also enforce ports <1024 as restricted ports.  So don't | 
 |     // allow protocol upgrades to user-controllable ports. | 
 |     const int kUnrestrictedPort = 1024; | 
 |     if (!session_->params().enable_user_alternate_protocol_ports && | 
 |         (alternative_service_info.alternative_service().port >= | 
 |              kUnrestrictedPort && | 
 |          origin.port() < kUnrestrictedPort)) | 
 |       continue; | 
 |  | 
 |     if (alternative_service_info.protocol() == kProtoHTTP2) { | 
 |       if (!session_->params().enable_http2_alternative_service) | 
 |         continue; | 
 |  | 
 |       // Cache this entry if we don't have a non-broken Alt-Svc yet. | 
 |       if (first_alternative_service_info.protocol() == kProtoUnknown) | 
 |         first_alternative_service_info = alternative_service_info; | 
 |       continue; | 
 |     } | 
 |  | 
 |     DCHECK_EQ(kProtoQUIC, alternative_service_info.protocol()); | 
 |     quic_all_broken = false; | 
 |     if (!session_->IsQuicEnabled()) | 
 |       continue; | 
 |  | 
 |     if (stream_type == HttpStreamRequest::BIDIRECTIONAL_STREAM && | 
 |         session_->params().quic_disable_bidirectional_streams) { | 
 |       continue; | 
 |     } | 
 |  | 
 |     if (!original_url.SchemeIs(url::kHttpsScheme)) | 
 |       continue; | 
 |  | 
 |     // If there is no QUIC version in the advertised versions that is | 
 |     // supported, ignore this entry. | 
 |     if (SelectQuicVersion(alternative_service_info.advertised_versions()) == | 
 |         quic::QUIC_VERSION_UNSUPPORTED) | 
 |       continue; | 
 |  | 
 | #if !defined(QUIC_DISABLED_FOR_STARBOARD) | 
 |     // Check whether there is an existing QUIC session to use for this origin. | 
 |     HostPortPair mapped_origin(origin.host(), origin.port()); | 
 |     ignore_result(ApplyHostMappingRules(original_url, &mapped_origin)); | 
 |     QuicSessionKey session_key(mapped_origin, request_info.privacy_mode, | 
 |                                request_info.socket_tag); | 
 |  | 
 |     HostPortPair destination(alternative_service_info.host_port_pair()); | 
 |     if (session_key.host() != destination.host() && | 
 |         !session_->params().quic_allow_remote_alt_svc) { | 
 |       continue; | 
 |     } | 
 |     ignore_result(ApplyHostMappingRules(original_url, &destination)); | 
 |  | 
 |     if (session_->quic_stream_factory()->CanUseExistingSession(session_key, | 
 |                                                                destination)) | 
 |       return alternative_service_info; | 
 |  | 
 |     if (!IsQuicWhitelistedForHost(destination.host())) | 
 |       continue; | 
 | #endif | 
 |  | 
 |     // Cache this entry if we don't have a non-broken Alt-Svc yet. | 
 |     if (first_alternative_service_info.protocol() == kProtoUnknown) | 
 |       first_alternative_service_info = alternative_service_info; | 
 |   } | 
 |  | 
 |   // Ask delegate to mark QUIC as broken for the origin. | 
 |   if (quic_advertised && quic_all_broken && delegate != nullptr) | 
 |     delegate->OnQuicBroken(); | 
 |  | 
 |   return first_alternative_service_info; | 
 | } | 
 |  | 
 | quic::QuicTransportVersion HttpStreamFactory::JobController::SelectQuicVersion( | 
 |     const quic::QuicTransportVersionVector& advertised_versions) { | 
 |   const quic::QuicTransportVersionVector& supported_versions = | 
 |       session_->params().quic_supported_versions; | 
 |   if (advertised_versions.empty()) | 
 |     return supported_versions[0]; | 
 |  | 
 |   for (const quic::QuicTransportVersion& supported : supported_versions) { | 
 |     for (const quic::QuicTransportVersion& advertised : advertised_versions) { | 
 |       if (supported == advertised) { | 
 |         DCHECK_NE(quic::QUIC_VERSION_UNSUPPORTED, supported); | 
 |         return supported; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   return quic::QUIC_VERSION_UNSUPPORTED; | 
 | } | 
 |  | 
 | bool HttpStreamFactory::JobController::ShouldCreateAlternativeProxyServerJob( | 
 |     const ProxyInfo& proxy_info, | 
 |     const GURL& url, | 
 |     ProxyInfo* alternative_proxy_info) const { | 
 |   DCHECK(alternative_proxy_info->is_empty()); | 
 |  | 
 |   if (!enable_alternative_services_) | 
 |     return false; | 
 |  | 
 |   if (!can_start_alternative_proxy_job_) { | 
 |     // Either an alternative service job or an alternative proxy server job has | 
 |     // already been started. | 
 |     return false; | 
 |   } | 
 |  | 
 |   if (proxy_info.is_empty() || proxy_info.is_direct() || proxy_info.is_quic()) { | 
 |     // Alternative proxy server job can be created only if |job| fetches the | 
 |     // |request_| through a non-QUIC proxy. | 
 |     return false; | 
 |   } | 
 |  | 
 |   if (!url.SchemeIs(url::kHttpScheme)) { | 
 |     // Only HTTP URLs can be fetched through alternative proxy server, since the | 
 |     // alternative proxy server may not support fetching of URLs with other | 
 |     // schemes. | 
 |     return false; | 
 |   } | 
 |  | 
 |   alternative_proxy_info->UseProxyServer(proxy_info.alternative_proxy()); | 
 |   if (alternative_proxy_info->is_empty()) | 
 |     return false; | 
 |  | 
 |   DCHECK(alternative_proxy_info->proxy_server() != proxy_info.proxy_server()); | 
 |  | 
 |   if (!alternative_proxy_info->is_https() && | 
 |       !alternative_proxy_info->is_quic()) { | 
 |     // Alternative proxy server should be a secure server. | 
 |     return false; | 
 |   } | 
 |  | 
 |   if (alternative_proxy_info->is_quic()) { | 
 |     // Check that QUIC is enabled globally. | 
 |     if (!session_->IsQuicEnabled()) | 
 |       return false; | 
 |   } | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | void HttpStreamFactory::JobController::ReportAlternateProtocolUsage( | 
 |     Job* job) const { | 
 |   DCHECK(main_job_ && alternative_job_); | 
 |  | 
 |   bool proxy_server_used = | 
 |       alternative_job_->alternative_proxy_server().is_quic(); | 
 |  | 
 |   if (job == main_job_.get()) { | 
 |     HistogramAlternateProtocolUsage(ALTERNATE_PROTOCOL_USAGE_LOST_RACE, | 
 |                                     proxy_server_used); | 
 |     return; | 
 |   } | 
 |  | 
 |   DCHECK_EQ(alternative_job_.get(), job); | 
 |   if (job->using_existing_quic_session()) { | 
 |     HistogramAlternateProtocolUsage(ALTERNATE_PROTOCOL_USAGE_NO_RACE, | 
 |                                     proxy_server_used); | 
 |     return; | 
 |   } | 
 |  | 
 |   HistogramAlternateProtocolUsage(ALTERNATE_PROTOCOL_USAGE_WON_RACE, | 
 |                                   proxy_server_used); | 
 | } | 
 |  | 
 | bool HttpStreamFactory::JobController::IsJobOrphaned(Job* job) const { | 
 |   return !request_ || (job_bound_ && bound_job_ != job); | 
 | } | 
 |  | 
 | int HttpStreamFactory::JobController::ReconsiderProxyAfterError(Job* job, | 
 |                                                                 int error) { | 
 |   // ReconsiderProxyAfterError() should only be called when the last job fails. | 
 |   DCHECK(!(alternative_job_ && main_job_)); | 
 |   DCHECK(!proxy_resolve_request_); | 
 |   DCHECK(session_); | 
 |  | 
 |   if (!job->should_reconsider_proxy()) | 
 |     return error; | 
 |  | 
 |   DCHECK(!job->alternative_proxy_server().is_valid()); | 
 |  | 
 |   if (request_info_.load_flags & LOAD_BYPASS_PROXY) | 
 |     return error; | 
 |  | 
 |   if (proxy_info_.is_https() && proxy_ssl_config_.send_client_cert) { | 
 |     session_->ssl_client_auth_cache()->Remove( | 
 |         proxy_info_.proxy_server().host_port_pair()); | 
 |   } | 
 |  | 
 |   if (!proxy_info_.Fallback(error, net_log_)) { | 
 |     // If there is no more proxy to fallback to, fail the transaction | 
 |     // with the last connection error we got. | 
 |     return error; | 
 |   } | 
 |  | 
 |   if (!job->using_quic()) | 
 |     RemoveRequestFromSpdySessionRequestMap(); | 
 |   // Abandon all Jobs and start over. | 
 |   job_bound_ = false; | 
 |   bound_job_ = nullptr; | 
 |   alternative_job_.reset(); | 
 |   main_job_.reset(); | 
 |   ResetErrorStatusForJobs(); | 
 |   // Also resets states that related to the old main job. In particular, | 
 |   // cancels |resume_main_job_callback_| so there won't be any delayed | 
 |   // ResumeMainJob() left in the task queue. | 
 |   resume_main_job_callback_.Cancel(); | 
 |   main_job_is_resumed_ = false; | 
 |   main_job_is_blocked_ = false; | 
 |  | 
 |   next_state_ = STATE_RESOLVE_PROXY_COMPLETE; | 
 |   return OK; | 
 | } | 
 |  | 
 | bool HttpStreamFactory::JobController::IsQuicWhitelistedForHost( | 
 |     const std::string& host) { | 
 |   const base::flat_set<std::string>& host_whitelist = | 
 |       session_->params().quic_host_whitelist; | 
 |   if (host_whitelist.empty()) | 
 |     return true; | 
 |  | 
 |   std::string lowered_host = base::ToLowerASCII(host); | 
 |   return base::ContainsKey(host_whitelist, lowered_host); | 
 | } | 
 |  | 
 | }  // namespace net |