OLD | NEW |
---|---|
(Empty) | |
1 # -*- coding: utf-8 -*- | |
2 import argparse | |
3 import json | |
4 import os | |
5 import signal | |
6 import socket | |
7 import sys | |
8 import threading | |
9 import time | |
10 import traceback | |
11 import urllib2 | |
12 import uuid | |
13 from collections import defaultdict | |
14 from multiprocessing import Process, Event | |
15 | |
16 from .. import localpaths | |
17 | |
18 import sslutils | |
19 from wptserve import server as wptserve, handlers | |
20 from wptserve.logger import set_logger | |
21 sys.path.insert(0, os.path.abspath(os.path.join('..', '..', '..', '..', '..', 'w ebkitpy', 'thirdparty'))) | |
burnik
2015/06/12 01:40:53
Hm, so this injected line didn't help fix the mod_
| |
22 from mod_pywebsocket import standalone as pywebsocket | |
23 | |
24 repo_root = localpaths.repo_root | |
25 | |
26 class WorkersHandler(object): | |
27 def __init__(self): | |
28 self.handler = handlers.handler(self.handle_request) | |
29 | |
30 def __call__(self, request, response): | |
31 return self.handler(request, response) | |
32 | |
33 def handle_request(self, request, response): | |
34 worker_path = request.url_parts.path.replace(".worker", ".worker.js") | |
35 return """<!doctype html> | |
36 <meta charset=utf-8> | |
37 <script src="/resources/testharness.js"></script> | |
38 <script src="/resources/testharnessreport.js"></script> | |
39 <div id=log></div> | |
40 <script> | |
41 fetch_tests_from_worker(new Worker("%s")); | |
42 </script> | |
43 """ % (worker_path,) | |
44 | |
45 rewrites = [("GET", "/resources/WebIDLParser.js", "/resources/webidl2/lib/webidl 2.js")] | |
46 | |
47 subdomains = [u"www", | |
48 u"www1", | |
49 u"www2", | |
50 u"天気の良い日", | |
51 u"élève"] | |
52 | |
53 def default_routes(): | |
54 return [("GET", "/tools/runner/*", handlers.file_handler), | |
55 ("POST", "/tools/runner/update_manifest.py", handlers.python_script_ handler), | |
56 ("*", "/_certs/*", handlers.ErrorHandler(404)), | |
57 ("*", "/tools/*", handlers.ErrorHandler(404)), | |
58 ("*", "{spec}/tools/*", handlers.ErrorHandler(404)), | |
59 ("*", "/serve.py", handlers.ErrorHandler(404)), | |
60 ("*", "*.py", handlers.python_script_handler), | |
61 ("GET", "*.asis", handlers.as_is_handler), | |
62 ("GET", "*.worker", WorkersHandler()), | |
63 ("GET", "*", handlers.file_handler),] | |
64 | |
65 def setup_logger(level): | |
66 import logging | |
67 global logger | |
68 logger = logging.getLogger("web-platform-tests") | |
69 logging.basicConfig(level=getattr(logging, level.upper())) | |
70 set_logger(logger) | |
71 | |
72 | |
73 def open_socket(port): | |
74 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
75 if port != 0: | |
76 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | |
77 sock.bind(('127.0.0.1', port)) | |
78 sock.listen(5) | |
79 return sock | |
80 | |
81 | |
82 def get_port(): | |
83 free_socket = open_socket(0) | |
84 port = free_socket.getsockname()[1] | |
85 logger.debug("Going to use port %s" % port) | |
86 free_socket.close() | |
87 return port | |
88 | |
89 | |
90 class ServerProc(object): | |
91 def __init__(self): | |
92 self.proc = None | |
93 self.daemon = None | |
94 self.stop = Event() | |
95 | |
96 def start(self, init_func, host, port, paths, routes, bind_hostname, externa l_config, | |
97 ssl_config, **kwargs): | |
98 self.proc = Process(target=self.create_daemon, | |
99 args=(init_func, host, port, paths, routes, bind_hos tname, | |
100 external_config, ssl_config)) | |
101 self.proc.daemon = True | |
102 self.proc.start() | |
103 | |
104 def create_daemon(self, init_func, host, port, paths, routes, bind_hostname, | |
105 external_config, ssl_config, **kwargs): | |
106 try: | |
107 self.daemon = init_func(host, port, paths, routes, bind_hostname, ex ternal_config, | |
108 ssl_config, **kwargs) | |
109 except socket.error: | |
110 print >> sys.stderr, "Socket error on port %s" % port | |
111 raise | |
112 except: | |
113 print >> sys.stderr, traceback.format_exc() | |
114 raise | |
115 | |
116 if self.daemon: | |
117 try: | |
118 self.daemon.start(block=False) | |
119 try: | |
120 self.stop.wait() | |
121 except KeyboardInterrupt: | |
122 pass | |
123 except: | |
124 print >> sys.stderr, traceback.format_exc() | |
125 raise | |
126 | |
127 def wait(self): | |
128 self.stop.set() | |
129 self.proc.join() | |
130 | |
131 def kill(self): | |
132 self.stop.set() | |
133 self.proc.terminate() | |
134 self.proc.join() | |
135 | |
136 def is_alive(self): | |
137 return self.proc.is_alive() | |
138 | |
139 | |
140 def check_subdomains(host, paths, bind_hostname, ssl_config): | |
141 port = get_port() | |
142 subdomains = get_subdomains(host) | |
143 | |
144 wrapper = ServerProc() | |
145 wrapper.start(start_http_server, host, port, paths, default_routes(), bind_h ostname, | |
146 None, ssl_config) | |
147 | |
148 connected = False | |
149 for i in range(10): | |
150 try: | |
151 urllib2.urlopen("http://%s:%d/" % (host, port)) | |
152 connected = True | |
153 break | |
154 except urllib2.URLError: | |
155 time.sleep(1) | |
156 | |
157 if not connected: | |
158 logger.critical("Failed to connect to test server on http://%s:%s You ma y need to edit /etc/hosts or similar" % (host, port)) | |
159 sys.exit(1) | |
160 | |
161 for subdomain, (punycode, host) in subdomains.iteritems(): | |
162 domain = "%s.%s" % (punycode, host) | |
163 try: | |
164 urllib2.urlopen("http://%s:%d/" % (domain, port)) | |
165 except Exception as e: | |
166 logger.critical("Failed probing domain %s. You may need to edit /etc /hosts or similar." % domain) | |
167 sys.exit(1) | |
168 | |
169 wrapper.wait() | |
170 | |
171 | |
172 def get_subdomains(host): | |
173 #This assumes that the tld is ascii-only or already in punycode | |
174 return {subdomain: (subdomain.encode("idna"), host) | |
175 for subdomain in subdomains} | |
176 | |
177 | |
178 def start_servers(host, ports, paths, routes, bind_hostname, external_config, ss l_config, | |
179 **kwargs): | |
180 servers = defaultdict(list) | |
181 for scheme, ports in ports.iteritems(): | |
182 assert len(ports) == {"http":2}.get(scheme, 1) | |
183 | |
184 for port in ports: | |
185 if port is None: | |
186 continue | |
187 init_func = {"http":start_http_server, | |
188 "https":start_https_server, | |
189 "ws":start_ws_server, | |
190 "wss":start_wss_server}[scheme] | |
191 | |
192 server_proc = ServerProc() | |
193 server_proc.start(init_func, host, port, paths, routes, bind_hostnam e, | |
194 external_config, ssl_config, **kwargs) | |
195 servers[scheme].append((port, server_proc)) | |
196 | |
197 return servers | |
198 | |
199 | |
200 def start_http_server(host, port, paths, routes, bind_hostname, external_config, ssl_config, | |
201 **kwargs): | |
202 return wptserve.WebTestHttpd(host=host, | |
203 port=port, | |
204 doc_root=paths["doc_root"], | |
205 routes=routes, | |
206 rewrites=rewrites, | |
207 bind_hostname=bind_hostname, | |
208 config=external_config, | |
209 use_ssl=False, | |
210 key_file=None, | |
211 certificate=None, | |
212 latency=kwargs.get("latency")) | |
213 | |
214 | |
215 def start_https_server(host, port, paths, routes, bind_hostname, external_config , ssl_config, | |
216 **kwargs): | |
217 return wptserve.WebTestHttpd(host=host, | |
218 port=port, | |
219 doc_root=paths["doc_root"], | |
220 routes=routes, | |
221 rewrites=rewrites, | |
222 bind_hostname=bind_hostname, | |
223 config=external_config, | |
224 use_ssl=True, | |
225 key_file=ssl_config["key_path"], | |
226 certificate=ssl_config["cert_path"], | |
227 encrypt_after_connect=ssl_config["encrypt_after _connect"], | |
228 latency=kwargs.get("latency")) | |
229 | |
230 | |
231 class WebSocketDaemon(object): | |
232 def __init__(self, host, port, doc_root, handlers_root, log_level, bind_host name, | |
233 ssl_config): | |
234 self.host = host | |
235 cmd_args = ["-p", port, | |
236 "-d", doc_root, | |
237 "-w", handlers_root, | |
238 "--log-level", log_level] | |
239 | |
240 if ssl_config is not None: | |
241 # This is usually done through pywebsocket.main, however we're | |
242 # working around that to get the server instance and manually | |
243 # setup the wss server. | |
244 if pywebsocket._import_ssl(): | |
245 tls_module = pywebsocket._TLS_BY_STANDARD_MODULE | |
246 elif pywebsocket._import_pyopenssl(): | |
247 tls_module = pywebsocket._TLS_BY_PYOPENSSL | |
248 else: | |
249 print "No SSL module available" | |
250 sys.exit(1) | |
251 | |
252 cmd_args += ["--tls", | |
253 "--private-key", ssl_config["key_path"], | |
254 "--certificate", ssl_config["cert_path"], | |
255 "--tls-module", tls_module] | |
256 | |
257 if (bind_hostname): | |
258 cmd_args = ["-H", host] + cmd_args | |
259 opts, args = pywebsocket._parse_args_and_config(cmd_args) | |
260 opts.cgi_directories = [] | |
261 opts.is_executable_method = None | |
262 self.server = pywebsocket.WebSocketServer(opts) | |
263 ports = [item[0].getsockname()[1] for item in self.server._sockets] | |
264 assert all(item == ports[0] for item in ports) | |
265 self.port = ports[0] | |
266 self.started = False | |
267 self.server_thread = None | |
268 | |
269 def start(self, block=False): | |
270 self.started = True | |
271 if block: | |
272 self.server.serve_forever() | |
273 else: | |
274 self.server_thread = threading.Thread(target=self.server.serve_forev er) | |
275 self.server_thread.setDaemon(True) # don't hang on exit | |
276 self.server_thread.start() | |
277 | |
278 def stop(self): | |
279 """ | |
280 Stops the server. | |
281 | |
282 If the server is not running, this method has no effect. | |
283 """ | |
284 if self.started: | |
285 try: | |
286 self.server.shutdown() | |
287 self.server.server_close() | |
288 self.server_thread.join() | |
289 self.server_thread = None | |
290 except AttributeError: | |
291 pass | |
292 self.started = False | |
293 self.server = None | |
294 | |
295 | |
296 def start_ws_server(host, port, paths, routes, bind_hostname, external_config, s sl_config, | |
297 **kwargs): | |
298 return WebSocketDaemon(host, | |
299 str(port), | |
300 repo_root, | |
301 paths["ws_doc_root"], | |
302 "debug", | |
303 bind_hostname, | |
304 ssl_config = None) | |
305 | |
306 | |
307 def start_wss_server(host, port, paths, routes, bind_hostname, external_config, ssl_config, | |
308 **kwargs): | |
309 return WebSocketDaemon(host, | |
310 str(port), | |
311 repo_root, | |
312 paths["ws_doc_root"], | |
313 "debug", | |
314 bind_hostname, | |
315 ssl_config) | |
316 | |
317 | |
318 def get_ports(config, ssl_environment): | |
319 rv = defaultdict(list) | |
320 for scheme, ports in config["ports"].iteritems(): | |
321 for i, port in enumerate(ports): | |
322 if scheme in ["wss", "https"] and not ssl_environment.ssl_enabled: | |
323 port = None | |
324 if port == "auto": | |
325 port = get_port() | |
326 else: | |
327 port = port | |
328 rv[scheme].append(port) | |
329 return rv | |
330 | |
331 | |
332 | |
333 def normalise_config(config, ports): | |
334 host = config["external_host"] if config["external_host"] else config["host" ] | |
335 domains = get_subdomains(host) | |
336 ports_ = {} | |
337 for scheme, ports_used in ports.iteritems(): | |
338 ports_[scheme] = ports_used | |
339 | |
340 for key, value in domains.iteritems(): | |
341 domains[key] = ".".join(value) | |
342 | |
343 domains[""] = host | |
344 | |
345 ports_ = {} | |
346 for scheme, ports_used in ports.iteritems(): | |
347 ports_[scheme] = ports_used | |
348 | |
349 return {"host": host, | |
350 "domains": domains, | |
351 "ports": ports_} | |
352 | |
353 | |
354 def get_ssl_config(config, external_domains, ssl_environment): | |
355 key_path, cert_path = ssl_environment.host_cert_path(external_domains) | |
356 return {"key_path": key_path, | |
357 "cert_path": cert_path, | |
358 "encrypt_after_connect": config["ssl"]["encrypt_after_connect"]} | |
359 | |
360 | |
361 def start(config, ssl_environment, routes, **kwargs): | |
362 host = config["host"] | |
363 domains = get_subdomains(host) | |
364 ports = get_ports(config, ssl_environment) | |
365 bind_hostname = config["bind_hostname"] | |
366 | |
367 paths = {"doc_root": config["doc_root"], | |
368 "ws_doc_root": config["ws_doc_root"]} | |
369 | |
370 external_config = normalise_config(config, ports) | |
371 | |
372 ssl_config = get_ssl_config(config, external_config["domains"].values(), ssl _environment) | |
373 | |
374 if config["check_subdomains"]: | |
375 check_subdomains(host, paths, bind_hostname, ssl_config) | |
376 | |
377 servers = start_servers(host, ports, paths, routes, bind_hostname, external_ config, | |
378 ssl_config, **kwargs) | |
379 | |
380 return external_config, servers | |
381 | |
382 | |
383 def iter_procs(servers): | |
384 for servers in servers.values(): | |
385 for port, server in servers: | |
386 yield server.proc | |
387 | |
388 | |
389 def value_set(config, key): | |
390 return key in config and config[key] is not None | |
391 | |
392 | |
393 def get_value_or_default(config, key, default=None): | |
394 return config[key] if value_set(config, key) else default | |
395 | |
396 | |
397 def set_computed_defaults(config): | |
398 if not value_set(config, "doc_root"): | |
399 config["doc_root"] = repo_root | |
400 | |
401 if not value_set(config, "ws_doc_root"): | |
402 root = get_value_or_default(config, "doc_root", default=repo_root) | |
403 config["ws_doc_root"] = os.path.join(root, "websockets", "handlers") | |
404 | |
405 | |
406 def merge_json(base_obj, override_obj): | |
407 rv = {} | |
408 for key, value in base_obj.iteritems(): | |
409 if key not in override_obj: | |
410 rv[key] = value | |
411 else: | |
412 if isinstance(value, dict): | |
413 rv[key] = merge_json(value, override_obj[key]) | |
414 else: | |
415 rv[key] = override_obj[key] | |
416 return rv | |
417 | |
418 | |
419 def get_ssl_environment(config): | |
420 implementation_type = config["ssl"]["type"] | |
421 cls = sslutils.environments[implementation_type] | |
422 try: | |
423 kwargs = config["ssl"][implementation_type].copy() | |
424 except KeyError: | |
425 raise ValueError("%s is not a vaid ssl type." % implementation_type) | |
426 return cls(logger, **kwargs) | |
427 | |
428 | |
429 def load_config(default_path, override_path=None, **kwargs): | |
430 if os.path.exists(default_path): | |
431 with open(default_path) as f: | |
432 base_obj = json.load(f) | |
433 else: | |
434 raise ValueError("Config path %s does not exist" % default_path) | |
435 | |
436 if os.path.exists(override_path): | |
437 with open(override_path) as f: | |
438 override_obj = json.load(f) | |
439 else: | |
440 override_obj = {} | |
441 rv = merge_json(base_obj, override_obj) | |
442 | |
443 if kwargs.get("config_path"): | |
444 other_path = os.path.abspath(os.path.expanduser(kwargs.get("config_path" ))) | |
445 if os.path.exists(other_path): | |
446 base_obj = rv | |
447 with open(other_path) as f: | |
448 override_obj = json.load(f) | |
449 rv = merge_json(base_obj, override_obj) | |
450 else: | |
451 raise ValueError("Config path %s does not exist" % other_path) | |
452 | |
453 overriding_path_args = [("doc_root", "Document root"), | |
454 ("ws_doc_root", "WebSockets document root")] | |
455 for key, title in overriding_path_args: | |
456 value = kwargs.get(key) | |
457 if value is None: | |
458 continue | |
459 value = os.path.abspath(os.path.expanduser(value)) | |
460 if not os.path.exists(value): | |
461 raise ValueError("%s path %s does not exist" % (title, value)) | |
462 rv[key] = value | |
463 | |
464 set_computed_defaults(rv) | |
465 return rv | |
466 | |
467 | |
468 def get_parser(): | |
469 parser = argparse.ArgumentParser() | |
470 parser.add_argument("--latency", type=int, | |
471 help="Artificial latency to add before sending http resp onses, in ms") | |
472 parser.add_argument("--config", action="store", dest="config_path", | |
473 help="Path to external config file") | |
474 parser.add_argument("--doc_root", action="store", dest="doc_root", | |
475 help="Path to document root. Overrides config.") | |
476 parser.add_argument("--ws_doc_root", action="store", dest="ws_doc_root", | |
477 help="Path to WebSockets document root. Overrides config .") | |
478 return parser | |
479 | |
480 | |
481 def main(): | |
482 kwargs = vars(get_parser().parse_args()) | |
483 config = load_config("config.default.json", | |
484 "config.json", | |
485 **kwargs) | |
486 | |
487 setup_logger(config["log_level"]) | |
488 | |
489 with get_ssl_environment(config) as ssl_env: | |
490 config_, servers = start(config, ssl_env, default_routes(), **kwargs) | |
491 | |
492 try: | |
493 while any(item.is_alive() for item in iter_procs(servers)): | |
494 for item in iter_procs(servers): | |
495 item.join(1) | |
496 except KeyboardInterrupt: | |
497 logger.info("Shutting down") | |
OLD | NEW |