| 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")
 | 
| 
 |