| // Copyright 2024 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_pool_job.h" |
| |
| #include <memory> |
| #include <vector> |
| |
| #include "base/memory/raw_ptr.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/time/time.h" |
| #include "net/base/load_states.h" |
| #include "net/base/net_error_details.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/net_export.h" |
| #include "net/dns/public/resolve_error_info.h" |
| #include "net/http/http_network_session.h" |
| #include "net/http/http_stream_pool.h" |
| #include "net/http/http_stream_pool_attempt_manager.h" |
| #include "net/http/http_stream_pool_group.h" |
| #include "net/log/net_log_source_type.h" |
| #include "net/log/net_log_with_source.h" |
| #include "net/proxy_resolution/proxy_resolution_service.h" |
| #include "net/quic/quic_http_stream.h" |
| #include "net/socket/connection_attempts.h" |
| #include "net/socket/next_proto.h" |
| #include "net/socket/stream_socket.h" |
| #include "net/spdy/spdy_http_stream.h" |
| #include "net/ssl/ssl_cert_request_info.h" |
| #include "net/ssl/ssl_info.h" |
| #include "net/third_party/quiche/src/quiche/quic/core/quic_versions.h" |
| |
| namespace net { |
| |
| namespace { |
| |
| NextProtoSet CalculateAllowedAlpns(HttpStreamPool::Job::Delegate* delegate, |
| HttpStreamPool::Group* group, |
| NextProto expected_protocol) { |
| if (group->force_quic()) { |
| return NextProtoSet({NextProto::kProtoQUIC}); |
| } |
| |
| NextProtoSet allowed_alpns = expected_protocol == NextProto::kProtoUnknown |
| ? NextProtoSet::All() |
| : NextProtoSet({expected_protocol}); |
| |
| allowed_alpns = Intersection(allowed_alpns, delegate->allowed_alpns()); |
| |
| if (!group->pool()->CanUseQuic( |
| group->stream_key().destination(), |
| group->stream_key().network_anonymization_key(), |
| delegate->enable_alternative_services())) { |
| allowed_alpns.Remove(NextProto::kProtoQUIC); |
| } |
| |
| CHECK(!allowed_alpns.empty()); |
| return allowed_alpns; |
| } |
| |
| // If the destination is forced to use QUIC and the QUIC version is unknown, |
| // try the preferred QUIC version that is supported by default. |
| quic::ParsedQuicVersion CalculateQuicVersion( |
| quic::ParsedQuicVersion original_quic_version, |
| HttpStreamPool::Group* group) { |
| return !original_quic_version.IsKnown() && group->force_quic() |
| ? group->http_network_session() |
| ->context() |
| .quic_context->params() |
| ->supported_versions[0] |
| : original_quic_version; |
| } |
| |
| } // namespace |
| |
| HttpStreamPool::Job::Job(Delegate* delegate, |
| JobType type, |
| Group* group, |
| quic::ParsedQuicVersion quic_version, |
| NextProto expected_protocol, |
| const NetLogWithSource& request_net_log, |
| size_t num_streams) |
| : delegate_(delegate), |
| type_(type), |
| attempt_manager_(group->GetAttemptManagerForJob(this)), |
| quic_version_(CalculateQuicVersion(quic_version, group)), |
| allowed_alpns_( |
| CalculateAllowedAlpns(delegate_, group, expected_protocol)), |
| request_net_log_(request_net_log), |
| job_net_log_( |
| NetLogWithSource::Make(request_net_log.net_log(), |
| NetLogSourceType::HTTP_STREAM_POOL_JOB)), |
| num_streams_(num_streams), |
| create_time_(base::TimeTicks::Now()) { |
| CHECK(attempt_manager_); |
| job_net_log_.BeginEvent(NetLogEventType::HTTP_STREAM_POOL_JOB_ALIVE, [&] { |
| base::Value::Dict dict; |
| dict.Set("stream_key", group->stream_key().ToValue()); |
| dict.Set("quic_version", quic::ParsedQuicVersionToString(quic_version)); |
| base::Value::List allowed_alpn_list; |
| for (const auto alpn : allowed_alpns_) { |
| allowed_alpn_list.Append(NextProtoToString(alpn)); |
| } |
| dict.Set("allowed_alpns", std::move(allowed_alpn_list)); |
| dict.Set("type", static_cast<int>(type_)); |
| dict.Set("num_streams", static_cast<int>(num_streams_)); |
| delegate_->net_log().source().AddToEventParameters(dict); |
| return dict; |
| }); |
| delegate_->net_log().AddEventReferencingSource( |
| NetLogEventType::HTTP_STREAM_POOL_JOB_CONTROLLER_JOB_BOUND, |
| job_net_log_.source()); |
| } |
| |
| HttpStreamPool::Job::~Job() { |
| CHECK(attempt_manager_); |
| |
| // Record histograms only when `this` has a result. If `this` doesn't have a |
| // result that means JobController destroyed `this` since another job |
| // completed. |
| if (result_.has_value()) { |
| constexpr std::string_view kCompleteTimeHistogramName = |
| "Net.HttpStreamPool.JobCompleteTime3."; |
| base::TimeDelta complete_time = base::TimeTicks::Now() - create_time_; |
| if (*result_ == OK) { |
| const std::string_view protocol = NegotiatedProtocolToHistogramSuffix( |
| negotiated_protocol_.value_or(NextProto::kProtoUnknown)); |
| base::UmaHistogramLongTimes100( |
| base::StrCat({kCompleteTimeHistogramName, protocol}), complete_time); |
| } else { |
| base::UmaHistogramLongTimes100( |
| base::StrCat({kCompleteTimeHistogramName, "Failure"}), complete_time); |
| base::UmaHistogramSparse("Net.HttpStreamPool.JobErrorCode", -*result_); |
| } |
| } |
| |
| job_net_log_.EndEvent(NetLogEventType::HTTP_STREAM_POOL_JOB_ALIVE, [&] { |
| base::Value::Dict dict; |
| if (result_.has_value()) { |
| // Use "net_error" for the result as the NetLog viewer converts the value |
| // to a human-readable string. |
| dict.Set("net_error", *result_); |
| } |
| if (negotiated_protocol_.has_value()) { |
| dict.Set("negotiated_protocol", NextProtoToString(*negotiated_protocol_)); |
| } |
| return dict; |
| }); |
| |
| // `attempt_manager_` may be deleted after this call. |
| attempt_manager_.ExtractAsDangling()->OnJobComplete(this); |
| } |
| |
| void HttpStreamPool::Job::Start() { |
| CHECK(attempt_manager_); |
| CHECK(!attempt_manager_->is_shutting_down()); |
| |
| switch (type_) { |
| case JobType::kRequest: |
| attempt_manager_->RequestStream(this); |
| break; |
| case JobType::kPreconnect: |
| case JobType::kAltSvcQuicPreconnect: |
| attempt_manager_->Preconnect(this); |
| break; |
| } |
| } |
| |
| LoadState HttpStreamPool::Job::GetLoadState() const { |
| if (!attempt_manager_) { |
| return LOAD_STATE_IDLE; |
| } |
| return attempt_manager_->GetLoadState(); |
| } |
| |
| void HttpStreamPool::Job::SetPriority(RequestPriority priority) { |
| if (attempt_manager_) { |
| attempt_manager_->SetJobPriority(this, priority); |
| } |
| } |
| |
| void HttpStreamPool::Job::AddConnectionAttempts( |
| const ConnectionAttempts& attempts) { |
| for (const auto& attempt : attempts) { |
| connection_attempts_.emplace_back(attempt); |
| } |
| } |
| |
| void HttpStreamPool::Job::OnStreamReady(std::unique_ptr<HttpStream> stream, |
| NextProto negotiated_protocol) { |
| CHECK(delegate_); |
| CHECK(!result_.has_value()); |
| CHECK(!negotiated_protocol_); |
| CHECK(attempt_manager_); |
| |
| if (!allowed_alpns_.Has(negotiated_protocol)) { |
| OnStreamFailed(ERR_ALPN_NEGOTIATION_FAILED, NetErrorDetails(), |
| ResolveErrorInfo()); |
| return; |
| } |
| |
| negotiated_protocol_ = negotiated_protocol; |
| attempt_manager_->group() |
| ->http_network_session() |
| ->proxy_resolution_service() |
| ->ReportSuccess(delegate_->proxy_info()); |
| delegate_->OnStreamReady(this, std::move(stream), negotiated_protocol); |
| } |
| |
| void HttpStreamPool::Job::OnStreamFailed( |
| int status, |
| const NetErrorDetails& net_error_details, |
| ResolveErrorInfo resolve_error_info) { |
| CHECK(delegate_); |
| CHECK(!result_.has_value()); |
| result_ = status; |
| delegate_->OnStreamFailed(this, status, net_error_details, |
| resolve_error_info); |
| } |
| |
| void HttpStreamPool::Job::OnCertificateError(int status, |
| const SSLInfo& ssl_info) { |
| CHECK(delegate_); |
| CHECK(!result_.has_value()); |
| result_ = status; |
| delegate_->OnCertificateError(this, status, ssl_info); |
| } |
| |
| void HttpStreamPool::Job::OnNeedsClientAuth(SSLCertRequestInfo* cert_info) { |
| CHECK(delegate_); |
| CHECK(!result_.has_value()); |
| result_ = ERR_SSL_CLIENT_AUTH_CERT_NEEDED; |
| delegate_->OnNeedsClientAuth(this, cert_info); |
| } |
| |
| void HttpStreamPool::Job::OnPreconnectComplete(int status) { |
| CHECK(delegate_); |
| CHECK(!result_.has_value()); |
| result_ = status; |
| delegate_->OnPreconnectComplete(this, status); |
| } |
| |
| void HttpStreamPool::Job::CallOnPreconnectCompleteLater(int status) { |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(&Job::OnPreconnectComplete, |
| weak_ptr_factory_.GetWeakPtr(), status)); |
| } |
| |
| } // namespace net |