Index: Tools/Scripts/webkitpy/thirdparty/wpt/wpt/tools/serve/serve.py |
diff --git a/Tools/Scripts/webkitpy/thirdparty/wpt/wpt/tools/serve/serve.py b/Tools/Scripts/webkitpy/thirdparty/wpt/wpt/tools/serve/serve.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..c232a412eda3e5e910fb32299d34d807771707f8 |
--- /dev/null |
+++ b/Tools/Scripts/webkitpy/thirdparty/wpt/wpt/tools/serve/serve.py |
@@ -0,0 +1,496 @@ |
+# -*- coding: utf-8 -*- |
+import argparse |
+import json |
+import os |
+import signal |
+import socket |
+import sys |
+import threading |
+import time |
+import traceback |
+import urllib2 |
+import uuid |
+from collections import defaultdict |
+from multiprocessing import Process, Event |
+ |
+from .. import localpaths |
+ |
+import sslutils |
+from wptserve import server as wptserve, handlers |
+from wptserve.logger import set_logger |
+from mod_pywebsocket import standalone as pywebsocket |
+ |
+repo_root = localpaths.repo_root |
+ |
+class WorkersHandler(object): |
+ def __init__(self): |
+ self.handler = handlers.handler(self.handle_request) |
+ |
+ def __call__(self, request, response): |
+ return self.handler(request, response) |
+ |
+ def handle_request(self, request, response): |
+ worker_path = request.url_parts.path.replace(".worker", ".worker.js") |
+ return """<!doctype html> |
+<meta charset=utf-8> |
+<script src="/resources/testharness.js"></script> |
+<script src="/resources/testharnessreport.js"></script> |
+<div id=log></div> |
+<script> |
+fetch_tests_from_worker(new Worker("%s")); |
+</script> |
+""" % (worker_path,) |
+ |
+rewrites = [("GET", "/resources/WebIDLParser.js", "/resources/webidl2/lib/webidl2.js")] |
+ |
+subdomains = [u"www", |
+ u"www1", |
+ u"www2", |
+ u"天気の良い日", |
+ u"élève"] |
+ |
+def default_routes(): |
+ return [("GET", "/tools/runner/*", handlers.file_handler), |
+ ("POST", "/tools/runner/update_manifest.py", handlers.python_script_handler), |
+ ("*", "/_certs/*", handlers.ErrorHandler(404)), |
+ ("*", "/tools/*", handlers.ErrorHandler(404)), |
+ ("*", "{spec}/tools/*", handlers.ErrorHandler(404)), |
+ ("*", "/serve.py", handlers.ErrorHandler(404)), |
+ ("*", "*.py", handlers.python_script_handler), |
+ ("GET", "*.asis", handlers.as_is_handler), |
+ ("GET", "*.worker", WorkersHandler()), |
+ ("GET", "*", handlers.file_handler),] |
+ |
+def setup_logger(level): |
+ import logging |
+ global logger |
+ logger = logging.getLogger("web-platform-tests") |
+ logging.basicConfig(level=getattr(logging, level.upper())) |
+ set_logger(logger) |
+ |
+ |
+def open_socket(port): |
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
+ if port != 0: |
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) |
+ sock.bind(('127.0.0.1', port)) |
+ sock.listen(5) |
+ return sock |
+ |
+ |
+def get_port(): |
+ free_socket = open_socket(0) |
+ port = free_socket.getsockname()[1] |
+ logger.debug("Going to use port %s" % port) |
+ free_socket.close() |
+ return port |
+ |
+ |
+class ServerProc(object): |
+ def __init__(self): |
+ self.proc = None |
+ self.daemon = None |
+ self.stop = Event() |
+ |
+ def start(self, init_func, host, port, paths, routes, bind_hostname, external_config, |
+ ssl_config, **kwargs): |
+ self.proc = Process(target=self.create_daemon, |
+ args=(init_func, host, port, paths, routes, bind_hostname, |
+ external_config, ssl_config)) |
+ self.proc.daemon = True |
+ self.proc.start() |
+ |
+ def create_daemon(self, init_func, host, port, paths, routes, bind_hostname, |
+ external_config, ssl_config, **kwargs): |
+ try: |
+ self.daemon = init_func(host, port, paths, routes, bind_hostname, external_config, |
+ ssl_config, **kwargs) |
+ except socket.error: |
+ print >> sys.stderr, "Socket error on port %s" % port |
+ raise |
+ except: |
+ print >> sys.stderr, traceback.format_exc() |
+ raise |
+ |
+ if self.daemon: |
+ try: |
+ self.daemon.start(block=False) |
+ try: |
+ self.stop.wait() |
+ except KeyboardInterrupt: |
+ pass |
+ except: |
+ print >> sys.stderr, traceback.format_exc() |
+ raise |
+ |
+ def wait(self): |
+ self.stop.set() |
+ self.proc.join() |
+ |
+ def kill(self): |
+ self.stop.set() |
+ self.proc.terminate() |
+ self.proc.join() |
+ |
+ def is_alive(self): |
+ return self.proc.is_alive() |
+ |
+ |
+def check_subdomains(host, paths, bind_hostname, ssl_config): |
+ port = get_port() |
+ subdomains = get_subdomains(host) |
+ |
+ wrapper = ServerProc() |
+ wrapper.start(start_http_server, host, port, paths, default_routes(), bind_hostname, |
+ None, ssl_config) |
+ |
+ connected = False |
+ for i in range(10): |
+ try: |
+ urllib2.urlopen("http://%s:%d/" % (host, port)) |
+ connected = True |
+ break |
+ except urllib2.URLError: |
+ time.sleep(1) |
+ |
+ if not connected: |
+ logger.critical("Failed to connect to test server on http://%s:%s You may need to edit /etc/hosts or similar" % (host, port)) |
+ sys.exit(1) |
+ |
+ for subdomain, (punycode, host) in subdomains.iteritems(): |
+ domain = "%s.%s" % (punycode, host) |
+ try: |
+ urllib2.urlopen("http://%s:%d/" % (domain, port)) |
+ except Exception as e: |
+ logger.critical("Failed probing domain %s. You may need to edit /etc/hosts or similar." % domain) |
+ sys.exit(1) |
+ |
+ wrapper.wait() |
+ |
+ |
+def get_subdomains(host): |
+ #This assumes that the tld is ascii-only or already in punycode |
+ return {subdomain: (subdomain.encode("idna"), host) |
+ for subdomain in subdomains} |
+ |
+ |
+def start_servers(host, ports, paths, routes, bind_hostname, external_config, ssl_config, |
+ **kwargs): |
+ servers = defaultdict(list) |
+ for scheme, ports in ports.iteritems(): |
+ assert len(ports) == {"http":2}.get(scheme, 1) |
+ |
+ for port in ports: |
+ if port is None: |
+ continue |
+ init_func = {"http":start_http_server, |
+ "https":start_https_server, |
+ "ws":start_ws_server, |
+ "wss":start_wss_server}[scheme] |
+ |
+ server_proc = ServerProc() |
+ server_proc.start(init_func, host, port, paths, routes, bind_hostname, |
+ external_config, ssl_config, **kwargs) |
+ servers[scheme].append((port, server_proc)) |
+ |
+ return servers |
+ |
+ |
+def start_http_server(host, port, paths, routes, bind_hostname, external_config, ssl_config, |
+ **kwargs): |
+ return wptserve.WebTestHttpd(host=host, |
+ port=port, |
+ doc_root=paths["doc_root"], |
+ routes=routes, |
+ rewrites=rewrites, |
+ bind_hostname=bind_hostname, |
+ config=external_config, |
+ use_ssl=False, |
+ key_file=None, |
+ certificate=None, |
+ latency=kwargs.get("latency")) |
+ |
+ |
+def start_https_server(host, port, paths, routes, bind_hostname, external_config, ssl_config, |
+ **kwargs): |
+ return wptserve.WebTestHttpd(host=host, |
+ port=port, |
+ doc_root=paths["doc_root"], |
+ routes=routes, |
+ rewrites=rewrites, |
+ bind_hostname=bind_hostname, |
+ config=external_config, |
+ use_ssl=True, |
+ key_file=ssl_config["key_path"], |
+ certificate=ssl_config["cert_path"], |
+ encrypt_after_connect=ssl_config["encrypt_after_connect"], |
+ latency=kwargs.get("latency")) |
+ |
+ |
+class WebSocketDaemon(object): |
+ def __init__(self, host, port, doc_root, handlers_root, log_level, bind_hostname, |
+ ssl_config): |
+ self.host = host |
+ cmd_args = ["-p", port, |
+ "-d", doc_root, |
+ "-w", handlers_root, |
+ "--log-level", log_level] |
+ |
+ if ssl_config is not None: |
+ # This is usually done through pywebsocket.main, however we're |
+ # working around that to get the server instance and manually |
+ # setup the wss server. |
+ if pywebsocket._import_ssl(): |
+ tls_module = pywebsocket._TLS_BY_STANDARD_MODULE |
+ elif pywebsocket._import_pyopenssl(): |
+ tls_module = pywebsocket._TLS_BY_PYOPENSSL |
+ else: |
+ print "No SSL module available" |
+ sys.exit(1) |
+ |
+ cmd_args += ["--tls", |
+ "--private-key", ssl_config["key_path"], |
+ "--certificate", ssl_config["cert_path"], |
+ "--tls-module", tls_module] |
+ |
+ if (bind_hostname): |
+ cmd_args = ["-H", host] + cmd_args |
+ opts, args = pywebsocket._parse_args_and_config(cmd_args) |
+ opts.cgi_directories = [] |
+ opts.is_executable_method = None |
+ self.server = pywebsocket.WebSocketServer(opts) |
+ ports = [item[0].getsockname()[1] for item in self.server._sockets] |
+ assert all(item == ports[0] for item in ports) |
+ self.port = ports[0] |
+ self.started = False |
+ self.server_thread = None |
+ |
+ def start(self, block=False): |
+ self.started = True |
+ if block: |
+ self.server.serve_forever() |
+ else: |
+ self.server_thread = threading.Thread(target=self.server.serve_forever) |
+ self.server_thread.setDaemon(True) # don't hang on exit |
+ self.server_thread.start() |
+ |
+ def stop(self): |
+ """ |
+ Stops the server. |
+ |
+ If the server is not running, this method has no effect. |
+ """ |
+ if self.started: |
+ try: |
+ self.server.shutdown() |
+ self.server.server_close() |
+ self.server_thread.join() |
+ self.server_thread = None |
+ except AttributeError: |
+ pass |
+ self.started = False |
+ self.server = None |
+ |
+ |
+def start_ws_server(host, port, paths, routes, bind_hostname, external_config, ssl_config, |
+ **kwargs): |
+ return WebSocketDaemon(host, |
+ str(port), |
+ repo_root, |
+ paths["ws_doc_root"], |
+ "debug", |
+ bind_hostname, |
+ ssl_config = None) |
+ |
+ |
+def start_wss_server(host, port, paths, routes, bind_hostname, external_config, ssl_config, |
+ **kwargs): |
+ return WebSocketDaemon(host, |
+ str(port), |
+ repo_root, |
+ paths["ws_doc_root"], |
+ "debug", |
+ bind_hostname, |
+ ssl_config) |
+ |
+ |
+def get_ports(config, ssl_environment): |
+ rv = defaultdict(list) |
+ for scheme, ports in config["ports"].iteritems(): |
+ for i, port in enumerate(ports): |
+ if scheme in ["wss", "https"] and not ssl_environment.ssl_enabled: |
+ port = None |
+ if port == "auto": |
+ port = get_port() |
+ else: |
+ port = port |
+ rv[scheme].append(port) |
+ return rv |
+ |
+ |
+ |
+def normalise_config(config, ports): |
+ host = config["external_host"] if config["external_host"] else config["host"] |
+ domains = get_subdomains(host) |
+ ports_ = {} |
+ for scheme, ports_used in ports.iteritems(): |
+ ports_[scheme] = ports_used |
+ |
+ for key, value in domains.iteritems(): |
+ domains[key] = ".".join(value) |
+ |
+ domains[""] = host |
+ |
+ ports_ = {} |
+ for scheme, ports_used in ports.iteritems(): |
+ ports_[scheme] = ports_used |
+ |
+ return {"host": host, |
+ "domains": domains, |
+ "ports": ports_} |
+ |
+ |
+def get_ssl_config(config, external_domains, ssl_environment): |
+ key_path, cert_path = ssl_environment.host_cert_path(external_domains) |
+ return {"key_path": key_path, |
+ "cert_path": cert_path, |
+ "encrypt_after_connect": config["ssl"]["encrypt_after_connect"]} |
+ |
+ |
+def start(config, ssl_environment, routes, **kwargs): |
+ host = config["host"] |
+ domains = get_subdomains(host) |
+ ports = get_ports(config, ssl_environment) |
+ bind_hostname = config["bind_hostname"] |
+ |
+ paths = {"doc_root": config["doc_root"], |
+ "ws_doc_root": config["ws_doc_root"]} |
+ |
+ external_config = normalise_config(config, ports) |
+ |
+ ssl_config = get_ssl_config(config, external_config["domains"].values(), ssl_environment) |
+ |
+ if config["check_subdomains"]: |
+ check_subdomains(host, paths, bind_hostname, ssl_config) |
+ |
+ servers = start_servers(host, ports, paths, routes, bind_hostname, external_config, |
+ ssl_config, **kwargs) |
+ |
+ return external_config, servers |
+ |
+ |
+def iter_procs(servers): |
+ for servers in servers.values(): |
+ for port, server in servers: |
+ yield server.proc |
+ |
+ |
+def value_set(config, key): |
+ return key in config and config[key] is not None |
+ |
+ |
+def get_value_or_default(config, key, default=None): |
+ return config[key] if value_set(config, key) else default |
+ |
+ |
+def set_computed_defaults(config): |
+ if not value_set(config, "doc_root"): |
+ config["doc_root"] = repo_root |
+ |
+ if not value_set(config, "ws_doc_root"): |
+ root = get_value_or_default(config, "doc_root", default=repo_root) |
+ config["ws_doc_root"] = os.path.join(root, "websockets", "handlers") |
+ |
+ |
+def merge_json(base_obj, override_obj): |
+ rv = {} |
+ for key, value in base_obj.iteritems(): |
+ if key not in override_obj: |
+ rv[key] = value |
+ else: |
+ if isinstance(value, dict): |
+ rv[key] = merge_json(value, override_obj[key]) |
+ else: |
+ rv[key] = override_obj[key] |
+ return rv |
+ |
+ |
+def get_ssl_environment(config): |
+ implementation_type = config["ssl"]["type"] |
+ cls = sslutils.environments[implementation_type] |
+ try: |
+ kwargs = config["ssl"][implementation_type].copy() |
+ except KeyError: |
+ raise ValueError("%s is not a vaid ssl type." % implementation_type) |
+ return cls(logger, **kwargs) |
+ |
+ |
+def load_config(default_path, override_path=None, **kwargs): |
+ if os.path.exists(default_path): |
+ with open(default_path) as f: |
+ base_obj = json.load(f) |
+ else: |
+ raise ValueError("Config path %s does not exist" % default_path) |
+ |
+ if os.path.exists(override_path): |
+ with open(override_path) as f: |
+ override_obj = json.load(f) |
+ else: |
+ override_obj = {} |
+ rv = merge_json(base_obj, override_obj) |
+ |
+ if kwargs.get("config_path"): |
+ other_path = os.path.abspath(os.path.expanduser(kwargs.get("config_path"))) |
+ if os.path.exists(other_path): |
+ base_obj = rv |
+ with open(other_path) as f: |
+ override_obj = json.load(f) |
+ rv = merge_json(base_obj, override_obj) |
+ else: |
+ raise ValueError("Config path %s does not exist" % other_path) |
+ |
+ overriding_path_args = [("doc_root", "Document root"), |
+ ("ws_doc_root", "WebSockets document root")] |
+ for key, title in overriding_path_args: |
+ value = kwargs.get(key) |
+ if value is None: |
+ continue |
+ value = os.path.abspath(os.path.expanduser(value)) |
+ if not os.path.exists(value): |
+ raise ValueError("%s path %s does not exist" % (title, value)) |
+ rv[key] = value |
+ |
+ set_computed_defaults(rv) |
+ return rv |
+ |
+ |
+def get_parser(): |
+ parser = argparse.ArgumentParser() |
+ parser.add_argument("--latency", type=int, |
+ help="Artificial latency to add before sending http responses, in ms") |
+ parser.add_argument("--config", action="store", dest="config_path", |
+ help="Path to external config file") |
+ parser.add_argument("--doc_root", action="store", dest="doc_root", |
+ help="Path to document root. Overrides config.") |
+ parser.add_argument("--ws_doc_root", action="store", dest="ws_doc_root", |
+ help="Path to WebSockets document root. Overrides config.") |
+ return parser |
+ |
+ |
+def main(): |
+ kwargs = vars(get_parser().parse_args()) |
+ config = load_config("config.default.json", |
+ "config.json", |
+ **kwargs) |
+ |
+ setup_logger(config["log_level"]) |
+ |
+ with get_ssl_environment(config) as ssl_env: |
+ config_, servers = start(config, ssl_env, default_routes(), **kwargs) |
+ |
+ try: |
+ while any(item.is_alive() for item in iter_procs(servers)): |
+ for item in iter_procs(servers): |
+ item.join(1) |
+ except KeyboardInterrupt: |
+ logger.info("Shutting down") |