1 // Copyright 2013 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/test/spawned_test_server/remote_test_server.h"
6
7 #include <stdint.h>
8
9 #include <limits>
10 #include <vector>
11
12 #include "base/base_paths.h"
13 #include "base/command_line.h"
14 #include "base/files/file_path.h"
15 #include "base/files/file_util.h"
16 #include "base/json/json_reader.h"
17 #include "base/json/json_writer.h"
18 #include "base/logging.h"
19 #include "base/message_loop/message_pump_type.h"
20 #include "base/path_service.h"
21 #include "base/strings/string_number_conversions.h"
22 #include "base/strings/string_split.h"
23 #include "base/strings/stringprintf.h"
24 #include "base/threading/thread_restrictions.h"
25 #include "base/values.h"
26 #include "build/build_config.h"
27 #include "net/base/host_port_pair.h"
28 #include "net/base/ip_endpoint.h"
29 #include "net/base/net_errors.h"
30 #include "net/test/spawned_test_server/remote_test_server_spawner_request.h"
31 #include "url/gurl.h"
32
33 namespace net {
34
35 namespace {
36
37 // Please keep in sync with dictionary SERVER_TYPES in testserver.py
GetServerTypeString(BaseTestServer::Type type)38 std::string GetServerTypeString(BaseTestServer::Type type) {
39 switch (type) {
40 case BaseTestServer::TYPE_WS:
41 case BaseTestServer::TYPE_WSS:
42 return "ws";
43 default:
44 NOTREACHED();
45 }
46 }
47
48 #if !BUILDFLAG(IS_FUCHSIA)
49 // Returns platform-specific path to the config file for the test server.
GetTestServerConfigFilePath()50 base::FilePath GetTestServerConfigFilePath() {
51 base::FilePath dir;
52 #if BUILDFLAG(IS_ANDROID)
53 base::PathService::Get(base::DIR_ANDROID_EXTERNAL_STORAGE, &dir);
54 #else
55 base::PathService::Get(base::DIR_TEMP, &dir);
56 #endif
57 return dir.AppendASCII("net-test-server-config");
58 }
59 #endif // !BUILDFLAG(IS_FUCHSIA)
60
61 // Reads base URL for the test server spawner. That URL is used to control the
62 // test server.
GetSpawnerUrlBase()63 std::string GetSpawnerUrlBase() {
64 #if BUILDFLAG(IS_FUCHSIA)
65 std::string spawner_url_base(
66 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
67 "remote-test-server-spawner-url-base"));
68 LOG_IF(FATAL, spawner_url_base.empty())
69 << "--remote-test-server-spawner-url-base missing from command line";
70 return spawner_url_base;
71 #else // BUILDFLAG(IS_FUCHSIA)
72 base::ScopedAllowBlockingForTesting allow_blocking;
73
74 base::FilePath config_path = GetTestServerConfigFilePath();
75
76 if (!base::PathExists(config_path))
77 return "";
78
79 std::string config_json;
80 if (!ReadFileToString(config_path, &config_json))
81 LOG(FATAL) << "Failed to read " << config_path.value();
82
83 std::optional<base::Value> config = base::JSONReader::Read(config_json);
84 if (!config)
85 LOG(FATAL) << "Failed to parse " << config_path.value();
86
87 std::string* result = config->GetDict().FindString("spawner_url_base");
88 if (!result)
89 LOG(FATAL) << "spawner_url_base is not specified in the config";
90
91 return *result;
92 #endif // BUILDFLAG(IS_FUCHSIA)
93 }
94
95 } // namespace
96
RemoteTestServer(Type type,const base::FilePath & document_root)97 RemoteTestServer::RemoteTestServer(Type type,
98 const base::FilePath& document_root)
99 : BaseTestServer(type), io_thread_("RemoteTestServer IO Thread") {
100 if (!Init(document_root)) {
101 NOTREACHED();
102 }
103 }
104
RemoteTestServer(Type type,const SSLOptions & ssl_options,const base::FilePath & document_root)105 RemoteTestServer::RemoteTestServer(Type type,
106 const SSLOptions& ssl_options,
107 const base::FilePath& document_root)
108 : BaseTestServer(type, ssl_options),
109 io_thread_("RemoteTestServer IO Thread") {
110 if (!Init(document_root)) {
111 NOTREACHED();
112 }
113 }
114
~RemoteTestServer()115 RemoteTestServer::~RemoteTestServer() {
116 Stop();
117 }
118
StartInBackground()119 bool RemoteTestServer::StartInBackground() {
120 DCHECK(!started());
121 DCHECK(!start_request_);
122
123 std::optional<base::Value::Dict> arguments_dict = GenerateArguments();
124 if (!arguments_dict)
125 return false;
126
127 arguments_dict->Set("on-remote-server", base::Value());
128
129 // Append the 'server-type' argument which is used by spawner server to
130 // pass right server type to Python test server.
131 arguments_dict->Set("server-type", GetServerTypeString(type()));
132
133 // Generate JSON-formatted argument string.
134 std::string arguments_string;
135 base::JSONWriter::Write(*arguments_dict, &arguments_string);
136 if (arguments_string.empty())
137 return false;
138
139 start_request_ = std::make_unique<RemoteTestServerSpawnerRequest>(
140 io_thread_.task_runner(), GetSpawnerUrl("start"), arguments_string);
141
142 return true;
143 }
144
BlockUntilStarted()145 bool RemoteTestServer::BlockUntilStarted() {
146 DCHECK(start_request_);
147
148 std::string server_data_json;
149 bool request_result = start_request_->WaitForCompletion(&server_data_json);
150 start_request_.reset();
151 if (!request_result)
152 return false;
153
154 // Parse server_data_json.
155 if (server_data_json.empty() ||
156 !SetAndParseServerData(server_data_json, &remote_port_)) {
157 LOG(ERROR) << "Could not parse server_data: " << server_data_json;
158 return false;
159 }
160
161 SetPort(remote_port_);
162
163 return SetupWhenServerStarted();
164 }
165
Stop()166 bool RemoteTestServer::Stop() {
167 DCHECK(!start_request_);
168
169 if (remote_port_) {
170 std::unique_ptr<RemoteTestServerSpawnerRequest> kill_request =
171 std::make_unique<RemoteTestServerSpawnerRequest>(
172 io_thread_.task_runner(),
173 GetSpawnerUrl(base::StringPrintf("kill?port=%d", remote_port_)),
174 std::string());
175
176 if (!kill_request->WaitForCompletion(nullptr))
177 LOG(FATAL) << "Failed stopping RemoteTestServer";
178
179 remote_port_ = 0;
180 }
181
182 CleanUpWhenStoppingServer();
183
184 return true;
185 }
186
187 // On Android, the document root in the device is not the same as the document
188 // root in the host machine where the test server is launched. So prepend
189 // DIR_SRC_TEST_DATA_ROOT here to get the actual path of document root on the
190 // Android device.
GetDocumentRoot() const191 base::FilePath RemoteTestServer::GetDocumentRoot() const {
192 base::FilePath src_dir;
193 base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &src_dir);
194 return src_dir.Append(document_root());
195 }
196
Init(const base::FilePath & document_root)197 bool RemoteTestServer::Init(const base::FilePath& document_root) {
198 if (document_root.IsAbsolute())
199 return false;
200
201 spawner_url_base_ = GetSpawnerUrlBase();
202
203 bool thread_started = io_thread_.StartWithOptions(
204 base::Thread::Options(base::MessagePumpType::IO, 0));
205 CHECK(thread_started);
206
207 // Unlike LocalTestServer, RemoteTestServer passes relative paths to the test
208 // server. The test server fails on empty strings in some configurations.
209 base::FilePath fixed_root = document_root;
210 if (fixed_root.empty())
211 fixed_root = base::FilePath(base::FilePath::kCurrentDirectory);
212 SetResourcePath(fixed_root, base::FilePath()
213 .AppendASCII("net")
214 .AppendASCII("data")
215 .AppendASCII("ssl")
216 .AppendASCII("certificates"));
217 return true;
218 }
219
GetSpawnerUrl(const std::string & command) const220 GURL RemoteTestServer::GetSpawnerUrl(const std::string& command) const {
221 CHECK(!spawner_url_base_.empty());
222 std::string url = spawner_url_base_ + "/" + command;
223 GURL result = GURL(url);
224 CHECK(result.is_valid()) << url;
225 return result;
226 }
227
228 } // namespace net
229