#!/usr/bin/env python # Copyright (c) 2012 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. import json import os import socket import sys import traceback from tvcm import project as project_module import SocketServer import SimpleHTTPServer import BaseHTTPServer TEST_DATA_PREFIX = '/test_data' class DevServerHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): def __init__(self, *args, **kwargs): SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self, *args, **kwargs) def send_response(self, code, message=None): SimpleHTTPServer.SimpleHTTPRequestHandler.send_response(self, code, message) if code == 200: self.send_header('Cache-Control', 'no-cache') def do_GET(self): if self.do_path_handler('GET'): return return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) def do_POST(self): if self.do_path_handler('POST'): return return SimpleHTTPServer.SimpleHTTPRequestHandler.do_POST(self) def do_path_handler(self, method): handler = self.server.GetPathHandler(self.path, method) if handler: try: handler(self) except Exception, ex: send_500(self, 'While parsing %s' % self.path, ex, path=self.path) return True return False def send_head(self): return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self) def translate_path(self, path): path = path.split('?', 1)[0] path = path.split('#', 1)[0] if path.startswith(TEST_DATA_PREFIX): path = path[len(TEST_DATA_PREFIX):] for mapped_path in self.server.project.source_paths: rel = os.path.relpath(path, '/') candidate = os.path.join(mapped_path, rel) if os.path.exists(candidate): return candidate return '' def log_error(self, log_format, *args): if self.server._quiet: return if self.path == '/favicon.ico': return self.log_message('While processing %s: ', self.path) SimpleHTTPServer.SimpleHTTPRequestHandler.log_error(self, log_format, *args) def log_request(self, code='-', size='-'): # Don't spam the console unless it is important. pass def finish(self): try: SimpleHTTPServer.SimpleHTTPRequestHandler.finish(self) except socket.error: # An final socket error may have occurred here, such as # the local error ECONNABORTED. pass def send_500(self, msg, ex, log_error=True, path=None): if path is None: is_html_output = False else: path = path.split('?', 1)[0] path = path.split('#', 1)[0] is_html_output = path.endswith('.html') if is_html_output: msg = """
""" % (json.dumps(traceback.format_exc()), json.dumps(ex.message))
ctype = 'text/html'
else:
msg = json.dumps({'details': traceback.format_exc(),
'message': ex.message})
ctype = 'application/json'
if log_error:
self.log_error('%s: %s', msg, ex.message)
self.send_response(500)
self.send_header('Content-Type', ctype)
self.send_header('Cache-Control', 'no-cache')
self.send_header('Content-Length', len(msg))
self.end_headers()
self.wfile.write(msg)
return
class PathHandler(object):
def __init__(self, path, handler, supports_get, supports_post):
self.path = path
self.handler = handler
self.supports_get = supports_get
self.supports_post = supports_post
def CanHandle(self, path, method):
if path != self.path:
return False
if method == 'GET' and self.supports_get:
return True
if method == 'POST' and self.supports_post:
return True
return False
def do_GET_root(request):
request.send_response(301)
request.send_header('Location', request.server.default_path)
request.end_headers()
class DevServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
def __init__(self, port, quiet=False, project=None):
BaseHTTPServer.HTTPServer.__init__(
self, ('localhost', port), DevServerHandler)
self._shutdown_request = None
self._quiet = quiet
if port == 0:
port = self.server_address[1]
self._port = port
self._path_handlers = []
if project:
self._project = project
else:
self._project = project_module.Project([])
self.AddPathHandler('/', do_GET_root)
self.AddPathHandler('', do_GET_root)
self.default_path = '/base/tests.html'
# Redirect old tests.html places to the new location until folks have
# gotten used to its new location.
self.AddPathHandler('/tvcm/tests.html', do_GET_root)
self.AddPathHandler('/tests.html', do_GET_root)
def AddPathHandler(self, path, handler,
supports_get=True, supports_post=False):
self._path_handlers.append(
PathHandler(path, handler, supports_get, supports_post))
def GetPathHandler(self, path, method):
for h in self._path_handlers:
if h.CanHandle(path, method):
return h.handler
return None
def AddSourcePathMapping(self, file_system_path):
self._project.AddSourcePath(file_system_path)
def RequestShutdown(self, exit_code):
self._shutdown_request = exit_code
@property
def project(self):
return self._project
@property
def loader(self):
return self._project.loader
@property
def port(self):
return self._port
@property
def data_dir(self):
return self._data_dir
def serve_forever(self): # pylint: disable=arguments-differ
if not self._quiet:
sys.stderr.write('Now running on http://localhost:%i\n' % self._port)
try:
self.timeout = 0.5
while True:
BaseHTTPServer.HTTPServer.handle_request(self)
if self._shutdown_request is not None:
sys.exit(self._shutdown_request)
except KeyboardInterrupt:
sys.exit(0)