blob: 804010c6deb96421aba0a8e77dfb5750ab3178dc [file] [log] [blame]
// 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/run_loop.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "content/browser/preloading/prefetch/prefetch_features.h"
#include "content/public/test/browser_task_environment.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/base/proxy_chain.h"
#include "net/base/proxy_string_util.h"
#include "net/http/http_util.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace content {
namespace {
const char kApiKey[] = "APIKEY";
class TestCustomProxyConfigClient
: public network::mojom::CustomProxyConfigClient {
public:
explicit TestCustomProxyConfigClient(
mojo::PendingReceiver<network::mojom::CustomProxyConfigClient>
pending_receiver)
: receiver_(this, std::move(pending_receiver)) {}
// network::mojom::CustomProxyConfigClient:
void OnCustomProxyConfigUpdated(
network::mojom::CustomProxyConfigPtr proxy_config,
OnCustomProxyConfigUpdatedCallback callback) override {
config_ = std::move(proxy_config);
std::move(callback).Run();
}
network::mojom::CustomProxyConfigPtr config_;
private:
mojo::Receiver<network::mojom::CustomProxyConfigClient> receiver_;
};
class PrefetchProxyConfiguratorTest : public testing::Test {
public:
network::mojom::CustomProxyConfigPtr LatestProxyConfig() {
return std::move(config_client_->config_);
}
GURL prefetch_proxy_url() { return GURL("https://prefetchproxy.com"); }
void VerifyLatestProxyConfig(const GURL& proxy_url,
const net::HttpRequestHeaders& headers) {
auto config = LatestProxyConfig();
ASSERT_TRUE(config);
EXPECT_EQ(config->rules.type,
net::ProxyConfig::ProxyRules::Type::PROXY_LIST_PER_SCHEME);
EXPECT_FALSE(config->should_override_existing_config);
EXPECT_FALSE(config->allow_non_idempotent_methods);
EXPECT_EQ(config->connect_tunnel_headers.ToString(), headers.ToString());
EXPECT_EQ(config->rules.proxies_for_http.size(), 0U);
EXPECT_EQ(config->rules.proxies_for_ftp.size(), 0U);
ASSERT_EQ(config->rules.proxies_for_https.size(), 1U);
EXPECT_EQ(GURL(net::ProxyServerToProxyUri(
config->rules.proxies_for_https.First().GetProxyServer(
/*chain_index=*/0))),
proxy_url);
}
PrefetchProxyConfigurator* configurator() {
if (!configurator_) {
// Lazy construct and init so that any changed field trials can be used.
configurator_ = std::make_unique<PrefetchProxyConfigurator>(
prefetch_proxy_url(), kApiKey);
mojo::Remote<network::mojom::CustomProxyConfigClient> client_remote;
config_client_ = std::make_unique<TestCustomProxyConfigClient>(
client_remote.BindNewPipeAndPassReceiver());
base::RunLoop run_loop;
configurator_->AddCustomProxyConfigClient(std::move(client_remote),
run_loop.QuitClosure());
configurator_->SetClockForTesting(task_environment_.GetMockClock());
run_loop.Run();
}
return configurator_.get();
}
void FastForwardBy(base::TimeDelta delta) {
task_environment_.FastForwardBy(delta);
}
private:
BrowserTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
std::unique_ptr<PrefetchProxyConfigurator> configurator_;
std::unique_ptr<TestCustomProxyConfigClient> config_client_;
};
TEST_F(PrefetchProxyConfiguratorTest, Fallback_DoesRandomBackoff_ErrFailed) {
base::HistogramTester histogram_tester;
net::ProxyChain proxy_chain(
net::GetSchemeFromUriScheme(prefetch_proxy_url().scheme()),
net::HostPortPair::FromURL(prefetch_proxy_url()));
EXPECT_TRUE(configurator()->IsPrefetchProxyAvailable());
configurator()->OnFallback(proxy_chain, net::ERR_FAILED);
EXPECT_FALSE(configurator()->IsPrefetchProxyAvailable());
histogram_tester.ExpectUniqueSample("PrefetchProxy.Proxy.Fallback.NetError",
std::abs(net::ERR_FAILED), 1);
FastForwardBy(base::Seconds(5 * 60 + 1));
EXPECT_TRUE(configurator()->IsPrefetchProxyAvailable());
}
TEST_F(PrefetchProxyConfiguratorTest, FallbackDoesRandomBackoff_ErrOK) {
base::HistogramTester histogram_tester;
net::ProxyChain proxy_chain(
net::GetSchemeFromUriScheme(prefetch_proxy_url().scheme()),
net::HostPortPair::FromURL(prefetch_proxy_url()));
EXPECT_TRUE(configurator()->IsPrefetchProxyAvailable());
configurator()->OnFallback(proxy_chain, net::OK);
EXPECT_FALSE(configurator()->IsPrefetchProxyAvailable());
histogram_tester.ExpectUniqueSample("PrefetchProxy.Proxy.Fallback.NetError",
net::OK, 1);
FastForwardBy(base::Seconds(5 * 60 + 1));
EXPECT_TRUE(configurator()->IsPrefetchProxyAvailable());
}
TEST_F(PrefetchProxyConfiguratorTest, Fallback_DifferentProxy) {
base::HistogramTester histogram_tester;
net::ProxyChain proxy_chain(
net::GetSchemeFromUriScheme(prefetch_proxy_url().scheme()),
net::HostPortPair::FromURL(GURL("http://foo.com")));
EXPECT_TRUE(configurator()->IsPrefetchProxyAvailable());
configurator()->OnFallback(proxy_chain, net::OK);
EXPECT_TRUE(configurator()->IsPrefetchProxyAvailable());
histogram_tester.ExpectTotalCount("PrefetchProxy.Proxy.Fallback.NetError", 0);
}
TEST_F(PrefetchProxyConfiguratorTest, TunnelHeaders_200OK) {
base::HistogramTester histogram_tester;
net::ProxyChain proxy_chain(
net::GetSchemeFromUriScheme(prefetch_proxy_url().scheme()),
net::HostPortPair::FromURL(prefetch_proxy_url()));
EXPECT_TRUE(configurator()->IsPrefetchProxyAvailable());
configurator()->OnTunnelHeadersReceived(
proxy_chain, 0,
base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK"));
EXPECT_TRUE(configurator()->IsPrefetchProxyAvailable());
histogram_tester.ExpectUniqueSample("PrefetchProxy.Proxy.RespCode", 200, 1);
}
TEST_F(PrefetchProxyConfiguratorTest, TunnelHeaders_DifferentProxy) {
base::HistogramTester histogram_tester;
net::ProxyChain proxy_chain(
net::GetSchemeFromUriScheme(prefetch_proxy_url().scheme()),
net::HostPortPair::FromURL(GURL("http://foo.com")));
EXPECT_TRUE(configurator()->IsPrefetchProxyAvailable());
configurator()->OnTunnelHeadersReceived(
proxy_chain, 0,
base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK"));
EXPECT_TRUE(configurator()->IsPrefetchProxyAvailable());
histogram_tester.ExpectTotalCount("PrefetchProxy.Proxy.RespCode", 0);
}
TEST_F(PrefetchProxyConfiguratorTest, TunnelHeaders_500NoRetryAfter) {
base::HistogramTester histogram_tester;
net::ProxyChain proxy_chain(
net::GetSchemeFromUriScheme(prefetch_proxy_url().scheme()),
net::HostPortPair::FromURL(prefetch_proxy_url()));
EXPECT_TRUE(configurator()->IsPrefetchProxyAvailable());
configurator()->OnTunnelHeadersReceived(
proxy_chain, 0,
base::MakeRefCounted<net::HttpResponseHeaders>(
"HTTP/1.1 500 Internal Server Error"));
EXPECT_FALSE(configurator()->IsPrefetchProxyAvailable());
histogram_tester.ExpectUniqueSample("PrefetchProxy.Proxy.RespCode", 500, 1);
FastForwardBy(base::Seconds(5 * 60 + 1));
EXPECT_TRUE(configurator()->IsPrefetchProxyAvailable());
}
TEST_F(PrefetchProxyConfiguratorTest, TunnelHeaders_500WithRetryAfter) {
base::HistogramTester histogram_tester;
net::ProxyChain proxy_chain(
net::GetSchemeFromUriScheme(prefetch_proxy_url().scheme()),
net::HostPortPair::FromURL(prefetch_proxy_url()));
EXPECT_TRUE(configurator()->IsPrefetchProxyAvailable());
configurator()->OnTunnelHeadersReceived(
proxy_chain, 0,
base::MakeRefCounted<
net::HttpResponseHeaders>(net::HttpUtil::AssembleRawHeaders(
"HTTP/1.1 500 Internal Server Error\r\nRetry-After: 120\r\n\r\n")));
EXPECT_FALSE(configurator()->IsPrefetchProxyAvailable());
histogram_tester.ExpectUniqueSample("PrefetchProxy.Proxy.RespCode", 500, 1);
FastForwardBy(base::Seconds(119));
EXPECT_FALSE(configurator()->IsPrefetchProxyAvailable());
FastForwardBy(base::Seconds(1));
EXPECT_TRUE(configurator()->IsPrefetchProxyAvailable());
}
TEST_F(PrefetchProxyConfiguratorTest, ServerExperimentGroup) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeatureWithParameters(
features::kPrefetchUseContentRefactor,
{{"server_experiment_group", "test_group"}});
base::RunLoop loop;
configurator()->UpdateCustomProxyConfig(loop.QuitClosure());
loop.Run();
net::HttpRequestHeaders headers;
headers.SetHeader("chrome-tunnel",
"key=" + std::string(kApiKey) + ",exp=test_group");
VerifyLatestProxyConfig(prefetch_proxy_url(), headers);
}
} // namespace
} // namespace content