1#!/usr/bin/env python 2# Copyright (c) 2012 The Chromium Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6import json 7import os 8import socket 9import sys 10import traceback 11 12from tvcm import project as project_module 13 14import SocketServer 15import SimpleHTTPServer 16import BaseHTTPServer 17 18TEST_DATA_PREFIX = '/test_data' 19 20 21class DevServerHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): 22 23 def __init__(self, *args, **kwargs): 24 SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self, *args, **kwargs) 25 26 def send_response(self, code, message=None): 27 SimpleHTTPServer.SimpleHTTPRequestHandler.send_response(self, code, message) 28 if code == 200: 29 self.send_header('Cache-Control', 'no-cache') 30 31 def do_GET(self): 32 if self.do_path_handler('GET'): 33 return 34 35 return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) 36 37 def do_POST(self): 38 if self.do_path_handler('POST'): 39 return 40 return SimpleHTTPServer.SimpleHTTPRequestHandler.do_POST(self) 41 42 def do_path_handler(self, method): 43 handler = self.server.GetPathHandler(self.path, method) 44 if handler: 45 try: 46 handler(self) 47 except Exception, ex: 48 send_500(self, 'While parsing %s' % self.path, ex, path=self.path) 49 return True 50 return False 51 52 def send_head(self): 53 return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self) 54 55 def translate_path(self, path): 56 path = path.split('?', 1)[0] 57 path = path.split('#', 1)[0] 58 59 if path.startswith(TEST_DATA_PREFIX): 60 path = path[len(TEST_DATA_PREFIX):] 61 62 for mapped_path in self.server.project.source_paths: 63 rel = os.path.relpath(path, '/') 64 candidate = os.path.join(mapped_path, rel) 65 if os.path.exists(candidate): 66 return candidate 67 return '' 68 69 def log_error(self, log_format, *args): 70 if self.server._quiet: 71 return 72 if self.path == '/favicon.ico': 73 return 74 self.log_message('While processing %s: ', self.path) 75 SimpleHTTPServer.SimpleHTTPRequestHandler.log_error(self, log_format, *args) 76 77 def log_request(self, code='-', size='-'): 78 # Don't spam the console unless it is important. 79 pass 80 81 def finish(self): 82 try: 83 SimpleHTTPServer.SimpleHTTPRequestHandler.finish(self) 84 except socket.error: 85 # An final socket error may have occurred here, such as 86 # the local error ECONNABORTED. 87 pass 88 89 90def send_500(self, msg, ex, log_error=True, path=None): 91 if path is None: 92 is_html_output = False 93 else: 94 path = path.split('?', 1)[0] 95 path = path.split('#', 1)[0] 96 is_html_output = path.endswith('.html') 97 98 if is_html_output: 99 msg = """<!DOCTYPE html> 100 <html> 101 <body> 102 <h1>OMG something is wrong</h1> 103 <b><pre><code id="message"></code></pre></b></p> 104 <pre><code id="details"></code></pre> 105 <script> 106 document.addEventListener('DOMContentLoaded', function() { 107 document.querySelector('#details').textContent = %s; 108 document.querySelector('#message').textContent = %s; 109 }); 110 </script> 111 </body> 112 </html> 113""" % (json.dumps(traceback.format_exc()), json.dumps(ex.message)) 114 ctype = 'text/html' 115 else: 116 msg = json.dumps({'details': traceback.format_exc(), 117 'message': ex.message}) 118 ctype = 'application/json' 119 120 if log_error: 121 self.log_error('%s: %s', msg, ex.message) 122 self.send_response(500) 123 self.send_header('Content-Type', ctype) 124 self.send_header('Cache-Control', 'no-cache') 125 self.send_header('Content-Length', len(msg)) 126 self.end_headers() 127 self.wfile.write(msg) 128 return 129 130 131class PathHandler(object): 132 def __init__(self, path, handler, supports_get, supports_post): 133 self.path = path 134 self.handler = handler 135 self.supports_get = supports_get 136 self.supports_post = supports_post 137 138 def CanHandle(self, path, method): 139 if path != self.path: 140 return False 141 if method == 'GET' and self.supports_get: 142 return True 143 if method == 'POST' and self.supports_post: 144 return True 145 return False 146 147 148def do_GET_root(request): 149 request.send_response(301) 150 request.send_header('Location', request.server.default_path) 151 request.end_headers() 152 153 154class DevServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): 155 156 def __init__(self, port, quiet=False, project=None): 157 BaseHTTPServer.HTTPServer.__init__( 158 self, ('localhost', port), DevServerHandler) 159 self._shutdown_request = None 160 self._quiet = quiet 161 if port == 0: 162 port = self.server_address[1] 163 self._port = port 164 self._path_handlers = [] 165 if project: 166 self._project = project 167 else: 168 self._project = project_module.Project([]) 169 170 self.AddPathHandler('/', do_GET_root) 171 self.AddPathHandler('', do_GET_root) 172 self.default_path = '/base/tests.html' 173 # Redirect old tests.html places to the new location until folks have 174 # gotten used to its new location. 175 self.AddPathHandler('/tvcm/tests.html', do_GET_root) 176 self.AddPathHandler('/tests.html', do_GET_root) 177 178 def AddPathHandler(self, path, handler, 179 supports_get=True, supports_post=False): 180 self._path_handlers.append( 181 PathHandler(path, handler, supports_get, supports_post)) 182 183 def GetPathHandler(self, path, method): 184 for h in self._path_handlers: 185 if h.CanHandle(path, method): 186 return h.handler 187 return None 188 189 def AddSourcePathMapping(self, file_system_path): 190 self._project.AddSourcePath(file_system_path) 191 192 def RequestShutdown(self, exit_code): 193 self._shutdown_request = exit_code 194 195 @property 196 def project(self): 197 return self._project 198 199 @property 200 def loader(self): 201 return self._project.loader 202 203 @property 204 def port(self): 205 return self._port 206 207 @property 208 def data_dir(self): 209 return self._data_dir 210 211 def serve_forever(self): # pylint: disable=arguments-differ 212 if not self._quiet: 213 sys.stderr.write('Now running on http://localhost:%i\n' % self._port) 214 try: 215 self.timeout = 0.5 216 while True: 217 BaseHTTPServer.HTTPServer.handle_request(self) 218 if self._shutdown_request is not None: 219 sys.exit(self._shutdown_request) 220 except KeyboardInterrupt: 221 sys.exit(0) 222