Commit 234f36e9 authored by Kartik Hegde's avatar Kartik Hegde Committed by Commit Bot

network_diagnostics: Add an HTTP request manager

The HttpRequestManager class will be used by network diagnostics
routines to make http/s requests.

BUG=chromium:956783
TEST=unit_tests --gtest_filter=HttpRequestManagerTest*

Change-Id: I2b2bd97cfb832fb4dc58ceb3b90da721497c9ee7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2377420Reviewed-by: default avatarSteven Bennetts <stevenjb@chromium.org>
Commit-Queue: Kartik Hegde <khegde@chromium.org>
Cr-Commit-Position: refs/heads/master@{#804495}
parent 9140711e
......@@ -1867,6 +1867,8 @@ source_set("chromeos") {
"net/network_diagnostics/has_secure_wifi_connection_routine.h",
"net/network_diagnostics/http_firewall_routine.cc",
"net/network_diagnostics/http_firewall_routine.h",
"net/network_diagnostics/http_request_manager.cc",
"net/network_diagnostics/http_request_manager.h",
"net/network_diagnostics/lan_connectivity_routine.cc",
"net/network_diagnostics/lan_connectivity_routine.h",
"net/network_diagnostics/network_diagnostics.cc",
......@@ -3346,6 +3348,7 @@ source_set("unit_tests") {
"net/network_diagnostics/gateway_can_be_pinged_routine_unittest.cc",
"net/network_diagnostics/has_secure_wifi_connection_routine_unittest.cc",
"net/network_diagnostics/http_firewall_routine_unittest.cc",
"net/network_diagnostics/http_request_manager_unittest.cc",
"net/network_diagnostics/lan_connectivity_routine_unittest.cc",
"net/network_diagnostics/network_diagnostics_routine_unittest.cc",
"net/network_diagnostics/network_diagnostics_unittest.cc",
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/net/network_diagnostics/http_request_manager.h"
#include <memory>
#include <utility>
#include "base/bind.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/storage_partition.h"
#include "net/base/load_flags.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_status_code.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/cpp/resource_request.h"
namespace chromeos {
namespace network_diagnostics {
namespace {
// Maximum number of retries for sending the request.
constexpr int kMaxRetries = 2;
// HTTP Get method.
const char kGetMethod[] = "GET";
const char kTrafficAnnotation[] = R"(
semantics {
sender: "NetworkDiagnosticsRoutines"
description: "Routines send network traffic (http requests) to "
"hosts in order to validate the internet connection on a device."
trigger:
"A routine makes an http request."
data:
"No data other than the path is sent. No user identifier is"
"sent along with the data."
destination: WEBSITE
}
policy {
cookies_allowed: NO
}
)";
net::NetworkTrafficAnnotationTag GetTrafficAnnotation() {
return net::DefineNetworkTrafficAnnotation("network_diagnostics_routines",
kTrafficAnnotation);
}
} // namespace
HttpRequestManager::HttpRequestManager(Profile* profile) {
// |profile| may be null in testing.
if (!profile) {
return;
}
shared_url_loader_factory_ =
content::BrowserContext::GetDefaultStoragePartition(profile)
->GetURLLoaderFactoryForBrowserProcess();
}
HttpRequestManager::~HttpRequestManager() = default;
void HttpRequestManager::MakeRequest(const GURL& url,
const base::TimeDelta& timeout,
HttpRequestCallback callback) {
DCHECK(url.is_valid());
auto request = std::make_unique<network::ResourceRequest>();
request->credentials_mode = network::mojom::CredentialsMode::kOmit;
request->method = kGetMethod;
request->url = url;
simple_url_loader_ = network::SimpleURLLoader::Create(std::move(request),
GetTrafficAnnotation());
simple_url_loader_->SetRetryOptions(
kMaxRetries,
network::SimpleURLLoader::RetryMode::RETRY_ON_5XX |
network::SimpleURLLoader::RetryMode::RETRY_ON_NETWORK_CHANGE);
simple_url_loader_->SetTimeoutDuration(timeout);
// |simple_url_loader_| is owned by |this|, so Unretained is safe to use.
simple_url_loader_->DownloadHeadersOnly(
shared_url_loader_factory_.get(),
base::BindOnce(&HttpRequestManager::OnURLLoadComplete,
base::Unretained(this), std::move(callback)));
}
void HttpRequestManager::SetURLLoaderFactoryForTesting(
scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory) {
shared_url_loader_factory_ = shared_url_loader_factory;
}
void HttpRequestManager::OnURLLoadComplete(
HttpRequestCallback callback,
scoped_refptr<net::HttpResponseHeaders> headers) {
DCHECK(simple_url_loader_);
bool connected = headers && headers->response_code() == net::HTTP_NO_CONTENT;
simple_url_loader_.reset();
std::move(callback).Run(connected);
}
} // namespace network_diagnostics
} // namespace chromeos
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_HTTP_REQUEST_MANAGER_H_
#define CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_HTTP_REQUEST_MANAGER_H_
#include <memory>
#include "base/callback.h"
#include "base/memory/scoped_refptr.h"
#include "base/time/time.h"
#include "net/http/http_response_headers.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "url/gurl.h"
class Profile;
namespace chromeos {
namespace network_diagnostics {
// Makes an HTTP request and determines the results. Used as a utility in
// network diagnostics routines.
class HttpRequestManager {
public:
using HttpRequestCallback = base::OnceCallback<void(bool)>;
explicit HttpRequestManager(Profile* profile);
HttpRequestManager(const HttpRequestManager&) = delete;
HttpRequestManager& operator=(const HttpRequestManager&) = delete;
virtual ~HttpRequestManager();
// Begins the asynchronous http request.
// |url| - The URL to connect to. It is expected that this will be a
// gstatic.com host.
// |timeout| - How long to wait for a response before giving up.
// |callback| - Invoked once an HTTP response is determined.
virtual void MakeRequest(const GURL& url,
const base::TimeDelta& timeout,
HttpRequestCallback callback);
// Setter for testing.
void SetURLLoaderFactoryForTesting(
scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory);
private:
// Processes the response code in |headers|.
void OnURLLoadComplete(HttpRequestCallback callback,
scoped_refptr<net::HttpResponseHeaders> headers);
// Holds a reference to the URLLoaderFactory associated with the storage
// partition for |profile_|.
scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory_;
// SimpleURLLoader to manage http requests.
std::unique_ptr<network::SimpleURLLoader> simple_url_loader_;
};
} // namespace network_diagnostics
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_NET_NETWORK_DIAGNOSTICS_HTTP_REQUEST_MANAGER_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/net/network_diagnostics/http_request_manager.h"
#include <memory>
#include "base/bind.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "content/public/test/browser_task_environment.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace network_diagnostics {
namespace {
const char kFakeUrl[] =
"https://abcdefgh-ccd-testing-v4.metric.gstatic.com/generate_204";
const int timeout_ms = 500;
} // namespace
class HttpRequestManagerTest : public ::testing::Test {
public:
HttpRequestManagerTest()
: task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME),
shared_url_loader_factory_(
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
&test_url_loader_factory_)) {
http_request_manager_ = std::make_unique<HttpRequestManager>(
/*profile=*/nullptr);
http_request_manager_->SetURLLoaderFactoryForTesting(
shared_url_loader_factory_);
}
HttpRequestManagerTest(const HttpRequestManagerTest&) = delete;
HttpRequestManagerTest& operator=(const HttpRequestManagerTest&) = delete;
void VerifyConnected(bool connected) {
EXPECT_EQ(expected_connected_, connected);
callback_invoked_ = true;
}
protected:
void VerifyCallbackInvoked(bool callback_invoked) {
EXPECT_EQ(callback_invoked_, callback_invoked);
}
void ResetCallbackInvoked() { callback_invoked_ = false; }
void ResetHttpRequestManager() { http_request_manager_.reset(); }
void SetExpectedConnectionResult(bool expected_connected) {
expected_connected_ = expected_connected;
}
content::BrowserTaskEnvironment& task_environment() {
return task_environment_;
}
HttpRequestManager* http_request_manager() {
return http_request_manager_.get();
}
network::TestURLLoaderFactory& test_url_loader_factory() {
return test_url_loader_factory_;
}
base::WeakPtr<HttpRequestManagerTest> weak_ptr() {
return weak_factory_.GetWeakPtr();
}
private:
content::BrowserTaskEnvironment task_environment_;
std::unique_ptr<HttpRequestManager> http_request_manager_;
network::TestURLLoaderFactory test_url_loader_factory_;
scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory_;
bool expected_connected_ = false;
bool callback_invoked_ = false;
base::WeakPtrFactory<HttpRequestManagerTest> weak_factory_{this};
};
TEST_F(HttpRequestManagerTest, TestSuccessfulConnection) {
SetExpectedConnectionResult(true);
http_request_manager()->MakeRequest(
GURL(kFakeUrl), base::TimeDelta::FromMilliseconds(timeout_ms),
base::BindOnce(&HttpRequestManagerTest::VerifyConnected, weak_ptr()));
EXPECT_EQ(1, test_url_loader_factory().NumPending());
EXPECT_TRUE(test_url_loader_factory().SimulateResponseForPendingRequest(
kFakeUrl, /*content=*/"", net::HTTP_NO_CONTENT));
VerifyCallbackInvoked(true);
}
TEST_F(HttpRequestManagerTest, TestUnsuccessfulConnection) {
SetExpectedConnectionResult(false);
http_request_manager()->MakeRequest(
GURL(kFakeUrl), base::TimeDelta::FromMilliseconds(timeout_ms),
base::BindOnce(&HttpRequestManagerTest::VerifyConnected, weak_ptr()));
EXPECT_EQ(1, test_url_loader_factory().NumPending());
EXPECT_TRUE(test_url_loader_factory().SimulateResponseForPendingRequest(
kFakeUrl, /*content=*/"", net::HTTP_BAD_REQUEST));
VerifyCallbackInvoked(true);
}
TEST_F(HttpRequestManagerTest, TestTimeoutExceeded) {
SetExpectedConnectionResult(false);
http_request_manager()->MakeRequest(
GURL(kFakeUrl), base::TimeDelta::FromMilliseconds(timeout_ms),
base::BindOnce(&HttpRequestManagerTest::VerifyConnected, weak_ptr()));
EXPECT_EQ(1, test_url_loader_factory().NumPending());
// Advance the clock by |timeout_ms| + 1 milliseconds.
task_environment().FastForwardBy(
base::TimeDelta::FromMilliseconds(timeout_ms + 1));
// HTTP requests time out after |timeout_ms| milliseconds.
EXPECT_EQ(0, test_url_loader_factory().NumPending());
VerifyCallbackInvoked(true);
}
TEST_F(HttpRequestManagerTest, TestRetryHttpRequest) {
SetExpectedConnectionResult(true);
http_request_manager()->MakeRequest(
GURL(kFakeUrl), base::TimeDelta::FromMilliseconds(timeout_ms),
base::BindOnce(&HttpRequestManagerTest::VerifyConnected, weak_ptr()));
EXPECT_EQ(1, test_url_loader_factory().NumPending());
EXPECT_TRUE(test_url_loader_factory().SimulateResponseForPendingRequest(
kFakeUrl, /*content=*/"", net::HTTP_INTERNAL_SERVER_ERROR));
// HTTP requests are retried on HTTP 5XX errors, hence it is expected there is
// a pending request.
EXPECT_EQ(1, test_url_loader_factory().NumPending());
EXPECT_TRUE(test_url_loader_factory().SimulateResponseForPendingRequest(
kFakeUrl, /*content=*/"", net::HTTP_NO_CONTENT));
EXPECT_EQ(0, test_url_loader_factory().NumPending());
VerifyCallbackInvoked(true);
}
TEST_F(HttpRequestManagerTest, TestOverlappingRequests) {
SetExpectedConnectionResult(true);
http_request_manager()->MakeRequest(
GURL(kFakeUrl), base::TimeDelta::FromMilliseconds(timeout_ms),
base::BindOnce(&HttpRequestManagerTest::VerifyConnected, weak_ptr()));
EXPECT_EQ(1, test_url_loader_factory().NumPending());
// Advance the the clock by |timeout_ms| - 1 milliseconds, ensuring the
// request has not timed out.
task_environment().FastForwardBy(
base::TimeDelta::FromMilliseconds(timeout_ms - 1));
// Launch another HTTP request.
http_request_manager()->MakeRequest(
GURL(kFakeUrl), base::TimeDelta::FromMilliseconds(timeout_ms),
base::BindOnce(&HttpRequestManagerTest::VerifyConnected, weak_ptr()));
// Only one request is expected because the first request was cancelled when
// the second one was created.
EXPECT_EQ(1, test_url_loader_factory().NumPending());
EXPECT_TRUE(test_url_loader_factory().SimulateResponseForPendingRequest(
kFakeUrl, /*content=*/"", net::HTTP_NO_CONTENT));
EXPECT_EQ(0, test_url_loader_factory().NumPending());
VerifyCallbackInvoked(true);
}
TEST_F(HttpRequestManagerTest, TestNonOverlappingRequests) {
SetExpectedConnectionResult(false);
http_request_manager()->MakeRequest(
GURL(kFakeUrl), base::TimeDelta::FromMilliseconds(timeout_ms),
base::BindOnce(&HttpRequestManagerTest::VerifyConnected, weak_ptr()));
EXPECT_EQ(1, test_url_loader_factory().NumPending());
EXPECT_TRUE(test_url_loader_factory().SimulateResponseForPendingRequest(
kFakeUrl, /*content=*/"", net::HTTP_BAD_REQUEST));
EXPECT_EQ(0, test_url_loader_factory().NumPending());
VerifyCallbackInvoked(true);
ResetCallbackInvoked();
// Advance the clock by |timeout_ms| + 1 milliseconds to simulate that the
// second request does not overlap with the first.
task_environment().FastForwardBy(
base::TimeDelta::FromMilliseconds(timeout_ms + 1));
SetExpectedConnectionResult(true);
http_request_manager()->MakeRequest(
GURL(kFakeUrl), base::TimeDelta::FromMilliseconds(timeout_ms),
base::BindOnce(&HttpRequestManagerTest::VerifyConnected, weak_ptr()));
EXPECT_EQ(1, test_url_loader_factory().NumPending());
EXPECT_TRUE(test_url_loader_factory().SimulateResponseForPendingRequest(
kFakeUrl, /*content=*/"", net::HTTP_NO_CONTENT));
VerifyCallbackInvoked(true);
}
TEST_F(HttpRequestManagerTest, TestManagerDestroyedWhenRequestPending) {
// A connection result will not be returned in this scenario and
// VerifyConnected() should be invoked.
http_request_manager()->MakeRequest(
GURL(kFakeUrl), base::TimeDelta::FromMilliseconds(timeout_ms),
base::BindOnce(&HttpRequestManagerTest::VerifyConnected, weak_ptr()));
EXPECT_EQ(1, test_url_loader_factory().NumPending());
ResetHttpRequestManager();
// Http request canceled.
EXPECT_EQ(0, test_url_loader_factory().NumPending());
VerifyCallbackInvoked(false);
}
} // namespace network_diagnostics
} // namespace chromeos
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment