// Copyright 2013 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/test/spawned_test_server/remote_test_server.h" #include #include #include #include "base/base_paths.h" #include "base/command_line.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/json/json_reader.h" #include "base/json/json_writer.h" #include "base/logging.h" #include "base/message_loop/message_pump_type.h" #include "base/path_service.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_split.h" #include "base/strings/stringprintf.h" #include "base/threading/thread_restrictions.h" #include "base/values.h" #include "build/build_config.h" #include "net/base/host_port_pair.h" #include "net/base/ip_endpoint.h" #include "net/base/net_errors.h" #include "net/test/spawned_test_server/remote_test_server_spawner_request.h" #include "url/gurl.h" namespace net { namespace { // Please keep in sync with dictionary SERVER_TYPES in testserver.py std::string GetServerTypeString(BaseTestServer::Type type) { switch (type) { case BaseTestServer::TYPE_WS: case BaseTestServer::TYPE_WSS: return "ws"; default: NOTREACHED(); } } #if !BUILDFLAG(IS_FUCHSIA) // Returns platform-specific path to the config file for the test server. base::FilePath GetTestServerConfigFilePath() { base::FilePath dir; #if BUILDFLAG(IS_ANDROID) base::PathService::Get(base::DIR_ANDROID_EXTERNAL_STORAGE, &dir); #else base::PathService::Get(base::DIR_TEMP, &dir); #endif return dir.AppendASCII("net-test-server-config"); } #endif // !BUILDFLAG(IS_FUCHSIA) // Reads base URL for the test server spawner. That URL is used to control the // test server. std::string GetSpawnerUrlBase() { #if BUILDFLAG(IS_FUCHSIA) std::string spawner_url_base( base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( "remote-test-server-spawner-url-base")); LOG_IF(FATAL, spawner_url_base.empty()) << "--remote-test-server-spawner-url-base missing from command line"; return spawner_url_base; #else // BUILDFLAG(IS_FUCHSIA) base::ScopedAllowBlockingForTesting allow_blocking; base::FilePath config_path = GetTestServerConfigFilePath(); if (!base::PathExists(config_path)) return ""; std::string config_json; if (!ReadFileToString(config_path, &config_json)) LOG(FATAL) << "Failed to read " << config_path.value(); std::optional config = base::JSONReader::Read(config_json); if (!config) LOG(FATAL) << "Failed to parse " << config_path.value(); std::string* result = config->GetDict().FindString("spawner_url_base"); if (!result) LOG(FATAL) << "spawner_url_base is not specified in the config"; return *result; #endif // BUILDFLAG(IS_FUCHSIA) } } // namespace RemoteTestServer::RemoteTestServer(Type type, const base::FilePath& document_root) : BaseTestServer(type), io_thread_("RemoteTestServer IO Thread") { if (!Init(document_root)) { NOTREACHED(); } } RemoteTestServer::RemoteTestServer(Type type, const SSLOptions& ssl_options, const base::FilePath& document_root) : BaseTestServer(type, ssl_options), io_thread_("RemoteTestServer IO Thread") { if (!Init(document_root)) { NOTREACHED(); } } RemoteTestServer::~RemoteTestServer() { Stop(); } bool RemoteTestServer::StartInBackground() { DCHECK(!started()); DCHECK(!start_request_); std::optional arguments_dict = GenerateArguments(); if (!arguments_dict) return false; arguments_dict->Set("on-remote-server", base::Value()); // Append the 'server-type' argument which is used by spawner server to // pass right server type to Python test server. arguments_dict->Set("server-type", GetServerTypeString(type())); // Generate JSON-formatted argument string. std::string arguments_string; base::JSONWriter::Write(*arguments_dict, &arguments_string); if (arguments_string.empty()) return false; start_request_ = std::make_unique( io_thread_.task_runner(), GetSpawnerUrl("start"), arguments_string); return true; } bool RemoteTestServer::BlockUntilStarted() { DCHECK(start_request_); std::string server_data_json; bool request_result = start_request_->WaitForCompletion(&server_data_json); start_request_.reset(); if (!request_result) return false; // Parse server_data_json. if (server_data_json.empty() || !SetAndParseServerData(server_data_json, &remote_port_)) { LOG(ERROR) << "Could not parse server_data: " << server_data_json; return false; } SetPort(remote_port_); return SetupWhenServerStarted(); } bool RemoteTestServer::Stop() { DCHECK(!start_request_); if (remote_port_) { std::unique_ptr kill_request = std::make_unique( io_thread_.task_runner(), GetSpawnerUrl(base::StringPrintf("kill?port=%d", remote_port_)), std::string()); if (!kill_request->WaitForCompletion(nullptr)) LOG(FATAL) << "Failed stopping RemoteTestServer"; remote_port_ = 0; } CleanUpWhenStoppingServer(); return true; } // On Android, the document root in the device is not the same as the document // root in the host machine where the test server is launched. So prepend // DIR_SRC_TEST_DATA_ROOT here to get the actual path of document root on the // Android device. base::FilePath RemoteTestServer::GetDocumentRoot() const { base::FilePath src_dir; base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &src_dir); return src_dir.Append(document_root()); } bool RemoteTestServer::Init(const base::FilePath& document_root) { if (document_root.IsAbsolute()) return false; spawner_url_base_ = GetSpawnerUrlBase(); bool thread_started = io_thread_.StartWithOptions( base::Thread::Options(base::MessagePumpType::IO, 0)); CHECK(thread_started); // Unlike LocalTestServer, RemoteTestServer passes relative paths to the test // server. The test server fails on empty strings in some configurations. base::FilePath fixed_root = document_root; if (fixed_root.empty()) fixed_root = base::FilePath(base::FilePath::kCurrentDirectory); SetResourcePath(fixed_root, base::FilePath() .AppendASCII("net") .AppendASCII("data") .AppendASCII("ssl") .AppendASCII("certificates")); return true; } GURL RemoteTestServer::GetSpawnerUrl(const std::string& command) const { CHECK(!spawner_url_base_.empty()); std::string url = spawner_url_base_ + "/" + command; GURL result = GURL(url); CHECK(result.is_valid()) << url; return result; } } // namespace net