|  | #! /usr/bin/python | 
|  | # Copyright 2016 The Chromium Authors. All rights reserved. | 
|  | # Use of this source code is governed by a BSD-style license that can be | 
|  | # found in the LICENSE file. | 
|  |  | 
|  | """A simple http server for running local integration tests. | 
|  |  | 
|  | This chooses a port dynamically and so can communicate that back to its spawner | 
|  | via a named pipe at --fifo. Sources are served from the tree named at | 
|  | --source_dir. | 
|  | """ | 
|  |  | 
|  |  | 
|  | import argparse | 
|  | import cgi | 
|  | import json | 
|  | import os.path | 
|  | import logging | 
|  | import re | 
|  | import time | 
|  | import wsgiref.simple_server | 
|  |  | 
|  |  | 
|  | _CONTENT_TYPE_FOR_SUFFIX = { | 
|  | 'css': 'text/css', | 
|  | 'html': 'text/html', | 
|  | 'jpg': 'image/jpeg', | 
|  | 'js': 'text/javascript', | 
|  | 'json': 'application/json', | 
|  | 'png': 'image/png', | 
|  | 'ttf': 'font/ttf',} | 
|  |  | 
|  | # Name of the JSON file containing per file custom response headers located in | 
|  | # the --source_dir. | 
|  | # This file should structured like: | 
|  | #   { | 
|  | #     'mydocument.html': [ | 
|  | #       ['Cache-Control', 'max-age=3600'], | 
|  | #       ['Content-Encoding', 'gzip'], | 
|  | #     ] | 
|  | #   } | 
|  | RESPONSE_HEADERS_PATH = 'RESPONSE_HEADERS.json' | 
|  |  | 
|  |  | 
|  | class ServerApp(object): | 
|  | """WSGI App. | 
|  |  | 
|  | Dispatches by matching, in order, against GetPaths. | 
|  | """ | 
|  | def __init__(self, source_dir): | 
|  | self._source_dir = source_dir | 
|  | self._response_headers = {} | 
|  | response_header_path = os.path.join(source_dir, RESPONSE_HEADERS_PATH) | 
|  | if os.path.exists(response_header_path): | 
|  | with open(response_header_path) as response_headers_file: | 
|  | self._response_headers = json.load(response_headers_file) | 
|  |  | 
|  | def __call__(self, environ, start_response): | 
|  | """WSGI dispatch. | 
|  |  | 
|  | Args: | 
|  | environ: environment list. | 
|  | start_response: WSGI response start. | 
|  |  | 
|  | Returns: | 
|  | Iterable server result. | 
|  | """ | 
|  | path = environ.get('PATH_INFO', '') | 
|  | while path.startswith('/'): | 
|  | path = path[1:] | 
|  | filename = os.path.join(self._source_dir, path) | 
|  | if not os.path.exists(filename): | 
|  | logging.info('%s not found', filename) | 
|  | start_response('404 Not Found', [('Content-Type', 'text/html')]) | 
|  | return ["""<!DOCTYPE html> | 
|  | <html> | 
|  | <head> | 
|  | <title>Not Found</title> | 
|  | <body>%s not found</body> | 
|  | </html>""" % path] | 
|  |  | 
|  | logging.info('responding with %s', filename) | 
|  | suffix = path[path.rfind('.') + 1:] | 
|  | headers = [('Content-Type', _CONTENT_TYPE_FOR_SUFFIX[suffix])] | 
|  | if path in self._response_headers: | 
|  | for header in self._response_headers[path]: | 
|  | headers.append((str(header[0]), str(header[1]))) | 
|  | start_response('200 OK', headers) | 
|  | return [file(filename).read()] | 
|  |  | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | logging.basicConfig(level=logging.INFO) | 
|  | parser = argparse.ArgumentParser() | 
|  | parser.add_argument('--fifo', default=None, | 
|  | help='Named pipe used to communicate port') | 
|  | parser.add_argument('--source_dir', required=True, | 
|  | help='Directory holding sources to serve.') | 
|  | args = parser.parse_args() | 
|  | server_app = ServerApp(args.source_dir) | 
|  | server = wsgiref.simple_server.make_server( | 
|  | 'localhost', 0, server_app) | 
|  | ip, port = server.server_address | 
|  | logging.info('Listening on port %s at %s', port, args.source_dir) | 
|  | if args.fifo: | 
|  | fifo = file(args.fifo, 'w') | 
|  | fifo.write('%s\n' % port) | 
|  | fifo.flush() | 
|  | fifo.close() | 
|  | server.serve_forever() |