| OLD | NEW |
| 1 # Copyright 2015 The Chromium Authors. All rights reserved. | 1 # Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import atexit | 5 import atexit |
| 6 import datetime | 6 import datetime |
| 7 import email.utils | 7 import email.utils |
| 8 import errno | 8 import errno |
| 9 import gzip | 9 import gzip |
| 10 import hashlib | 10 import hashlib |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 46 def handle_error(self, request, client_address): | 46 def handle_error(self, request, client_address): |
| 47 """Override the base class method to have conditional logging.""" | 47 """Override the base class method to have conditional logging.""" |
| 48 if logging.getLogger().isEnabledFor(logging.DEBUG): | 48 if logging.getLogger().isEnabledFor(logging.DEBUG): |
| 49 SocketServer.TCPServer.handle_error(self, request, client_address) | 49 SocketServer.TCPServer.handle_error(self, request, client_address) |
| 50 | 50 |
| 51 | 51 |
| 52 def _get_handler_class_for_path(mappings): | 52 def _get_handler_class_for_path(mappings): |
| 53 """Creates a handler override for SimpleHTTPServer. | 53 """Creates a handler override for SimpleHTTPServer. |
| 54 | 54 |
| 55 Args: | 55 Args: |
| 56 mappings: List of tuples (prefix, local_base_path), mapping path prefixes | 56 mappings: List of tuples (prefix, local_base_path_list) mapping URLs that |
| 57 without the leading slash to local filesystem directory paths. The first | 57 start with |prefix| to one or more local directories enumerated in |
| 58 matching prefix will be used each time. | 58 |local_base_path_list|. The prefixes should skip the leading slash. |
| 59 The first matching prefix and the first location that contains the |
| 60 requested file will be used each time. |
| 59 """ | 61 """ |
| 60 for prefix, _ in mappings: | 62 for prefix, _ in mappings: |
| 61 assert not prefix.startswith('/'), ('Prefixes for the http server mappings ' | 63 assert not prefix.startswith('/'), ('Prefixes for the http server mappings ' |
| 62 'should skip the leading slash.') | 64 'should skip the leading slash.') |
| 63 | 65 |
| 64 class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): | 66 class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): |
| 65 """Handler for SocketServer.TCPServer that will serve the files from | 67 """Handler for SocketServer.TCPServer that will serve the files from |
| 66 local directiories over http. | 68 local directiories over http. |
| 67 """ | 69 """ |
| 68 | 70 |
| (...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 131 return SimpleHTTPServer.SimpleHTTPRequestHandler.end_headers(self) | 133 return SimpleHTTPServer.SimpleHTTPRequestHandler.end_headers(self) |
| 132 | 134 |
| 133 # pylint: disable=W0221 | 135 # pylint: disable=W0221 |
| 134 def translate_path(self, path, gzipped=True): | 136 def translate_path(self, path, gzipped=True): |
| 135 # Parent translate_path() will strip away the query string and fragment | 137 # Parent translate_path() will strip away the query string and fragment |
| 136 # identifier, but also will prepend the cwd to the path. relpath() gives | 138 # identifier, but also will prepend the cwd to the path. relpath() gives |
| 137 # us the relative path back. | 139 # us the relative path back. |
| 138 normalized_path = os.path.relpath( | 140 normalized_path = os.path.relpath( |
| 139 SimpleHTTPServer.SimpleHTTPRequestHandler.translate_path(self, path)) | 141 SimpleHTTPServer.SimpleHTTPRequestHandler.translate_path(self, path)) |
| 140 | 142 |
| 141 for prefix, local_base_path in mappings: | 143 for prefix, local_base_path_list in mappings: |
| 142 if normalized_path.startswith(prefix): | 144 if normalized_path.startswith(prefix): |
| 143 result = os.path.join(local_base_path, normalized_path[len(prefix):]) | 145 for local_base_path in local_base_path_list: |
| 144 if os.path.isfile(result): | 146 candidate = os.path.join(local_base_path, |
| 145 if gzipped: | 147 normalized_path[len(prefix):]) |
| 146 gz_result = result + '.gz' | 148 if os.path.isfile(candidate): |
| 147 if (not os.path.isfile(gz_result) or | 149 if gzipped: |
| 148 os.path.getmtime(gz_result) <= os.path.getmtime(result)): | 150 gz_result = candidate + '.gz' |
| 149 with open(result, 'rb') as f: | 151 if (not os.path.isfile(gz_result) or |
| 150 with gzip.open(gz_result, 'wb') as zf: | 152 os.path.getmtime(gz_result) <= os.path.getmtime(candidate)): |
| 151 shutil.copyfileobj(f, zf) | 153 with open(candidate, 'rb') as f: |
| 152 result = gz_result | 154 with gzip.open(gz_result, 'wb') as zf: |
| 153 return result | 155 shutil.copyfileobj(f, zf) |
| 154 | 156 return gz_result |
| 155 # This class is only used internally, and we're adding a catch-all '' | 157 return candidate |
| 156 # prefix at the end of |mappings|. | 158 else: |
| 157 assert False | 159 self.send_response(404) |
| 160 return None |
| 161 self.send_response(404) |
| 162 return None |
| 158 | 163 |
| 159 def guess_type(self, path): | 164 def guess_type(self, path): |
| 160 # This is needed so that exploded Sky apps without shebang can still run | 165 # This is needed so that exploded Sky apps without shebang can still run |
| 161 # thanks to content-type mappings. | 166 # thanks to content-type mappings. |
| 162 # TODO(ppi): drop this part once we can rely on the Sky files declaring | 167 # TODO(ppi): drop this part once we can rely on the Sky files declaring |
| 163 # correct shebang. | 168 # correct shebang. |
| 164 if path.endswith('.dart') or path.endswith('.dart.gz'): | 169 if path.endswith('.dart') or path.endswith('.dart.gz'): |
| 165 return 'application/dart' | 170 return 'application/dart' |
| 166 return SimpleHTTPServer.SimpleHTTPRequestHandler.guess_type(self, path) | 171 return SimpleHTTPServer.SimpleHTTPRequestHandler.guess_type(self, path) |
| 167 | 172 |
| 168 def log_message(self, *_): | 173 def log_message(self, *_): |
| 169 """Override the base class method to disable logging.""" | 174 """Override the base class method to disable logging.""" |
| 170 pass | 175 pass |
| 171 | 176 |
| 172 RequestHandler.protocol_version = 'HTTP/1.1' | 177 RequestHandler.protocol_version = 'HTTP/1.1' |
| 173 return RequestHandler | 178 return RequestHandler |
| 174 | 179 |
| 175 | 180 |
| 176 def start_http_server(mappings, host_port=0): | 181 def start_http_server(mappings, host_port=0): |
| 177 """Starts an http server serving files from |local_dir_path| on |host_port|. | 182 """Starts an http server serving files from |local_dir_path| on |host_port|. |
| 178 | 183 |
| 179 Args: | 184 Args: |
| 180 mappings: List of tuples (prefix, local_base_path) mapping | 185 mappings: List of tuples (prefix, local_base_path_list) mapping URLs that |
| 181 URLs that start with |prefix| to local directory at |local_base_path|. | 186 start with |prefix| to one or more local directories enumerated in |
| 182 The prefixes should skip the leading slash. The first matching prefix | 187 |local_base_path_list|. The prefixes should skip the leading slash. |
| 183 will be used each time. | 188 The first matching prefix and the first location that contains the |
| 189 requested file will be used each time. |
| 184 host_port: Port on the host machine to run the server on. Pass 0 to use a | 190 host_port: Port on the host machine to run the server on. Pass 0 to use a |
| 185 system-assigned port. | 191 system-assigned port. |
| 186 | 192 |
| 187 Returns: | 193 Returns: |
| 188 Tuple of the server address and the port on which it runs. | 194 Tuple of the server address and the port on which it runs. |
| 189 """ | 195 """ |
| 190 assert mappings | 196 assert mappings |
| 191 handler_class = _get_handler_class_for_path(mappings) | 197 handler_class = _get_handler_class_for_path(mappings) |
| 192 | 198 |
| 193 try: | 199 try: |
| 194 httpd = _SilentTCPServer(('127.0.0.1', host_port), handler_class) | 200 httpd = _SilentTCPServer(('127.0.0.1', host_port), handler_class) |
| 195 atexit.register(httpd.shutdown) | 201 atexit.register(httpd.shutdown) |
| 196 | 202 |
| 197 http_thread = threading.Thread(target=httpd.serve_forever) | 203 http_thread = threading.Thread(target=httpd.serve_forever) |
| 198 http_thread.daemon = True | 204 http_thread.daemon = True |
| 199 http_thread.start() | 205 http_thread.start() |
| 200 print 'Started http://%s:%d to host %s.' % (httpd.server_address[0], | 206 print 'Started http://%s:%d to host %s.' % (httpd.server_address[0], |
| 201 httpd.server_address[1], | 207 httpd.server_address[1], |
| 202 str(mappings)) | 208 str(mappings)) |
| 203 return httpd.server_address | 209 return httpd.server_address |
| 204 except socket.error as v: | 210 except socket.error as v: |
| 205 error_code = v[0] | 211 error_code = v[0] |
| 206 print 'Failed to start http server for %s on port %d: %s.' % ( | 212 print 'Failed to start http server for %s on port %d: %s.' % ( |
| 207 str(mappings), host_port, os.strerror(error_code)) | 213 str(mappings), host_port, os.strerror(error_code)) |
| 208 if error_code == errno.EADDRINUSE: | 214 if error_code == errno.EADDRINUSE: |
| 209 print (' Run `fuser %d/tcp` to find out which process is using the port.' | 215 print (' Run `fuser %d/tcp` to find out which process is using the port.' |
| 210 % host_port) | 216 % host_port) |
| 211 print '---' | 217 print '---' |
| 212 raise | 218 raise |
| OLD | NEW |