1// Copyright 2013 The Chromium Authors. All rights reserved. 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/local_test_server.h" 6 7#include "base/command_line.h" 8#include "base/json/json_reader.h" 9#include "base/logging.h" 10#include "base/path_service.h" 11#include "base/process/kill.h" 12#include "base/strings/string_number_conversions.h" 13#include "base/values.h" 14#include "net/base/host_port_pair.h" 15#include "net/base/net_errors.h" 16#include "net/test/python_utils.h" 17#include "url/gurl.h" 18 19namespace net { 20 21namespace { 22 23bool AppendArgumentFromJSONValue(const std::string& key, 24 const base::Value& value_node, 25 base::CommandLine* command_line) { 26 std::string argument_name = "--" + key; 27 switch (value_node.GetType()) { 28 case base::Value::TYPE_NULL: 29 command_line->AppendArg(argument_name); 30 break; 31 case base::Value::TYPE_INTEGER: { 32 int value; 33 bool result = value_node.GetAsInteger(&value); 34 DCHECK(result); 35 command_line->AppendArg(argument_name + "=" + base::IntToString(value)); 36 break; 37 } 38 case base::Value::TYPE_STRING: { 39 std::string value; 40 bool result = value_node.GetAsString(&value); 41 if (!result || value.empty()) 42 return false; 43 command_line->AppendArg(argument_name + "=" + value); 44 break; 45 } 46 case base::Value::TYPE_BOOLEAN: 47 case base::Value::TYPE_DOUBLE: 48 case base::Value::TYPE_LIST: 49 case base::Value::TYPE_DICTIONARY: 50 case base::Value::TYPE_BINARY: 51 default: 52 NOTREACHED() << "improper json type"; 53 return false; 54 } 55 return true; 56} 57 58} // namespace 59 60LocalTestServer::LocalTestServer(Type type, 61 const std::string& host, 62 const base::FilePath& document_root) 63 : BaseTestServer(type, host) { 64 if (!Init(document_root)) 65 NOTREACHED(); 66} 67 68LocalTestServer::LocalTestServer(Type type, 69 const SSLOptions& ssl_options, 70 const base::FilePath& document_root) 71 : BaseTestServer(type, ssl_options) { 72 if (!Init(document_root)) 73 NOTREACHED(); 74} 75 76LocalTestServer::~LocalTestServer() { 77 Stop(); 78} 79 80bool LocalTestServer::GetTestServerPath(base::FilePath* testserver_path) const { 81 base::FilePath testserver_dir; 82 if (!PathService::Get(base::DIR_SOURCE_ROOT, &testserver_dir)) { 83 LOG(ERROR) << "Failed to get DIR_SOURCE_ROOT"; 84 return false; 85 } 86 testserver_dir = testserver_dir.Append(FILE_PATH_LITERAL("net")) 87 .Append(FILE_PATH_LITERAL("tools")) 88 .Append(FILE_PATH_LITERAL("testserver")); 89 *testserver_path = testserver_dir.Append(FILE_PATH_LITERAL("testserver.py")); 90 return true; 91} 92 93bool LocalTestServer::Start() { 94 return StartInBackground() && BlockUntilStarted(); 95} 96 97bool LocalTestServer::StartInBackground() { 98 // Get path to Python server script. 99 base::FilePath testserver_path; 100 if (!GetTestServerPath(&testserver_path)) 101 return false; 102 103 if (!SetPythonPath()) 104 return false; 105 106 if (!LaunchPython(testserver_path)) 107 return false; 108 109 return true; 110} 111 112bool LocalTestServer::BlockUntilStarted() { 113 if (!WaitToStart()) { 114 Stop(); 115 return false; 116 } 117 118 return SetupWhenServerStarted(); 119} 120 121bool LocalTestServer::Stop() { 122 CleanUpWhenStoppingServer(); 123 124 if (!process_handle_) 125 return true; 126 127 // First check if the process has already terminated. 128 bool ret = base::WaitForSingleProcess(process_handle_, base::TimeDelta()); 129 if (!ret) { 130 ret = base::KillProcess(process_handle_, 1, true); 131 } 132 133 if (ret) { 134 base::CloseProcessHandle(process_handle_); 135 process_handle_ = base::kNullProcessHandle; 136 } else { 137 VLOG(1) << "Kill failed?"; 138 } 139 140 return ret; 141} 142 143bool LocalTestServer::Init(const base::FilePath& document_root) { 144 if (document_root.IsAbsolute()) 145 return false; 146 147 // At this point, the port that the test server will listen on is unknown. 148 // The test server will listen on an ephemeral port, and write the port 149 // number out over a pipe that this TestServer object will read from. Once 150 // that is complete, the host port pair will contain the actual port. 151 DCHECK(!GetPort()); 152 process_handle_ = base::kNullProcessHandle; 153 154 base::FilePath src_dir; 155 if (!PathService::Get(base::DIR_SOURCE_ROOT, &src_dir)) 156 return false; 157 SetResourcePath(src_dir.Append(document_root), 158 src_dir.AppendASCII("net") 159 .AppendASCII("data") 160 .AppendASCII("ssl") 161 .AppendASCII("certificates")); 162 return true; 163} 164 165bool LocalTestServer::SetPythonPath() const { 166 base::FilePath third_party_dir; 167 if (!PathService::Get(base::DIR_SOURCE_ROOT, &third_party_dir)) { 168 LOG(ERROR) << "Failed to get DIR_SOURCE_ROOT"; 169 return false; 170 } 171 third_party_dir = third_party_dir.AppendASCII("third_party"); 172 173 // For simplejson. (simplejson, unlike all the other Python modules 174 // we include, doesn't have an extra 'simplejson' directory, so we 175 // need to include its parent directory, i.e. third_party_dir). 176 AppendToPythonPath(third_party_dir); 177 178 AppendToPythonPath(third_party_dir.AppendASCII("tlslite")); 179 AppendToPythonPath( 180 third_party_dir.AppendASCII("pyftpdlib").AppendASCII("src")); 181 AppendToPythonPath( 182 third_party_dir.AppendASCII("pywebsocket").AppendASCII("src")); 183 184 // Locate the Python code generated by the protocol buffers compiler. 185 base::FilePath pyproto_dir; 186 if (!GetPyProtoPath(&pyproto_dir)) { 187 LOG(WARNING) << "Cannot find pyproto dir for generated code. " 188 << "Testserver features that rely on it will not work"; 189 return true; 190 } 191 AppendToPythonPath(pyproto_dir); 192 193 return true; 194} 195 196bool LocalTestServer::AddCommandLineArguments( 197 base::CommandLine* command_line) const { 198 base::DictionaryValue arguments_dict; 199 if (!GenerateArguments(&arguments_dict)) 200 return false; 201 202 // Serialize the argument dictionary into CommandLine. 203 for (base::DictionaryValue::Iterator it(arguments_dict); !it.IsAtEnd(); 204 it.Advance()) { 205 const base::Value& value = it.value(); 206 const std::string& key = it.key(); 207 208 // Add arguments from a list. 209 if (value.IsType(base::Value::TYPE_LIST)) { 210 const base::ListValue* list = NULL; 211 if (!value.GetAsList(&list) || !list || list->empty()) 212 return false; 213 for (base::ListValue::const_iterator list_it = list->begin(); 214 list_it != list->end(); ++list_it) { 215 if (!AppendArgumentFromJSONValue(key, *(*list_it), command_line)) 216 return false; 217 } 218 } else if (!AppendArgumentFromJSONValue(key, value, command_line)) { 219 return false; 220 } 221 } 222 223 // Append the appropriate server type argument. 224 switch (type()) { 225 case TYPE_HTTP: // The default type is HTTP, no argument required. 226 break; 227 case TYPE_HTTPS: 228 command_line->AppendArg("--https"); 229 break; 230 case TYPE_WS: 231 case TYPE_WSS: 232 command_line->AppendArg("--websocket"); 233 break; 234 case TYPE_FTP: 235 command_line->AppendArg("--ftp"); 236 break; 237 case TYPE_TCP_ECHO: 238 command_line->AppendArg("--tcp-echo"); 239 break; 240 case TYPE_UDP_ECHO: 241 command_line->AppendArg("--udp-echo"); 242 break; 243 case TYPE_BASIC_AUTH_PROXY: 244 command_line->AppendArg("--basic-auth-proxy"); 245 break; 246 default: 247 NOTREACHED(); 248 return false; 249 } 250 251 return true; 252} 253 254} // namespace net 255