blob: 5b524b058b32a68f5541d1fd69c736fee37d457d [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// 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.h"
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/sparse_histogram.h"
#include "base/notreached.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/task/single_thread_task_runner.h"
#include "base/values.h"
#include "build/build_config.h"
#include "net/base/host_port_pair.h"
#include "net/base/port_util.h"
#include "net/base/proxy_delegate.h"
#include "net/base/tracing.h"
#include "net/cert/cert_verifier.h"
#include "net/dns/public/secure_dns_policy.h"
#include "net/http/bidirectional_stream_impl.h"
#include "net/http/http_basic_stream.h"
#include "net/http/http_network_session.h"
#include "net/http/http_request_info.h"
#include "net/http/http_server_properties.h"
#include "net/http/http_stream_factory.h"
#include "net/http/proxy_fallback.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_source_type.h"
#include "net/quic/bidirectional_stream_quic_impl.h"
#include "net/quic/quic_http_stream.h"
#include "net/socket/client_socket_handle.h"
#include "net/socket/client_socket_pool_manager.h"
#include "net/socket/connect_job.h"
#include "net/socket/ssl_client_socket.h"
#include "net/socket/stream_socket.h"
#include "net/spdy/bidirectional_stream_spdy_impl.h"
#include "net/spdy/http2_push_promise_index.h"
#include "net/spdy/spdy_http_stream.h"
#include "net/spdy/spdy_session.h"
#include "net/ssl/ssl_cert_request_info.h"
#include "net/third_party/quiche/src/quiche/spdy/core/spdy_protocol.h"
#include "url/scheme_host_port.h"
#include "url/url_constants.h"
namespace net {
namespace {
// Experiment to preconnect only one connection if HttpServerProperties is
// not supported or initialized.
BASE_FEATURE(kLimitEarlyPreconnectsExperiment,
"LimitEarlyPreconnects",
base::FEATURE_ENABLED_BY_DEFAULT);
} // namespace
const char* NetLogHttpStreamJobType(HttpStreamFactory::JobType job_type) {
switch (job_type) {
case HttpStreamFactory::MAIN:
return "main";
case HttpStreamFactory::ALTERNATIVE:
return "alternative";
case HttpStreamFactory::DNS_ALPN_H3:
return "dns_alpn_h3";
case HttpStreamFactory::PRECONNECT:
return "preconnect";
case HttpStreamFactory::PRECONNECT_DNS_ALPN_H3:
return "preconnect_dns_alpn_h3";
}
return "";
}
// Returns parameters associated with the start of a HTTP stream job.
base::Value::Dict NetLogHttpStreamJobParams(const NetLogSource& source,
const GURL& original_url,
const GURL& url,
bool expect_spdy,
bool using_quic,
HttpStreamFactory::JobType job_type,
RequestPriority priority) {
base::Value::Dict dict;
if (source.IsValid())
source.AddToEventParameters(dict);
dict.Set("original_url", original_url.DeprecatedGetOriginAsURL().spec());
dict.Set("url", url.DeprecatedGetOriginAsURL().spec());
dict.Set("expect_spdy", expect_spdy);
dict.Set("using_quic", using_quic);
dict.Set("priority", RequestPriorityToString(priority));
dict.Set("type", NetLogHttpStreamJobType(job_type));
return dict;
}
// Returns parameters associated with the ALPN protocol of a HTTP stream.
base::Value::Dict NetLogHttpStreamProtoParams(NextProto negotiated_protocol) {
base::Value::Dict dict;
dict.Set("proto", NextProtoToString(negotiated_protocol));
return dict;
}
HttpStreamFactory::Job::Job(Delegate* delegate,
JobType job_type,
HttpNetworkSession* session,
const HttpRequestInfo& request_info,
RequestPriority priority,
const ProxyInfo& proxy_info,
const SSLConfig& server_ssl_config,
const SSLConfig& proxy_ssl_config,
url::SchemeHostPort destination,
GURL origin_url,
NextProto alternative_protocol,
quic::ParsedQuicVersion quic_version,
bool is_websocket,
bool enable_ip_based_pooling,
NetLog* net_log)
: request_info_(request_info),
priority_(priority),
proxy_info_(proxy_info),
server_ssl_config_(server_ssl_config),
proxy_ssl_config_(proxy_ssl_config),
net_log_(
NetLogWithSource::Make(net_log, NetLogSourceType::HTTP_STREAM_JOB)),
io_callback_(
base::BindRepeating(&Job::OnIOComplete, base::Unretained(this))),
connection_(std::make_unique<ClientSocketHandle>()),
session_(session),
destination_(std::move(destination)),
origin_url_(origin_url),
is_websocket_(is_websocket),
try_websocket_over_http2_(
is_websocket_ && origin_url_.SchemeIs(url::kWssScheme) &&
// TODO(https://crbug.com/1277306): Remove the proxy check.
proxy_info_.is_direct()),
// Don't use IP connection pooling for HTTP over HTTPS proxies. It doesn't
// get us much, and testing it is more effort than its worth.
enable_ip_based_pooling_(
enable_ip_based_pooling &&
!(proxy_info_.proxy_server().is_secure_http_like() &&
origin_url_.SchemeIs(url::kHttpScheme))),
delegate_(delegate),
job_type_(job_type),
using_ssl_(origin_url_.SchemeIs(url::kHttpsScheme) ||
origin_url_.SchemeIs(url::kWssScheme)),
using_quic_(alternative_protocol == kProtoQUIC ||
(ShouldForceQuic(session,
destination_,
proxy_info,
using_ssl_,
is_websocket_)) ||
job_type == DNS_ALPN_H3 ||
job_type == PRECONNECT_DNS_ALPN_H3),
quic_version_(quic_version),
expect_spdy_(alternative_protocol == kProtoHTTP2 && !using_quic_),
quic_request_(session_->quic_stream_factory()),
pushed_stream_id_(kNoPushedStreamFound),
spdy_session_key_(
using_quic_
? SpdySessionKey()
: GetSpdySessionKey(proxy_info_.proxy_server(),
origin_url_,
request_info_.privacy_mode,
request_info_.socket_tag,
request_info_.network_anonymization_key,
request_info_.secure_dns_policy)) {
// Websocket `destination` schemes should be converted to HTTP(S).
DCHECK(base::EqualsCaseInsensitiveASCII(destination_.scheme(),
url::kHttpScheme) ||
base::EqualsCaseInsensitiveASCII(destination_.scheme(),
url::kHttpsScheme));
// This class is specific to a single `ProxyServer`, so `proxy_info_` must be
// non-empty. Entries beyond the first are ignored. It should simply take a
// `ProxyServer`, but the full `ProxyInfo` is passed back to
// `HttpNetworkTransaction`, which consumes additional fields.
DCHECK(!proxy_info_.is_empty());
// QUIC can only be spoken to servers, never to proxies.
if (alternative_protocol == kProtoQUIC)
DCHECK(proxy_info_.is_direct());
// The Job is forced to use QUIC without a designated version, try the
// preferred QUIC version that is supported by default.
if (quic_version_ == quic::ParsedQuicVersion::Unsupported() &&
ShouldForceQuic(session, destination_, proxy_info, using_ssl_,
is_websocket_)) {
quic_version_ =
session->context().quic_context->params()->supported_versions[0];
}
if (using_quic_) {
DCHECK((quic_version_ != quic::ParsedQuicVersion::Unsupported()) ||
(job_type_ == DNS_ALPN_H3) || (job_type_ == PRECONNECT_DNS_ALPN_H3));
}
DCHECK(session);
if (alternative_protocol != kProtoUnknown) {
// If the alternative service protocol is specified, then the job type must
// be either ALTERNATIVE or PRECONNECT.
DCHECK(job_type_ == ALTERNATIVE || job_type_ == PRECONNECT);
}
if (expect_spdy_) {
DCHECK(origin_url_.SchemeIs(url::kHttpsScheme));
}
if (using_quic_) {
DCHECK(session_->IsQuicEnabled());
}
if (job_type_ == PRECONNECT || is_websocket_) {
DCHECK(request_info_.socket_tag == SocketTag());
}
if (is_websocket_) {
DCHECK(origin_url_.SchemeIsWSOrWSS());
} else {
DCHECK(!origin_url_.SchemeIsWSOrWSS());
}
const NetLogWithSource* delegate_net_log = delegate_->GetNetLog();
if (delegate_net_log) {
net_log_.BeginEvent(NetLogEventType::HTTP_STREAM_JOB, [&] {
return NetLogHttpStreamJobParams(
delegate_net_log->source(), request_info_.url, origin_url_,
expect_spdy_, using_quic_, job_type_, priority_);
});
delegate_net_log->AddEventReferencingSource(
NetLogEventType::HTTP_STREAM_REQUEST_STARTED_JOB, net_log_.source());
}
}
HttpStreamFactory::Job::~Job() {
net_log_.EndEvent(NetLogEventType::HTTP_STREAM_JOB);
// When we're in a partially constructed state, waiting for the user to
// provide certificate handling information or authentication, we can't reuse
// this stream at all.
if (next_state_ == STATE_WAITING_USER_ACTION) {
connection_->socket()->Disconnect();
connection_.reset();
}
// The stream could be in a partial state. It is not reusable.
if (stream_.get() && next_state_ != STATE_DONE)
stream_->Close(true /* not reusable */);
}
void HttpStreamFactory::Job::Start(HttpStreamRequest::StreamType stream_type) {
stream_type_ = stream_type;
StartInternal();
}
int HttpStreamFactory::Job::Preconnect(int num_streams) {
DCHECK_GT(num_streams, 0);
HttpServerProperties* http_server_properties =
session_->http_server_properties();
DCHECK(http_server_properties);
// Preconnect one connection if either of the following is true:
// (1) kLimitEarlyPreconnectsStreamExperiment is turned on,
// HttpServerProperties is not initialized, and url scheme is cryptographic.
// (2) The server supports H2 or QUIC.
bool connect_one_stream =
base::FeatureList::IsEnabled(kLimitEarlyPreconnectsExperiment) &&
!http_server_properties->IsInitialized() &&
request_info_.url.SchemeIsCryptographic();
if (connect_one_stream || http_server_properties->SupportsRequestPriority(
url::SchemeHostPort(request_info_.url),
request_info_.network_anonymization_key)) {
num_streams_ = 1;
} else {
num_streams_ = num_streams;
}
return StartInternal();
}
int HttpStreamFactory::Job::RestartTunnelWithProxyAuth() {
DCHECK(establishing_tunnel_);
DCHECK(restart_with_auth_callback_);
std::move(restart_with_auth_callback_).Run();
return ERR_IO_PENDING;
}
LoadState HttpStreamFactory::Job::GetLoadState() const {
switch (next_state_) {
case STATE_INIT_CONNECTION_COMPLETE:
case STATE_CREATE_STREAM_COMPLETE:
return using_quic_ ? LOAD_STATE_CONNECTING : connection_->GetLoadState();
default:
return LOAD_STATE_IDLE;
}
}
void HttpStreamFactory::Job::Resume() {
DCHECK_EQ(job_type_, MAIN);
DCHECK_EQ(next_state_, STATE_WAIT_COMPLETE);
OnIOComplete(OK);
}
void HttpStreamFactory::Job::Orphan() {
DCHECK(job_type_ == ALTERNATIVE || job_type_ == DNS_ALPN_H3);
net_log_.AddEvent(NetLogEventType::HTTP_STREAM_JOB_ORPHANED);
// Watching for SPDY sessions isn't supported on orphaned jobs.
// TODO(mmenke): Fix that.
spdy_session_request_.reset();
}
void HttpStreamFactory::Job::SetPriority(RequestPriority priority) {
priority_ = priority;
// Ownership of |connection_| is passed to the newly created stream
// or H2 session in DoCreateStream(), and the consumer is not
// notified immediately, so this call may occur when |connection_|
// is null.
//
// Note that streams are created without a priority associated with them,
// and it is up to the consumer to set their priority via
// HttpStream::InitializeStream(). So there is no need for this code
// to propagate priority changes to the newly created stream.
if (connection_ && connection_->is_initialized())
connection_->SetPriority(priority);
// TODO(akalin): Maybe Propagate this to the preconnect state.
}
bool HttpStreamFactory::Job::HasAvailableSpdySession() const {
return !using_quic_ && CanUseExistingSpdySession() &&
session_->spdy_session_pool()->HasAvailableSession(spdy_session_key_,
is_websocket_);
}
bool HttpStreamFactory::Job::HasAvailableQuicSession() const {
if (!using_quic_)
return false;
bool require_dns_https_alpn =
(job_type_ == DNS_ALPN_H3) || (job_type_ == PRECONNECT_DNS_ALPN_H3);
return quic_request_.CanUseExistingSession(
origin_url_, request_info_.privacy_mode, request_info_.socket_tag,
request_info_.network_anonymization_key, request_info_.secure_dns_policy,
require_dns_https_alpn, destination_);
}
bool HttpStreamFactory::Job::TargettedSocketGroupHasActiveSocket() const {
DCHECK(!using_quic_);
DCHECK(!is_websocket_);
ClientSocketPool* pool = session_->GetSocketPool(
HttpNetworkSession::NORMAL_SOCKET_POOL, proxy_info_.proxy_server());
DCHECK(pool);
ClientSocketPool::GroupId connection_group(
destination_, request_info_.privacy_mode,
request_info_.network_anonymization_key, request_info_.secure_dns_policy);
return pool->HasActiveSocket(connection_group);
}
bool HttpStreamFactory::Job::was_alpn_negotiated() const {
return was_alpn_negotiated_;
}
NextProto HttpStreamFactory::Job::negotiated_protocol() const {
return negotiated_protocol_;
}
bool HttpStreamFactory::Job::using_spdy() const {
return using_spdy_;
}
const ProxyInfo& HttpStreamFactory::Job::proxy_info() const {
return proxy_info_;
}
ResolveErrorInfo HttpStreamFactory::Job::resolve_error_info() const {
return resolve_error_info_;
}
void HttpStreamFactory::Job::GetSSLInfo(SSLInfo* ssl_info) {
DCHECK(using_ssl_);
DCHECK(!establishing_tunnel_);
DCHECK(connection_.get() && connection_->socket());
connection_->socket()->GetSSLInfo(ssl_info);
}
// static
bool HttpStreamFactory::Job::ShouldForceQuic(
HttpNetworkSession* session,
const url::SchemeHostPort& destination,
const ProxyInfo& proxy_info,
bool using_ssl,
bool is_websocket) {
if (!session->IsQuicEnabled())
return false;
if (is_websocket)
return false;
// If this is going through a QUIC proxy, only force QUIC for insecure
// requests. If the request is secure, a tunnel will be needed, and those are
// handled by the socket pools, using an HttpProxyConnectJob.
if (proxy_info.is_quic())
return !using_ssl;
const QuicParams* quic_params = session->context().quic_context->params();
// TODO(crbug.com/1206799): Consider converting `origins_to_force_quic_on` to
// use url::SchemeHostPort.
return (base::Contains(quic_params->origins_to_force_quic_on,
HostPortPair()) ||
base::Contains(quic_params->origins_to_force_quic_on,
HostPortPair::FromSchemeHostPort(destination))) &&
proxy_info.is_direct() &&
base::EqualsCaseInsensitiveASCII(destination.scheme(),
url::kHttpsScheme);
}
// static
SpdySessionKey HttpStreamFactory::Job::GetSpdySessionKey(
const ProxyServer& proxy_server,
const GURL& origin_url,
PrivacyMode privacy_mode,
const SocketTag& socket_tag,
const NetworkAnonymizationKey& network_anonymization_key,
SecureDnsPolicy secure_dns_policy) {
// In the case that we're using an HTTPS proxy for an HTTP url, look for a
// HTTP/2 proxy session *to* the proxy, instead of to the origin server.
if (proxy_server.is_https() && origin_url.SchemeIs(url::kHttpScheme)) {
return SpdySessionKey(proxy_server.host_port_pair(), ProxyServer::Direct(),
PRIVACY_MODE_DISABLED,
SpdySessionKey::IsProxySession::kTrue, socket_tag,
network_anonymization_key, secure_dns_policy);
}
return SpdySessionKey(HostPortPair::FromURL(origin_url), proxy_server,
privacy_mode, SpdySessionKey::IsProxySession::kFalse,
socket_tag, network_anonymization_key,
secure_dns_policy);
}
bool HttpStreamFactory::Job::CanUseExistingSpdySession() const {
DCHECK(!using_quic_);
if (proxy_info_.is_direct() &&
session_->http_server_properties()->RequiresHTTP11(
url::SchemeHostPort(request_info_.url),
request_info_.network_anonymization_key)) {
return false;
}
if (is_websocket_)
return try_websocket_over_http2_;
DCHECK(origin_url_.SchemeIsHTTPOrHTTPS());
// We need to make sure that if a HTTP/2 session was created for
// https://somehost/ then we do not use that session for http://somehost:443/.
// The only time we can use an existing session is if the request URL is
// https (the normal case) or if we are connecting to a HTTP/2 proxy.
// https://crbug.com/133176
return origin_url_.SchemeIs(url::kHttpsScheme) ||
proxy_info_.proxy_server().is_https();
}
void HttpStreamFactory::Job::OnStreamReadyCallback() {
DCHECK(stream_.get());
DCHECK_NE(job_type_, PRECONNECT);
DCHECK_NE(job_type_, PRECONNECT_DNS_ALPN_H3);
DCHECK(!is_websocket_ || try_websocket_over_http2_);
MaybeCopyConnectionAttemptsFromHandle();
delegate_->OnStreamReady(this, server_ssl_config_);
// |this| may be deleted after this call.
}
void HttpStreamFactory::Job::OnWebSocketHandshakeStreamReadyCallback() {
DCHECK(websocket_stream_);
DCHECK_NE(job_type_, PRECONNECT);
DCHECK_NE(job_type_, PRECONNECT_DNS_ALPN_H3);
DCHECK(is_websocket_);
MaybeCopyConnectionAttemptsFromHandle();
delegate_->OnWebSocketHandshakeStreamReady(
this, server_ssl_config_, proxy_info_, std::move(websocket_stream_));
// |this| may be deleted after this call.
}
void HttpStreamFactory::Job::OnBidirectionalStreamImplReadyCallback() {
DCHECK(bidirectional_stream_impl_);
MaybeCopyConnectionAttemptsFromHandle();
delegate_->OnBidirectionalStreamImplReady(this, server_ssl_config_,
proxy_info_);
// |this| may be deleted after this call.
}
void HttpStreamFactory::Job::OnStreamFailedCallback(int result) {
DCHECK_NE(job_type_, PRECONNECT);
DCHECK_NE(job_type_, PRECONNECT_DNS_ALPN_H3);
MaybeCopyConnectionAttemptsFromHandle();
delegate_->OnStreamFailed(this, result, server_ssl_config_);
// |this| may be deleted after this call.
}
void HttpStreamFactory::Job::OnCertificateErrorCallback(
int result,
const SSLInfo& ssl_info) {
DCHECK_NE(job_type_, PRECONNECT);
DCHECK_NE(job_type_, PRECONNECT_DNS_ALPN_H3);
DCHECK(!spdy_session_request_);
MaybeCopyConnectionAttemptsFromHandle();
delegate_->OnCertificateError(this, result, server_ssl_config_, ssl_info);
// |this| may be deleted after this call.
}
void HttpStreamFactory::Job::OnNeedsProxyAuthCallback(
const HttpResponseInfo& response,
HttpAuthController* auth_controller,
base::OnceClosure restart_with_auth_callback) {
DCHECK_NE(job_type_, PRECONNECT);
DCHECK_NE(job_type_, PRECONNECT_DNS_ALPN_H3);
DCHECK(establishing_tunnel_);
DCHECK(!restart_with_auth_callback_);
restart_with_auth_callback_ = std::move(restart_with_auth_callback);
// This is called out of band, so need to abort the SpdySessionRequest to
// prevent being passed a new session while waiting on proxy auth credentials.
spdy_session_request_.reset();
delegate_->OnNeedsProxyAuth(this, response, server_ssl_config_, proxy_info_,
auth_controller);
// |this| may be deleted after this call.
}
void HttpStreamFactory::Job::OnNeedsClientAuthCallback(
SSLCertRequestInfo* cert_info) {
DCHECK_NE(job_type_, PRECONNECT);
DCHECK_NE(job_type_, PRECONNECT_DNS_ALPN_H3);
DCHECK(!spdy_session_request_);
delegate_->OnNeedsClientAuth(this, server_ssl_config_, cert_info);
// |this| may be deleted after this call.
}
void HttpStreamFactory::Job::OnPreconnectsComplete(int result) {
delegate_->OnPreconnectsComplete(this, result);
// |this| may be deleted after this call.
}
void HttpStreamFactory::Job::OnIOComplete(int result) {
RunLoop(result);
}
void HttpStreamFactory::Job::RunLoop(int result) {
result = DoLoop(result);
if (result == ERR_IO_PENDING)
return;
// Stop watching for new SpdySessions, to avoid receiving a new SPDY session
// while doing anything other than waiting to establish a connection.
spdy_session_request_.reset();
if ((job_type_ == PRECONNECT) || (job_type_ == PRECONNECT_DNS_ALPN_H3)) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&HttpStreamFactory::Job::OnPreconnectsComplete,
ptr_factory_.GetWeakPtr(), result));
return;
}
if (IsCertificateError(result)) {
// Retrieve SSL information from the socket.
SSLInfo ssl_info;
GetSSLInfo(&ssl_info);
next_state_ = STATE_WAITING_USER_ACTION;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&HttpStreamFactory::Job::OnCertificateErrorCallback,
ptr_factory_.GetWeakPtr(), result, ssl_info));
return;
}
switch (result) {
case ERR_SSL_CLIENT_AUTH_CERT_NEEDED:
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
&Job::OnNeedsClientAuthCallback, ptr_factory_.GetWeakPtr(),
base::RetainedRef(connection_->ssl_cert_request_info())));
return;
case OK:
next_state_ = STATE_DONE;
if (is_websocket_) {
DCHECK(websocket_stream_);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&Job::OnWebSocketHandshakeStreamReadyCallback,
ptr_factory_.GetWeakPtr()));
} else if (stream_type_ == HttpStreamRequest::BIDIRECTIONAL_STREAM) {
if (!bidirectional_stream_impl_) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&Job::OnStreamFailedCallback,
ptr_factory_.GetWeakPtr(), ERR_FAILED));
} else {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&Job::OnBidirectionalStreamImplReadyCallback,
ptr_factory_.GetWeakPtr()));
}
} else {
DCHECK(stream_.get());
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&Job::OnStreamReadyCallback,
ptr_factory_.GetWeakPtr()));
}
return;
default:
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&Job::OnStreamFailedCallback,
ptr_factory_.GetWeakPtr(), result));
return;
}
}
int HttpStreamFactory::Job::DoLoop(int result) {
DCHECK_NE(next_state_, STATE_NONE);
int rv = result;
do {
State state = next_state_;
next_state_ = STATE_NONE;
switch (state) {
case STATE_START:
DCHECK_EQ(OK, rv);
rv = DoStart();
break;
case STATE_WAIT:
DCHECK_EQ(OK, rv);
rv = DoWait();
break;
case STATE_WAIT_COMPLETE:
rv = DoWaitComplete(rv);
break;
case STATE_INIT_CONNECTION:
DCHECK_EQ(OK, rv);
rv = DoInitConnection();
break;
case STATE_INIT_CONNECTION_COMPLETE:
rv = DoInitConnectionComplete(rv);
break;
case STATE_WAITING_USER_ACTION:
rv = DoWaitingUserAction(rv);
break;
case STATE_CREATE_STREAM:
DCHECK_EQ(OK, rv);
rv = DoCreateStream();
break;
case STATE_CREATE_STREAM_COMPLETE:
rv = DoCreateStreamComplete(rv);
break;
default:
NOTREACHED() << "bad state";
rv = ERR_FAILED;
break;
}
} while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
return rv;
}
int HttpStreamFactory::Job::StartInternal() {
CHECK_EQ(STATE_NONE, next_state_);
next_state_ = STATE_START;
RunLoop(OK);
return ERR_IO_PENDING;
}
int HttpStreamFactory::Job::DoStart() {
// Don't connect to restricted ports.
if (!IsPortAllowedForScheme(destination_.port(),
request_info_.url.scheme_piece())) {
return ERR_UNSAFE_PORT;
}
if (!session_->params().enable_quic_proxies_for_https_urls &&
proxy_info_.is_quic() && request_info_.url.SchemeIsCryptographic()) {
return ERR_NOT_IMPLEMENTED;
}
next_state_ = STATE_WAIT;
return OK;
}
int HttpStreamFactory::Job::DoWait() {
next_state_ = STATE_WAIT_COMPLETE;
bool should_wait = delegate_->ShouldWait(this);
net_log_.AddEntryWithBoolParams(NetLogEventType::HTTP_STREAM_JOB_WAITING,
NetLogEventPhase::BEGIN, "should_wait",
should_wait);
if (should_wait)
return ERR_IO_PENDING;
return OK;
}
int HttpStreamFactory::Job::DoWaitComplete(int result) {
net_log_.EndEvent(NetLogEventType::HTTP_STREAM_JOB_WAITING);
DCHECK_EQ(OK, result);
next_state_ = STATE_INIT_CONNECTION;
return OK;
}
void HttpStreamFactory::Job::ResumeInitConnection() {
if (init_connection_already_resumed_)
return;
DCHECK_EQ(next_state_, STATE_INIT_CONNECTION);
net_log_.AddEvent(NetLogEventType::HTTP_STREAM_JOB_RESUME_INIT_CONNECTION);
init_connection_already_resumed_ = true;
OnIOComplete(OK);
}
int HttpStreamFactory::Job::DoInitConnection() {
net_log_.BeginEvent(NetLogEventType::HTTP_STREAM_JOB_INIT_CONNECTION);
int result = DoInitConnectionImpl();
if (!expect_on_quic_session_created_ && !expect_on_quic_host_resolution_) {
delegate_->OnConnectionInitialized(this, result);
}
return result;
}
int HttpStreamFactory::Job::DoInitConnectionImpl() {
DCHECK(!connection_->is_initialized());
if (using_quic_ && !proxy_info_.is_quic() && !proxy_info_.is_direct()) {
// QUIC can not be spoken to non-QUIC proxies. This error should not be
// user visible, because the non-alternative Job should be resumed.
return ERR_NO_SUPPORTED_PROXIES;
}
DCHECK(proxy_info_.proxy_server().is_valid());
next_state_ = STATE_INIT_CONNECTION_COMPLETE;
if (proxy_info_.is_secure_http_like()) {
// Disable network fetches for HTTPS proxies, since the network requests
// are probably going to need to go through the proxy too.
proxy_ssl_config_.disable_cert_verification_network_fetches = true;
}
if (using_ssl_) {
// Prior to HTTP/2 and SPDY, some servers use TLS renegotiation to request
// TLS client authentication after the HTTP request was sent. Allow
// renegotiation for only those connections.
//
// Note that this does NOT implement the provision in
// https://http2.github.io/http2-spec/#rfc.section.9.2.1 which allows the
// server to request a renegotiation immediately before sending the
// connection preface as waiting for the preface would cost the round trip
// that False Start otherwise saves.
server_ssl_config_.renego_allowed_default = true;
server_ssl_config_.renego_allowed_for_protos.push_back(kProtoHTTP11);
}
server_ssl_config_.alpn_protos = session_->GetAlpnProtos();
proxy_ssl_config_.alpn_protos = session_->GetAlpnProtos();
server_ssl_config_.application_settings = session_->GetApplicationSettings();
proxy_ssl_config_.application_settings = session_->GetApplicationSettings();
server_ssl_config_.ignore_certificate_errors =
session_->params().ignore_certificate_errors;
proxy_ssl_config_.ignore_certificate_errors =
session_->params().ignore_certificate_errors;
// TODO(https://crbug.com/964642): Also enable 0-RTT for TLS proxies.
server_ssl_config_.early_data_enabled = session_->params().enable_early_data;
if (using_quic_)
return DoInitConnectionImplQuic();
// Check first if there is a pushed stream matching the request, or an HTTP/2
// connection this request can pool to. If so, then go straight to using
// that.
if (CanUseExistingSpdySession()) {
if (!is_websocket_) {
session_->spdy_session_pool()->push_promise_index()->ClaimPushedStream(
spdy_session_key_, origin_url_, request_info_,
&existing_spdy_session_, &pushed_stream_id_);
}
if (!existing_spdy_session_) {
if (!spdy_session_request_) {
// If not currently watching for an H2 session, use
// SpdySessionPool::RequestSession() to check for a session, and start
// watching for one.
bool should_throttle_connect = ShouldThrottleConnectForSpdy();
base::RepeatingClosure resume_callback =
should_throttle_connect
? base::BindRepeating(
&HttpStreamFactory::Job::ResumeInitConnection,
ptr_factory_.GetWeakPtr())
: base::RepeatingClosure();
bool is_blocking_request_for_session;
existing_spdy_session_ = session_->spdy_session_pool()->RequestSession(
spdy_session_key_, enable_ip_based_pooling_, is_websocket_,
net_log_, resume_callback, this, &spdy_session_request_,
&is_blocking_request_for_session);
if (!existing_spdy_session_ && should_throttle_connect &&
!is_blocking_request_for_session) {
net_log_.AddEvent(NetLogEventType::HTTP_STREAM_JOB_THROTTLED);
next_state_ = STATE_INIT_CONNECTION;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, resume_callback, base::Milliseconds(kHTTP2ThrottleMs));
return ERR_IO_PENDING;
}
} else if (enable_ip_based_pooling_) {
// If already watching for an H2 session, still need to check for an
// existing connection that can be reused through IP pooling, as those
// don't post session available notifications.
//
// TODO(mmenke): Make sessions created through IP pooling invoke the
// callback.
existing_spdy_session_ =
session_->spdy_session_pool()->FindAvailableSession(
spdy_session_key_, enable_ip_based_pooling_, is_websocket_,
net_log_);
}
}
if (existing_spdy_session_) {
// Stop watching for SpdySessions.
spdy_session_request_.reset();
// If we're preconnecting, but we already have a SpdySession, we don't
// actually need to preconnect any sockets, so we're done.
if (job_type_ == PRECONNECT)
return OK;
using_spdy_ = true;
next_state_ = STATE_CREATE_STREAM;
return OK;
}
}
if (proxy_info_.is_http_like())
establishing_tunnel_ = using_ssl_;
HttpServerProperties* http_server_properties =
session_->http_server_properties();
if (http_server_properties) {
http_server_properties->MaybeForceHTTP11(
url::SchemeHostPort(request_info_.url),
request_info_.network_anonymization_key, &server_ssl_config_);
if (proxy_info_.is_https()) {
http_server_properties->MaybeForceHTTP11(
url::SchemeHostPort(
url::kHttpsScheme,
proxy_info_.proxy_server().host_port_pair().host(),
proxy_info_.proxy_server().host_port_pair().port()),
request_info_.network_anonymization_key, &proxy_ssl_config_);
}
}
if (job_type_ == PRECONNECT) {
DCHECK(!is_websocket_);
DCHECK(request_info_.socket_tag == SocketTag());
// The lifeime of the preconnect tasks is not controlled by |connection_|.
// It may outlives |this|. So we can't use |io_callback_| which holds
// base::Unretained(this).
auto callback =
base::BindOnce(&Job::OnIOComplete, ptr_factory_.GetWeakPtr());
return PreconnectSocketsForHttpRequest(
destination_, request_info_.load_flags, priority_, session_,
proxy_info_, server_ssl_config_, proxy_ssl_config_,
request_info_.privacy_mode, request_info_.network_anonymization_key,
request_info_.secure_dns_policy, net_log_, num_streams_,
std::move(callback));
}
ClientSocketPool::ProxyAuthCallback proxy_auth_callback =
base::BindRepeating(&HttpStreamFactory::Job::OnNeedsProxyAuthCallback,
base::Unretained(this));
if (is_websocket_) {
DCHECK(request_info_.socket_tag == SocketTag());
DCHECK_EQ(SecureDnsPolicy::kAllow, request_info_.secure_dns_policy);
// Only offer HTTP/1.1 for WebSockets. Although RFC 8441 defines WebSockets
// over HTTP/2, a single WSS/HTTPS origin may support HTTP over HTTP/2
// without supporting WebSockets over HTTP/2. Offering HTTP/2 for a fresh
// connection would break such origins.
//
// However, still offer HTTP/1.1 rather than skipping ALPN entirely. While
// this will not change the application protocol (HTTP/1.1 is default), it
// provides hardens against cross-protocol attacks and allows for the False
// Start (RFC 7918) optimization.
SSLConfig websocket_server_ssl_config = server_ssl_config_;
websocket_server_ssl_config.alpn_protos = {kProtoHTTP11};
return InitSocketHandleForWebSocketRequest(
destination_, request_info_.load_flags, priority_, session_,
proxy_info_, websocket_server_ssl_config, proxy_ssl_config_,
request_info_.privacy_mode, request_info_.network_anonymization_key,
net_log_, connection_.get(), io_callback_, proxy_auth_callback);
}
return InitSocketHandleForHttpRequest(
destination_, request_info_.load_flags, priority_, session_, proxy_info_,
server_ssl_config_, proxy_ssl_config_, request_info_.privacy_mode,
request_info_.network_anonymization_key, request_info_.secure_dns_policy,
request_info_.socket_tag, net_log_, connection_.get(), io_callback_,
proxy_auth_callback);
}
int HttpStreamFactory::Job::DoInitConnectionImplQuic() {
url::SchemeHostPort destination;
SSLConfig* ssl_config;
GURL url(request_info_.url);
if (proxy_info_.is_quic()) {
ssl_config = &proxy_ssl_config_;
const HostPortPair& proxy_endpoint =
proxy_info_.proxy_server().host_port_pair();
destination = url::SchemeHostPort(url::kHttpsScheme, proxy_endpoint.host(),
proxy_endpoint.port());
url = destination.GetURL();
} else {
DCHECK(using_ssl_);
destination = destination_;
ssl_config = &server_ssl_config_;
}
DCHECK(url.SchemeIs(url::kHttpsScheme));
bool require_dns_https_alpn =
(job_type_ == DNS_ALPN_H3) || (job_type_ == PRECONNECT_DNS_ALPN_H3);
int rv = quic_request_.Request(
std::move(destination), quic_version_, request_info_.privacy_mode,
priority_, request_info_.socket_tag,
request_info_.network_anonymization_key, request_info_.secure_dns_policy,
proxy_info_.is_direct(), require_dns_https_alpn,
ssl_config->GetCertVerifyFlags(), url, net_log_, &net_error_details_,
base::BindOnce(&Job::OnFailedOnDefaultNetwork, ptr_factory_.GetWeakPtr()),
io_callback_);
if (rv == OK) {
using_existing_quic_session_ = true;
} else if (rv == ERR_IO_PENDING) {
// There's no available QUIC session. Inform the delegate how long to
// delay the main job.
delegate_->MaybeSetWaitTimeForMainJob(
quic_request_.GetTimeDelayForWaitingJob());
expect_on_quic_host_resolution_ = quic_request_.WaitForHostResolution(
base::BindOnce(&Job::OnQuicHostResolution, base::Unretained(this)));
expect_on_quic_session_created_ = quic_request_.WaitForQuicSessionCreation(
base::BindOnce(&Job::OnQuicSessionCreated, ptr_factory_.GetWeakPtr()));
}
return rv;
}
void HttpStreamFactory::Job::OnQuicSessionCreated(int result) {
DCHECK(expect_on_quic_session_created_);
expect_on_quic_session_created_ = false;
delegate_->OnConnectionInitialized(this, result);
}
void HttpStreamFactory::Job::OnQuicHostResolution(int result) {
DCHECK(expect_on_quic_host_resolution_);
expect_on_quic_host_resolution_ = false;
if (!expect_on_quic_session_created_) {
delegate_->OnConnectionInitialized(this, result);
}
}
void HttpStreamFactory::Job::OnFailedOnDefaultNetwork(int result) {
DCHECK(job_type_ == ALTERNATIVE || job_type_ == DNS_ALPN_H3);
DCHECK(using_quic_);
delegate_->OnFailedOnDefaultNetwork(this);
}
int HttpStreamFactory::Job::DoInitConnectionComplete(int result) {
net_log_.EndEvent(NetLogEventType::HTTP_STREAM_JOB_INIT_CONNECTION);
// No need to continue waiting for a session, once a connection is
// established.
spdy_session_request_.reset();
if ((job_type_ == PRECONNECT) || (job_type_ == PRECONNECT_DNS_ALPN_H3)) {
if (using_quic_)
return result;
DCHECK_EQ(OK, result);
return OK;
}
resolve_error_info_ = connection_->resolve_error_info();
// Determine the protocol (HTTP/1.1, HTTP/2, or HTTP/3). This covers both the
// origin and some proxy cases. First, if the URL is HTTPS (or WSS), we may
// negotiate HTTP/2 or HTTP/3 with the origin. Second, non-tunneled requests
// (i.e. HTTP URLs) through an HTTPS or QUIC proxy work by sending the request
// to the proxy directly. In that case, this logic also handles the proxy's
// negotiated protocol. HTTPS requests are always tunneled, so at most one of
// these applies.
//
// Tunneled requests may also negotiate ALPN at the proxy, but
// HttpProxyConnectJob handles ALPN. The resulting StreamSocket will not
// report an ALPN protocol.
if (result == OK) {
if (using_quic_) {
// TODO(davidben): Record these values consistently between QUIC and TCP
// below. In the QUIC case, we only record it for origin connections. In
// the TCP case, we also record it for non-tunneled, proxied requests.
if (using_ssl_) {
was_alpn_negotiated_ = true;
negotiated_protocol_ = kProtoQUIC;
}
} else if (connection_->socket()->WasAlpnNegotiated()) {
// Only connections that use TLS can negotiate ALPN.
DCHECK(using_ssl_ || proxy_info_.is_secure_http_like());
was_alpn_negotiated_ = true;
negotiated_protocol_ = connection_->socket()->GetNegotiatedProtocol();
net_log_.AddEvent(NetLogEventType::HTTP_STREAM_REQUEST_PROTO, [&] {
return NetLogHttpStreamProtoParams(negotiated_protocol_);
});
if (negotiated_protocol_ == kProtoHTTP2) {
if (is_websocket_) {
// WebSocket is not supported over a fresh HTTP/2 connection. This
// should not be reachable. For the origin, we do not request HTTP/2
// on fresh WebSockets connections, because not all HTTP/2 servers
// implement RFC 8441. For proxies, WebSockets are always tunneled.
//
// TODO(davidben): This isn't a CHECK() because, previously, it was
// reachable in https://crbug.com/828865. However, if reachable, it
// means a bug in the socket pools. The socket pools have since been
// cleaned up, so this may no longer be reachable. Restore the CHECK
// and see if this is still needed.
return ERR_NOT_IMPLEMENTED;
}
using_spdy_ = true;
}
}
}
if (proxy_info_.is_quic() && using_quic_ && result < 0)
return ReconsiderProxyAfterError(result);
if (expect_spdy_ && !using_spdy_)
return ERR_ALPN_NEGOTIATION_FAILED;
// |result| may be the result of any of the stacked protocols. The following
// logic is used when determining how to interpret an error.
// If |result| < 0:
// and connection_->socket() != NULL, then the SSL handshake ran and it
// is a potentially recoverable error.
// and connection_->socket == NULL and connection_->is_ssl_error() is true,
// then the SSL handshake ran with an unrecoverable error.
// otherwise, the error came from one of the other protocols.
bool ssl_started = using_ssl_ && (result == OK || connection_->socket() ||
connection_->is_ssl_error());
if (!ssl_started && result < 0 && (expect_spdy_ || using_quic_))
return result;
if (using_quic_) {
if (result < 0)
return result;
if (stream_type_ == HttpStreamRequest::BIDIRECTIONAL_STREAM) {
std::unique_ptr<QuicChromiumClientSession::Handle> session =
quic_request_.ReleaseSessionHandle();
if (!session) {
// Quic session is closed before stream can be created.
return ERR_CONNECTION_CLOSED;
}
bidirectional_stream_impl_ =
std::make_unique<BidirectionalStreamQuicImpl>(std::move(session));
} else {
std::unique_ptr<QuicChromiumClientSession::Handle> session =
quic_request_.ReleaseSessionHandle();
if (!session) {
// Quic session is closed before stream can be created.
return ERR_CONNECTION_CLOSED;
}
auto dns_aliases =
session->GetDnsAliasesForSessionKey(quic_request_.session_key());
stream_ = std::make_unique<QuicHttpStream>(std::move(session),
std::move(dns_aliases));
}
next_state_ = STATE_NONE;
return OK;
}
if (result < 0 && !ssl_started)
return ReconsiderProxyAfterError(result);
establishing_tunnel_ = false;
// Handle SSL errors below.
if (using_ssl_) {
DCHECK(ssl_started);
if (IsCertificateError(result)) {
SSLInfo ssl_info;
GetSSLInfo(&ssl_info);
if (ssl_info.cert) {
// Add the bad certificate to the set of allowed certificates in the
// SSL config object. This data structure will be consulted after
// calling RestartIgnoringLastError(). And the user will be asked
// interactively before RestartIgnoringLastError() is ever called.
server_ssl_config_.allowed_bad_certs.emplace_back(ssl_info.cert,
ssl_info.cert_status);
}
}
if (result < 0)
return result;
}
next_state_ = STATE_CREATE_STREAM;
return OK;
}
int HttpStreamFactory::Job::DoWaitingUserAction(int result) {
// This state indicates that the stream request is in a partially
// completed state, and we've called back to the delegate for more
// information.
// We're always waiting here for the delegate to call us back.
return ERR_IO_PENDING;
}
int HttpStreamFactory::Job::SetSpdyHttpStreamOrBidirectionalStreamImpl(
base::WeakPtr<SpdySession> session) {
DCHECK(using_spdy_);
auto dns_aliases = session_->spdy_session_pool()->GetDnsAliasesForSessionKey(
spdy_session_key_);
if (is_websocket_) {
DCHECK_NE(job_type_, PRECONNECT);
DCHECK_NE(job_type_, PRECONNECT_DNS_ALPN_H3);
DCHECK(delegate_->websocket_handshake_stream_create_helper());
if (!try_websocket_over_http2_) {
// TODO(davidben): Is this reachable? We shouldn't receive a SpdySession
// if not requested.
return ERR_NOT_IMPLEMENTED;
}
websocket_stream_ =
delegate_->websocket_handshake_stream_create_helper()
->CreateHttp2Stream(session, std::move(dns_aliases));
return OK;
}
if (stream_type_ == HttpStreamRequest::BIDIRECTIONAL_STREAM) {
bidirectional_stream_impl_ = std::make_unique<BidirectionalStreamSpdyImpl>(
session, net_log_.source());
return OK;
}
// TODO(willchan): Delete this code, because eventually, the HttpStreamFactory
// will be creating all the SpdyHttpStreams, since it will know when
// SpdySessions become available.
stream_ = std::make_unique<SpdyHttpStream>(
session, pushed_stream_id_, net_log_.source(), std::move(dns_aliases));
return OK;
}
int HttpStreamFactory::Job::DoCreateStream() {
DCHECK(connection_->socket() || existing_spdy_session_.get());
DCHECK(!using_quic_);
next_state_ = STATE_CREATE_STREAM_COMPLETE;
if (!using_spdy_) {
DCHECK(!expect_spdy_);
bool using_proxy = (proxy_info_.is_http_like()) &&
request_info_.url.SchemeIs(url::kHttpScheme);
if (is_websocket_) {
DCHECK_NE(job_type_, PRECONNECT);
DCHECK_NE(job_type_, PRECONNECT_DNS_ALPN_H3);
DCHECK(delegate_->websocket_handshake_stream_create_helper());
websocket_stream_ =
delegate_->websocket_handshake_stream_create_helper()
->CreateBasicStream(std::move(connection_), using_proxy,
session_->websocket_endpoint_lock_manager());
} else {
if (request_info_.upload_data_stream &&
!request_info_.upload_data_stream->AllowHTTP1()) {
return ERR_H2_OR_QUIC_REQUIRED;
}
stream_ = std::make_unique<HttpBasicStream>(std::move(connection_),
using_proxy);
}
return OK;
}
CHECK(!stream_.get());
// It is possible that a pushed stream has been opened by a server since last
// time Job checked above.
if (!existing_spdy_session_) {
// WebSocket over HTTP/2 is only allowed to use existing HTTP/2 connections.
// Therefore |using_spdy_| could not have been set unless a connection had
// already been found.
DCHECK(!is_websocket_);
session_->spdy_session_pool()->push_promise_index()->ClaimPushedStream(
spdy_session_key_, origin_url_, request_info_, &existing_spdy_session_,
&pushed_stream_id_);
// It is also possible that an HTTP/2 connection has been established since
// last time Job checked above.
if (!existing_spdy_session_) {
existing_spdy_session_ =
session_->spdy_session_pool()->FindAvailableSession(
spdy_session_key_, enable_ip_based_pooling_,
/* is_websocket = */ false, net_log_);
}
}
if (existing_spdy_session_) {
// We picked up an existing session, so we don't need our socket.
if (connection_->socket())
connection_->socket()->Disconnect();
connection_->Reset();
int set_result =
SetSpdyHttpStreamOrBidirectionalStreamImpl(existing_spdy_session_);
existing_spdy_session_.reset();
return set_result;
}
// Close idle sockets in this group, since subsequent requests will go over
// |spdy_session|.
if (connection_->socket()->IsConnected())
connection_->CloseIdleSocketsInGroup("Switching to HTTP2 session");
base::WeakPtr<SpdySession> spdy_session;
int rv =
session_->spdy_session_pool()->CreateAvailableSessionFromSocketHandle(
spdy_session_key_, std::move(connection_), net_log_, &spdy_session);
if (rv != OK) {
return rv;
}
url::SchemeHostPort scheme_host_port(
using_ssl_ ? url::kHttpsScheme : url::kHttpScheme,
spdy_session_key_.host_port_pair().host(),
spdy_session_key_.host_port_pair().port());
HttpServerProperties* http_server_properties =
session_->http_server_properties();
if (http_server_properties) {
http_server_properties->SetSupportsSpdy(
scheme_host_port, request_info_.network_anonymization_key,
true /* supports_spdy */);
}
// Create a SpdyHttpStream or a BidirectionalStreamImpl attached to the
// session.
return SetSpdyHttpStreamOrBidirectionalStreamImpl(spdy_session);
}
int HttpStreamFactory::Job::DoCreateStreamComplete(int result) {
if (result < 0)
return result;
session_->proxy_resolution_service()->ReportSuccess(proxy_info_);
next_state_ = STATE_NONE;
return OK;
}
void HttpStreamFactory::Job::OnSpdySessionAvailable(
base::WeakPtr<SpdySession> spdy_session) {
DCHECK(spdy_session);
// No need for the connection any more, since |spdy_session| can be used
// instead, and there's no benefit from keeping the old ConnectJob in the
// socket pool.
if (connection_)
connection_->ResetAndCloseSocket();
// Once a connection is initialized, or if there's any out-of-band callback,
// like proxy auth challenge, the SpdySessionRequest is cancelled.
DCHECK(next_state_ == STATE_INIT_CONNECTION ||
next_state_ == STATE_INIT_CONNECTION_COMPLETE);
// Ignore calls to ResumeInitConnection() from either the timer or the
// SpdySessionPool.
init_connection_already_resumed_ = true;
// If this is a preconnect, nothing left do to.
if (job_type_ == PRECONNECT) {
OnPreconnectsComplete(OK);
return;
}
using_spdy_ = true;
existing_spdy_session_ = spdy_session;
next_state_ = STATE_CREATE_STREAM;
// This will synchronously close |connection_|, so no need to worry about it
// calling back into |this|.
RunLoop(net::OK);
}
int HttpStreamFactory::Job::ReconsiderProxyAfterError(int error) {
// Check if the error was a proxy failure.
if (!CanFalloverToNextProxy(proxy_info_.proxy_server(), error, &error))
return error;
should_reconsider_proxy_ = true;
return error;
}
void HttpStreamFactory::Job::MaybeCopyConnectionAttemptsFromHandle() {
if (!connection_)
return;
delegate_->AddConnectionAttemptsToRequest(this,
connection_->connection_attempts());
}
HttpStreamFactory::JobFactory::JobFactory() = default;
HttpStreamFactory::JobFactory::~JobFactory() = default;
std::unique_ptr<HttpStreamFactory::Job>
HttpStreamFactory::JobFactory::CreateJob(
HttpStreamFactory::Job::Delegate* delegate,
HttpStreamFactory::JobType job_type,
HttpNetworkSession* session,
const HttpRequestInfo& request_info,
RequestPriority priority,
const ProxyInfo& proxy_info,
const SSLConfig& server_ssl_config,
const SSLConfig& proxy_ssl_config,
url::SchemeHostPort destination,
GURL origin_url,
bool is_websocket,
bool enable_ip_based_pooling,
NetLog* net_log,
NextProto alternative_protocol,
quic::ParsedQuicVersion quic_version) {
return std::make_unique<HttpStreamFactory::Job>(
delegate, job_type, session, request_info, priority, proxy_info,
server_ssl_config, proxy_ssl_config, std::move(destination), origin_url,
alternative_protocol, quic_version, is_websocket, enable_ip_based_pooling,
net_log);
}
bool HttpStreamFactory::Job::ShouldThrottleConnectForSpdy() const {
DCHECK(!using_quic_);
DCHECK(!spdy_session_request_);
// If the job has previously been throttled, don't throttle it again.
if (init_connection_already_resumed_)
return false;
url::SchemeHostPort scheme_host_port(
using_ssl_ ? url::kHttpsScheme : url::kHttpScheme,
spdy_session_key_.host_port_pair().host(),
spdy_session_key_.host_port_pair().port());
// Only throttle the request if the server is believed to support H2.
return session_->http_server_properties()->GetSupportsSpdy(
scheme_host_port, request_info_.network_anonymization_key);
}
} // namespace net