blob: 477756d0257c429d364e6c28bf9a73658d6408fb [file] [log] [blame]
// 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