910e62b5创建于 1月15日历史提交
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/preloading/prefetch/prefetch_proxy_configurator.h"

#include "base/barrier_closure.h"
#include "base/metrics/histogram_functions.h"
#include "base/rand_util.h"
#include "base/time/default_clock.h"
#include "content/browser/preloading/prefetch/prefetch_params.h"
#include "content/public/common/content_features.h"
#include "net/base/host_port_pair.h"
#include "net/base/proxy_chain.h"
#include "net/base/proxy_string_util.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
#include "net/http/http_util.h"
#include "net/proxy_resolution/proxy_config.h"
#include "url/gurl.h"

namespace content {

// static
std::unique_ptr<PrefetchProxyConfigurator>
PrefetchProxyConfigurator::MaybeCreatePrefetchProxyConfigurator(
    const GURL& proxy_url,
    const std::string& api_key) {
  if (!base::FeatureList::IsEnabled(features::kPrefetchProxy)) {
    return nullptr;
  }

  if (!proxy_url.is_valid())
    return nullptr;

  return std::make_unique<PrefetchProxyConfigurator>(proxy_url, api_key);
}

PrefetchProxyConfigurator::PrefetchProxyConfigurator(const GURL& proxy_url,
                                                     const std::string& api_key)
    : prefetch_proxy_chain_(net::GetSchemeFromUriScheme(proxy_url.GetScheme()),
                            net::HostPortPair::FromURL(proxy_url)),
      clock_(base::DefaultClock::GetInstance()) {
  DCHECK(proxy_url.is_valid());

  std::string server_experiment_group = PrefetchProxyServerExperimentGroup();
  std::string header_value =
      "key=" + api_key +
      (server_experiment_group != "" ? ",exp=" + server_experiment_group : "");

  connect_tunnel_headers_.SetHeader("chrome-tunnel", header_value);
}

PrefetchProxyConfigurator::~PrefetchProxyConfigurator() = default;

void PrefetchProxyConfigurator::SetClockForTesting(const base::Clock* clock) {
  clock_ = clock;
}

void PrefetchProxyConfigurator::AddCustomProxyConfigClient(
    mojo::Remote<network::mojom::CustomProxyConfigClient> config_client,
    base::OnceCallback<void()> callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  proxy_config_clients_.Add(std::move(config_client));
  UpdateCustomProxyConfig(std::move(callback));
}

void PrefetchProxyConfigurator::UpdateCustomProxyConfig(
    base::OnceCallback<void()> callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  base::RepeatingClosure repeating_closure =
      base::BarrierClosure(proxy_config_clients_.size(), std::move(callback));
  network::mojom::CustomProxyConfigPtr config = CreateCustomProxyConfig();
  for (auto& client : proxy_config_clients_) {
    client->OnCustomProxyConfigUpdated(config->Clone(), repeating_closure);
  }
}

network::mojom::CustomProxyConfigPtr
PrefetchProxyConfigurator::CreateCustomProxyConfig() const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  auto config = network::mojom::CustomProxyConfig::New();
  config->rules.type =
      net::ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME;

  // DIRECT is intentionally not added here because we want the proxy to always
  // be used in order to mask the user's IP address during the prerender.
  config->rules.proxies_for_https.AddProxyChain(prefetch_proxy_chain_);

  // This ensures that the user's set proxy is honored, although we also disable
  // the feature is such cases.
  config->should_override_existing_config = false;
  config->allow_non_idempotent_methods = false;
  config->connect_tunnel_headers = connect_tunnel_headers_;
  return config;
}

mojo::PendingRemote<network::mojom::CustomProxyConnectionObserver>
PrefetchProxyConfigurator::NewProxyConnectionObserverRemote() {
  mojo::PendingRemote<network::mojom::CustomProxyConnectionObserver>
      observer_remote;
  observer_receivers_.Add(this,
                          observer_remote.InitWithNewPipeAndPassReceiver());
  // The disconnect handler is intentionally not set since ReceiverSet manages
  // connection clean up on disconnect.
  return observer_remote;
}

void PrefetchProxyConfigurator::OnFallback(const net::ProxyChain& bad_chain,
                                           int net_error) {
  if (bad_chain != prefetch_proxy_chain_) {
    return;
  }

  base::UmaHistogramSparse("PrefetchProxy.Proxy.Fallback.NetError",
                           std::abs(net_error));

  OnTunnelProxyConnectionError(std::nullopt);
}

void PrefetchProxyConfigurator::OnTunnelHeadersReceived(
    const net::ProxyChain& proxy_chain,
    uint64_t chain_index,
    const scoped_refptr<net::HttpResponseHeaders>& response_headers) {
  DCHECK(response_headers);

  if (proxy_chain != prefetch_proxy_chain_) {
    return;
  }

  base::UmaHistogramSparse("PrefetchProxy.Proxy.RespCode",
                           response_headers->response_code());

  if (response_headers->response_code() == net::HTTP_OK) {
    return;
  }

  std::string retry_after_string;
  if (response_headers->EnumerateHeader(nullptr, "Retry-After",
                                        &retry_after_string)) {
    base::TimeDelta retry_after;
    if (net::HttpUtil::ParseRetryAfterHeader(retry_after_string, clock_->Now(),
                                             &retry_after)) {
      OnTunnelProxyConnectionError(retry_after);
      return;
    }
  }

  OnTunnelProxyConnectionError(std::nullopt);
}

bool PrefetchProxyConfigurator::IsPrefetchProxyAvailable() const {
  if (!prefetch_proxy_not_available_until_) {
    return true;
  }

  return prefetch_proxy_not_available_until_.value() <= clock_->Now();
}

void PrefetchProxyConfigurator::OnTunnelProxyConnectionError(
    std::optional<base::TimeDelta> retry_after) {
  base::Time retry_proxy_at;
  if (retry_after) {
    retry_proxy_at = clock_->Now() + *retry_after;
  } else {
    // Pick a random value between 1-5 mins if the proxy didn't give us a
    // Retry-After value. The randomness will help ensure there is no sudden
    // wave of requests following a proxy error.
    retry_proxy_at = clock_->Now() + base::Seconds(base::RandInt(
                                         base::Time::kSecondsPerMinute,
                                         5 * base::Time::kSecondsPerMinute));
  }
  DCHECK(!retry_proxy_at.is_null());

  // If there is already a value in |prefetch_proxy_not_available_until_|,
  // probably due to some race, take the max.
  if (prefetch_proxy_not_available_until_) {
    prefetch_proxy_not_available_until_ =
        std::max(*prefetch_proxy_not_available_until_, retry_proxy_at);
  } else {
    prefetch_proxy_not_available_until_ = retry_proxy_at;
  }
  DCHECK(prefetch_proxy_not_available_until_);

  // TODO(crbug.com/40152136): Consider persisting to prefs.
}

}  // namespace content