| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 import BaseHTTPServer | 6 import BaseHTTPServer |
| 7 import imp | |
| 8 import logging | 7 import logging |
| 9 import multiprocessing | 8 import multiprocessing |
| 10 import optparse | 9 import optparse |
| 11 import os | 10 import os |
| 12 import SimpleHTTPServer # pylint: disable=W0611 | 11 import SimpleHTTPServer # pylint: disable=W0611 |
| 13 import socket | 12 import socket |
| 14 import sys | 13 import sys |
| 15 import time | 14 import time |
| 16 import urlparse | 15 import urlparse |
| 17 | 16 |
| (...skipping 17 matching lines...) Expand all Loading... |
| 35 # Verify we don't serve anywhere above NACL_SDK_ROOT. | 34 # Verify we don't serve anywhere above NACL_SDK_ROOT. |
| 36 if abs_serve_dir[:len(NACL_SDK_ROOT)] == NACL_SDK_ROOT: | 35 if abs_serve_dir[:len(NACL_SDK_ROOT)] == NACL_SDK_ROOT: |
| 37 return | 36 return |
| 38 logging.error('For security, httpd.py should only be run from within the') | 37 logging.error('For security, httpd.py should only be run from within the') |
| 39 logging.error('example directory tree.') | 38 logging.error('example directory tree.') |
| 40 logging.error('Attempting to serve from %s.' % abs_serve_dir) | 39 logging.error('Attempting to serve from %s.' % abs_serve_dir) |
| 41 logging.error('Run with --no-dir-check to bypass this check.') | 40 logging.error('Run with --no-dir-check to bypass this check.') |
| 42 sys.exit(1) | 41 sys.exit(1) |
| 43 | 42 |
| 44 | 43 |
| 45 class PluggableHTTPServer(BaseHTTPServer.HTTPServer): | 44 class HTTPServer(BaseHTTPServer.HTTPServer): |
| 46 def __init__(self, *args, **kwargs): | 45 def __init__(self, *args, **kwargs): |
| 47 BaseHTTPServer.HTTPServer.__init__(self, *args) | 46 BaseHTTPServer.HTTPServer.__init__(self, *args) |
| 48 self.serve_dir = kwargs.get('serve_dir', '.') | |
| 49 self.test_mode = kwargs.get('test_mode', False) | |
| 50 self.delegate_map = {} | |
| 51 self.running = True | 47 self.running = True |
| 52 self.result = 0 | 48 self.result = 0 |
| 53 | 49 |
| 54 def Shutdown(self, result=0): | 50 def Shutdown(self, result=0): |
| 55 self.running = False | 51 self.running = False |
| 56 self.result = result | 52 self.result = result |
| 57 | 53 |
| 58 | 54 |
| 59 class PluggableHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): | 55 class HTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): |
| 60 def _FindDelegateAtPath(self, dirname): | |
| 61 # First check the cache... | |
| 62 logging.debug('Looking for cached delegate in %s...' % dirname) | |
| 63 handler_script = os.path.join(dirname, 'handler.py') | |
| 64 | |
| 65 if dirname in self.server.delegate_map: | |
| 66 result = self.server.delegate_map[dirname] | |
| 67 if result is None: | |
| 68 logging.debug('Found None.') | |
| 69 else: | |
| 70 logging.debug('Found delegate.') | |
| 71 return result | |
| 72 | |
| 73 # Don't have one yet, look for one. | |
| 74 delegate = None | |
| 75 logging.debug('Testing file %s for existence...' % handler_script) | |
| 76 if os.path.exists(handler_script): | |
| 77 logging.debug( | |
| 78 'File %s exists, looking for HTTPRequestHandlerDelegate.' % | |
| 79 handler_script) | |
| 80 | |
| 81 module = imp.load_source('handler', handler_script) | |
| 82 delegate_class = getattr(module, 'HTTPRequestHandlerDelegate', None) | |
| 83 delegate = delegate_class() | |
| 84 if not delegate: | |
| 85 logging.warn( | |
| 86 'Unable to find symbol HTTPRequestHandlerDelegate in module %s.' % | |
| 87 handler_script) | |
| 88 | |
| 89 return delegate | |
| 90 | |
| 91 def _FindDelegateForURLRecurse(self, cur_dir, abs_root): | |
| 92 delegate = self._FindDelegateAtPath(cur_dir) | |
| 93 if not delegate: | |
| 94 # Didn't find it, try the parent directory, but stop if this is the server | |
| 95 # root. | |
| 96 if cur_dir != abs_root: | |
| 97 parent_dir = os.path.dirname(cur_dir) | |
| 98 delegate = self._FindDelegateForURLRecurse(parent_dir, abs_root) | |
| 99 | |
| 100 logging.debug('Adding delegate to cache for %s.' % cur_dir) | |
| 101 self.server.delegate_map[cur_dir] = delegate | |
| 102 return delegate | |
| 103 | |
| 104 def _FindDelegateForURL(self, url_path): | |
| 105 path = self.translate_path(url_path) | |
| 106 if os.path.isdir(path): | |
| 107 dirname = path | |
| 108 else: | |
| 109 dirname = os.path.dirname(path) | |
| 110 | |
| 111 abs_serve_dir = os.path.abspath(self.server.serve_dir) | |
| 112 delegate = self._FindDelegateForURLRecurse(dirname, abs_serve_dir) | |
| 113 if not delegate: | |
| 114 logging.info('No handler found for path %s. Using default.' % url_path) | |
| 115 return delegate | |
| 116 | |
| 117 def _SendNothingAndDie(self, result=0): | 56 def _SendNothingAndDie(self, result=0): |
| 118 self.send_response(200, 'OK') | 57 self.send_response(200, 'OK') |
| 119 self.send_header('Content-type', 'text/html') | 58 self.send_header('Content-type', 'text/html') |
| 120 self.send_header('Content-length', '0') | 59 self.send_header('Content-length', '0') |
| 121 self.end_headers() | 60 self.end_headers() |
| 122 self.server.Shutdown(result) | 61 self.server.Shutdown(result) |
| 123 | 62 |
| 124 def send_head(self): | |
| 125 delegate = self._FindDelegateForURL(self.path) | |
| 126 if delegate: | |
| 127 return delegate.send_head(self) | |
| 128 return self.base_send_head() | |
| 129 | |
| 130 def base_send_head(self): | |
| 131 return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self) | |
| 132 | |
| 133 def do_GET(self): | 63 def do_GET(self): |
| 134 # TODO(binji): pyauto tests use the ?quit=1 method to kill the server. | 64 # Browsing to ?quit=1 will kill the server cleanly. |
| 135 # Remove this when we kill the pyauto tests. | |
| 136 _, _, _, query, _ = urlparse.urlsplit(self.path) | 65 _, _, _, query, _ = urlparse.urlsplit(self.path) |
| 137 if query: | 66 if query: |
| 138 params = urlparse.parse_qs(query) | 67 params = urlparse.parse_qs(query) |
| 139 if '1' in params.get('quit', []): | 68 if '1' in params.get('quit', []): |
| 140 self._SendNothingAndDie() | 69 self._SendNothingAndDie() |
| 141 return | 70 return |
| 142 | 71 |
| 143 delegate = self._FindDelegateForURL(self.path) | |
| 144 if delegate: | |
| 145 return delegate.do_GET(self) | |
| 146 return self.base_do_GET() | |
| 147 | |
| 148 def base_do_GET(self): | |
| 149 return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) | 72 return SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self) |
| 150 | 73 |
| 151 def do_POST(self): | |
| 152 delegate = self._FindDelegateForURL(self.path) | |
| 153 if delegate: | |
| 154 return delegate.do_POST(self) | |
| 155 return self.base_do_POST() | |
| 156 | |
| 157 def base_do_POST(self): | |
| 158 if self.server.test_mode: | |
| 159 if self.path == '/ok': | |
| 160 self._SendNothingAndDie(0) | |
| 161 elif self.path == '/fail': | |
| 162 self._SendNothingAndDie(1) | |
| 163 | |
| 164 | 74 |
| 165 class LocalHTTPServer(object): | 75 class LocalHTTPServer(object): |
| 166 """Class to start a local HTTP server as a child process.""" | 76 """Class to start a local HTTP server as a child process.""" |
| 167 | 77 |
| 168 def __init__(self, dirname, port, test_mode): | 78 def __init__(self, dirname, port): |
| 169 parent_conn, child_conn = multiprocessing.Pipe() | 79 parent_conn, child_conn = multiprocessing.Pipe() |
| 170 self.process = multiprocessing.Process( | 80 self.process = multiprocessing.Process( |
| 171 target=_HTTPServerProcess, | 81 target=_HTTPServerProcess, |
| 172 args=(child_conn, dirname, port, { | 82 args=(child_conn, dirname, port, {})) |
| 173 'serve_dir': dirname, | |
| 174 'test_mode': test_mode, | |
| 175 })) | |
| 176 self.process.start() | 83 self.process.start() |
| 177 if parent_conn.poll(10): # wait 10 seconds | 84 if parent_conn.poll(10): # wait 10 seconds |
| 178 self.port = parent_conn.recv() | 85 self.port = parent_conn.recv() |
| 179 else: | 86 else: |
| 180 raise Exception('Unable to launch HTTP server.') | 87 raise Exception('Unable to launch HTTP server.') |
| 181 | 88 |
| 182 self.conn = parent_conn | 89 self.conn = parent_conn |
| 183 | 90 |
| 184 def ServeForever(self): | 91 def ServeForever(self): |
| 185 """Serve until the child HTTP process tells us to stop. | 92 """Serve until the child HTTP process tells us to stop. |
| (...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 248 the local port, and waits for a message from the parent to | 155 the local port, and waits for a message from the parent to |
| 249 stop serving. It also sends a "result" back to the parent -- this can | 156 stop serving. It also sends a "result" back to the parent -- this can |
| 250 be used to allow a client-side test to notify the server of results. | 157 be used to allow a client-side test to notify the server of results. |
| 251 dirname: The directory to serve. All files are accessible through | 158 dirname: The directory to serve. All files are accessible through |
| 252 http://localhost:<port>/path/to/filename. | 159 http://localhost:<port>/path/to/filename. |
| 253 port: The port to serve on. If 0, an ephemeral port will be chosen. | 160 port: The port to serve on. If 0, an ephemeral port will be chosen. |
| 254 server_kwargs: A dict that will be passed as kwargs to the server. | 161 server_kwargs: A dict that will be passed as kwargs to the server. |
| 255 """ | 162 """ |
| 256 try: | 163 try: |
| 257 os.chdir(dirname) | 164 os.chdir(dirname) |
| 258 httpd = PluggableHTTPServer(('', port), PluggableHTTPRequestHandler, | 165 httpd = HTTPServer(('', port), HTTPRequestHandler, **server_kwargs) |
| 259 **server_kwargs) | |
| 260 except socket.error as e: | 166 except socket.error as e: |
| 261 sys.stderr.write('Error creating HTTPServer: %s\n' % e) | 167 sys.stderr.write('Error creating HTTPServer: %s\n' % e) |
| 262 sys.exit(1) | 168 sys.exit(1) |
| 263 | 169 |
| 264 try: | 170 try: |
| 265 conn.send(httpd.server_address[1]) # the chosen port number | 171 conn.send(httpd.server_address[1]) # the chosen port number |
| 266 httpd.timeout = 0.5 # seconds | 172 httpd.timeout = 0.5 # seconds |
| 267 while httpd.running: | 173 while httpd.running: |
| 268 # Flush output for MSVS Add-In. | 174 # Flush output for MSVS Add-In. |
| 269 sys.stdout.flush() | 175 sys.stdout.flush() |
| (...skipping 11 matching lines...) Expand all Loading... |
| 281 def main(args): | 187 def main(args): |
| 282 parser = optparse.OptionParser() | 188 parser = optparse.OptionParser() |
| 283 parser.add_option('-C', '--serve-dir', | 189 parser.add_option('-C', '--serve-dir', |
| 284 help='Serve files out of this directory.', | 190 help='Serve files out of this directory.', |
| 285 default=os.path.abspath('.')) | 191 default=os.path.abspath('.')) |
| 286 parser.add_option('-p', '--port', | 192 parser.add_option('-p', '--port', |
| 287 help='Run server on this port.', default=5103) | 193 help='Run server on this port.', default=5103) |
| 288 parser.add_option('--no-dir-check', '--no_dir_check', | 194 parser.add_option('--no-dir-check', '--no_dir_check', |
| 289 help='No check to ensure serving from safe directory.', | 195 help='No check to ensure serving from safe directory.', |
| 290 dest='do_safe_check', action='store_false', default=True) | 196 dest='do_safe_check', action='store_false', default=True) |
| 291 parser.add_option('--test-mode', | |
| 292 help='Listen for posts to /ok or /fail and shut down the server with ' | |
| 293 ' errorcodes 0 and 1 respectively.', | |
| 294 action='store_true') | |
| 295 | 197 |
| 296 # To enable bash completion for this command first install optcomplete | 198 # To enable bash completion for this command first install optcomplete |
| 297 # and then add this line to your .bashrc: | 199 # and then add this line to your .bashrc: |
| 298 # complete -F _optcomplete httpd.py | 200 # complete -F _optcomplete httpd.py |
| 299 try: | 201 try: |
| 300 import optcomplete | 202 import optcomplete |
| 301 optcomplete.autocomplete(parser) | 203 optcomplete.autocomplete(parser) |
| 302 except ImportError: | 204 except ImportError: |
| 303 pass | 205 pass |
| 304 | 206 |
| 305 options, args = parser.parse_args(args) | 207 options, args = parser.parse_args(args) |
| 306 if options.do_safe_check: | 208 if options.do_safe_check: |
| 307 SanityCheckDirectory(options.serve_dir) | 209 SanityCheckDirectory(options.serve_dir) |
| 308 | 210 |
| 309 server = LocalHTTPServer(options.serve_dir, int(options.port), | 211 server = LocalHTTPServer(options.serve_dir, int(options.port)) |
| 310 options.test_mode) | |
| 311 | 212 |
| 312 # Serve until the client tells us to stop. When it does, it will give us an | 213 # Serve until the client tells us to stop. When it does, it will give us an |
| 313 # errorcode. | 214 # errorcode. |
| 314 print 'Serving %s on %s...' % (options.serve_dir, server.GetURL('')) | 215 print 'Serving %s on %s...' % (options.serve_dir, server.GetURL('')) |
| 315 return server.ServeForever() | 216 return server.ServeForever() |
| 316 | 217 |
| 317 if __name__ == '__main__': | 218 if __name__ == '__main__': |
| 318 sys.exit(main(sys.argv[1:])) | 219 sys.exit(main(sys.argv[1:])) |
| OLD | NEW |