Commit e6156903 authored by Yuwei Huang's avatar Yuwei Huang Committed by Commit Bot

[remoting][mobile] Implement GstaticJsonFetcher

This CL implements a GstaticJsonFetcher that actually connects to
gstatic to fetch the JSON files.

Bug: 1001291
Change-Id: I5dcb34e1ea10b8ab6a80343d73757de5bd08da37
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1825484
Commit-Queue: Yuwei Huang <yuweih@chromium.org>
Reviewed-by: default avatarJamie Walch <jamiewalch@chromium.org>
Cr-Commit-Position: refs/heads/master@{#700421}
parent dd658e9b
......@@ -4,6 +4,8 @@
source_set("notification") {
sources = [
"gstatic_json_fetcher.cc",
"gstatic_json_fetcher.h",
"json_fetcher.h",
"notification_client.cc",
"notification_client.h",
......@@ -15,6 +17,8 @@ source_set("notification") {
deps = [
"//base",
"//net",
"//remoting/base",
]
}
......@@ -22,12 +26,14 @@ source_set("unit_tests") {
testonly = true
sources = [
"gstatic_json_fetcher_unittest.cc",
"notification_client_unittest.cc",
"version_range_unittest.cc",
]
deps = [
":notification",
"//net",
"//testing/gmock",
"//testing/gtest",
]
......
// Copyright 2019 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 "remoting/client/notification/gstatic_json_fetcher.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/threading/thread_task_runner_handle.h"
#include "net/http/http_status_code.h"
#include "net/url_request/url_fetcher.h"
#include "remoting/base/url_request_context_getter.h"
namespace remoting {
namespace {
constexpr char kGstaticUrlPrefix[] = "https://www.gstatic.com/chromoting/";
base::Optional<base::Value> GetResponse(
std::unique_ptr<net::URLFetcher> fetcher) {
int response_code = fetcher->GetResponseCode();
if (response_code != net::HTTP_OK) {
LOG(ERROR) << "Json fetch request failed with error code: "
<< response_code;
return base::nullopt;
}
std::string response_string;
if (!fetcher->GetResponseAsString(&response_string)) {
LOG(ERROR) << "Failed to retrieve response data";
return base::nullopt;
}
return base::JSONReader::Read(response_string);
}
} // namespace
GstaticJsonFetcher::GstaticJsonFetcher() {
request_context_getter_ = new remoting::URLRequestContextGetter(
base::ThreadTaskRunnerHandle::Get());
}
GstaticJsonFetcher::~GstaticJsonFetcher() = default;
void GstaticJsonFetcher::FetchJsonFile(
const std::string& relative_path,
FetchJsonFileCallback done,
const net::NetworkTrafficAnnotationTag& traffic_annotation) {
auto fetcher =
net::URLFetcher::Create(GetFullUrl(relative_path), net::URLFetcher::GET,
this, traffic_annotation);
fetcher->SetRequestContext(request_context_getter_.get());
auto* fetcher_ptr = fetcher.get();
fetcher_callback_map_[std::move(fetcher)] = std::move(done);
fetcher_ptr->Start();
}
// static
GURL GstaticJsonFetcher::GetFullUrl(const std::string& relative_path) {
return GURL(kGstaticUrlPrefix + relative_path);
}
void GstaticJsonFetcher::OnURLFetchComplete(const net::URLFetcher* source) {
auto find_fetcher = [source](const std::pair<std::unique_ptr<net::URLFetcher>,
FetchJsonFileCallback>& pair) {
return pair.first.get() == source;
};
auto it = std::find_if(fetcher_callback_map_.begin(),
fetcher_callback_map_.end(), find_fetcher);
if (it == fetcher_callback_map_.end()) {
LOG(DFATAL) << "Fetcher not found in the map";
return;
}
std::move(it->second).Run(GetResponse(std::move(it->first)));
fetcher_callback_map_.erase(it);
}
} // namespace remoting
\ No newline at end of file
// Copyright 2019 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 REMOTING_CLIENT_NOTIFICATION_GSTATIC_JSON_FETCHER_H_
#define REMOTING_CLIENT_NOTIFICATION_GSTATIC_JSON_FETCHER_H_
#include "base/containers/flat_map.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/optional.h"
#include "net/url_request/url_fetcher_delegate.h"
#include "remoting/client/notification/json_fetcher.h"
#include "url/gurl.h"
namespace remoting {
class URLRequestContextGetter;
// A JsonFetcher implementation that actually talks to the gstatic server to
// get back the JSON files.
class GstaticJsonFetcher final : public JsonFetcher,
public net::URLFetcherDelegate {
public:
GstaticJsonFetcher();
~GstaticJsonFetcher() override;
// JsonFetcher implementation.
void FetchJsonFile(
const std::string& relative_path,
FetchJsonFileCallback done,
const net::NetworkTrafficAnnotationTag& traffic_annotation) override;
private:
friend class GstaticJsonFetcherTest;
static GURL GetFullUrl(const std::string& relative_path);
// net::URLFetcherDelegate implementation.
void OnURLFetchComplete(const net::URLFetcher* source) override;
scoped_refptr<URLRequestContextGetter> request_context_getter_;
base::flat_map<std::unique_ptr<net::URLFetcher>, FetchJsonFileCallback>
fetcher_callback_map_;
DISALLOW_COPY_AND_ASSIGN(GstaticJsonFetcher);
};
} // namespace remoting
#endif // REMOTING_CLIENT_NOTIFICATION_GSTATIC_JSON_FETCHER_H_
\ No newline at end of file
// Copyright 2019 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 "remoting/client/notification/gstatic_json_fetcher.h"
#include <memory>
#include "base/run_loop.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "base/values.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "net/url_request/test_url_fetcher_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace remoting {
namespace {
using ::testing::Return;
constexpr char kTestPath1[] = "test_1.json";
constexpr char kTestPath2[] = "test_2.json";
constexpr char kTestJsonData1[] = R"({"a": 1})";
constexpr char kTestJsonData2[] = R"(["123"])";
MATCHER(NoJsonData, "") {
return !arg;
}
MATCHER(IsJsonData1, "") {
if (!arg || !arg->is_dict()) {
return false;
}
const base::Value* a = arg->FindKey("a");
return a && a->is_int() && a->GetInt() == 1;
}
MATCHER(IsJsonData2, "") {
if (!arg || !arg->is_list()) {
return false;
}
auto list = arg->GetList();
return list.size() == 1 && list[0].is_string() &&
list[0].GetString() == "123";
}
} // namespace
class GstaticJsonFetcherTest : public testing::Test {
protected:
void SetFakeOkResponse(const std::string& relative_path,
const std::string& data) {
url_fetcher_factory_.SetFakeResponse(
GstaticJsonFetcher::GetFullUrl(relative_path), data, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
}
void SetFakeFailedResponse(const std::string& relative_path,
net::HttpStatusCode status_code) {
url_fetcher_factory_.SetFakeResponse(
GstaticJsonFetcher::GetFullUrl(relative_path), "", status_code,
net::URLRequestStatus::FAILED);
}
base::test::SingleThreadTaskEnvironment task_environment_{
base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
net::FakeURLFetcherFactory url_fetcher_factory_{nullptr};
GstaticJsonFetcher fetcher_;
};
TEST_F(GstaticJsonFetcherTest, FetchJsonFileSuccess) {
SetFakeOkResponse(kTestPath1, kTestJsonData1);
base::MockCallback<JsonFetcher::FetchJsonFileCallback> callback;
base::RunLoop run_loop;
EXPECT_CALL(callback, Run(IsJsonData1()))
.WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
fetcher_.FetchJsonFile(kTestPath1, callback.Get(),
TRAFFIC_ANNOTATION_FOR_TESTS);
run_loop.Run();
}
TEST_F(GstaticJsonFetcherTest, FetchTwoJsonFilesInParallel) {
SetFakeOkResponse(kTestPath1, kTestJsonData1);
SetFakeOkResponse(kTestPath2, kTestJsonData2);
base::RunLoop run_loop;
base::MockRepeatingClosure quit_on_second_run;
EXPECT_CALL(quit_on_second_run, Run())
.WillOnce(Return())
.WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
base::MockCallback<JsonFetcher::FetchJsonFileCallback> callback1;
EXPECT_CALL(callback1, Run(IsJsonData1()))
.WillOnce(base::test::RunClosure(quit_on_second_run.Get()));
fetcher_.FetchJsonFile(kTestPath1, callback1.Get(),
TRAFFIC_ANNOTATION_FOR_TESTS);
base::MockCallback<JsonFetcher::FetchJsonFileCallback> callback2;
EXPECT_CALL(callback2, Run(IsJsonData2()))
.WillOnce(base::test::RunClosure(quit_on_second_run.Get()));
fetcher_.FetchJsonFile(kTestPath2, callback2.Get(),
TRAFFIC_ANNOTATION_FOR_TESTS);
run_loop.Run();
}
TEST_F(GstaticJsonFetcherTest, FetchJsonFileNotOk) {
SetFakeFailedResponse(kTestPath1, net::HTTP_INTERNAL_SERVER_ERROR);
base::MockCallback<JsonFetcher::FetchJsonFileCallback> callback;
base::RunLoop run_loop;
EXPECT_CALL(callback, Run(NoJsonData()))
.WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
fetcher_.FetchJsonFile(kTestPath1, callback.Get(),
TRAFFIC_ANNOTATION_FOR_TESTS);
run_loop.Run();
}
TEST_F(GstaticJsonFetcherTest, FetchJsonFileMalformed) {
SetFakeOkResponse(kTestPath1, "Malformed!");
base::MockCallback<JsonFetcher::FetchJsonFileCallback> callback;
base::RunLoop run_loop;
EXPECT_CALL(callback, Run(NoJsonData()))
.WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
fetcher_.FetchJsonFile(kTestPath1, callback.Get(),
TRAFFIC_ANNOTATION_FOR_TESTS);
run_loop.Run();
}
} // namespace remoting
\ No newline at end of file
......@@ -13,11 +13,18 @@ namespace base {
class Value;
} // namespace base
namespace net {
struct NetworkTrafficAnnotationTag;
} // namespace net
namespace remoting {
// Interface for fetching a JSON file under https://www.gstatic.com/chromoting.
class JsonFetcher {
public:
using FetchJsonFileCallback =
base::OnceCallback<void(base::Optional<base::Value>)>;
JsonFetcher() = default;
virtual ~JsonFetcher() = default;
......@@ -29,7 +36,8 @@ class JsonFetcher {
// MUST NOT keep |done| after its destructor is called.
virtual void FetchJsonFile(
const std::string& relative_path,
base::OnceCallback<void(base::Optional<base::Value>)> done) const = 0;
FetchJsonFileCallback done,
const net::NetworkTrafficAnnotationTag& traffic_annotation) = 0;
};
} // namespace remoting
......
......@@ -11,6 +11,7 @@
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/values.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "remoting/client/notification/json_fetcher.h"
#include "remoting/client/notification/notification_message.h"
#include "remoting/client/notification/version_range.h"
......@@ -23,6 +24,30 @@ constexpr char kDefaultLocale[] = "en-US";
constexpr char kNotificationRootPath[] = "notification/";
constexpr char kNotificationRulesPath[] = "notification/rules.json";
constexpr net::NetworkTrafficAnnotationTag kTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("notification_client",
R"(
semantics {
sender: "Chromoting notification client"
description:
"Fetches new notification messages to be shown on Chrome Remote "
"Desktop app."
trigger:
"Opening the Chrome Remote Desktop app."
data: "No user data."
destination: OTHER
destination_other:
"The Chrome Remote Desktop app."
}
policy {
cookies_allowed: NO
setting:
"This request cannot be stopped in settings, but will not be sent "
"if user does not use Chrome Remote Desktop."
policy_exception_justification:
"Not implemented."
})");
template <typename T, typename IsTChecker, typename TGetter>
bool FindKeyAndGet(const base::Value& dict,
const std::string& key,
......@@ -151,7 +176,8 @@ void NotificationClient::GetNotification(const std::string& user_email,
fetcher_->FetchJsonFile(
kNotificationRulesPath,
base::BindOnce(&NotificationClient::OnRulesFetched,
base::Unretained(this), user_email, std::move(callback)));
base::Unretained(this), user_email, std::move(callback)),
kTrafficAnnotation);
}
void NotificationClient::OnRulesFetched(const std::string& user_email,
......@@ -269,11 +295,13 @@ void NotificationClient::FetchTranslatedTexts(
fetcher_->FetchJsonFile(
kNotificationRootPath + message_text_filename,
base::BindOnce(&MessageAndLinkTextResults::OnMessageTranslationsFetched,
results));
results),
kTrafficAnnotation);
fetcher_->FetchJsonFile(
kNotificationRootPath + link_text_filename,
base::BindOnce(&MessageAndLinkTextResults::OnLinkTranslationsFetched,
results));
results),
kTrafficAnnotation);
}
} // namespace remoting
......@@ -33,8 +33,8 @@ class MockJsonFetcher : public JsonFetcher {
MOCK_CONST_METHOD1(FetchJsonFile,
base::Optional<base::Value>(const std::string&));
void FetchJsonFile(const std::string& relative_path,
base::OnceCallback<void(base::Optional<base::Value>)> done)
const override {
FetchJsonFileCallback done,
const net::NetworkTrafficAnnotationTag&) override {
auto value_opt = FetchJsonFile(relative_path);
std::move(done).Run(std::move(value_opt));
}
......
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