| 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 |
| 11 import logging | 11 import logging |
| 12 import math | 12 import math |
| 13 import os.path | 13 import os.path |
| 14 import shutil | 14 import shutil |
| 15 import socket | 15 import socket |
| 16 import threading | 16 import threading |
| 17 import tempfile |
| 17 | 18 |
| 18 import SimpleHTTPServer | 19 import SimpleHTTPServer |
| 19 import SocketServer | 20 import SocketServer |
| 20 | 21 |
| 21 _ZERO = datetime.timedelta(0) | 22 _ZERO = datetime.timedelta(0) |
| 22 | 23 |
| 23 | 24 |
| 24 class UTC_TZINFO(datetime.tzinfo): | 25 class UTC_TZINFO(datetime.tzinfo): |
| 25 """UTC time zone representation.""" | 26 """UTC time zone representation.""" |
| 26 | 27 |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 59 The first matching prefix and the first location that contains the | 60 The first matching prefix and the first location that contains the |
| 60 requested file will be used each time. | 61 requested file will be used each time. |
| 61 """ | 62 """ |
| 62 for prefix, _ in mappings: | 63 for prefix, _ in mappings: |
| 63 assert not prefix.startswith('/'), ('Prefixes for the http server mappings ' | 64 assert not prefix.startswith('/'), ('Prefixes for the http server mappings ' |
| 64 'should skip the leading slash.') | 65 'should skip the leading slash.') |
| 65 | 66 |
| 66 class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): | 67 class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): |
| 67 """Handler for SocketServer.TCPServer that will serve the files from | 68 """Handler for SocketServer.TCPServer that will serve the files from |
| 68 local directiories over http. | 69 local directiories over http. |
| 70 |
| 71 A new instance is created for each request. |
| 69 """ | 72 """ |
| 70 | 73 |
| 71 def __init__(self, *args, **kwargs): | 74 def __init__(self, *args, **kwargs): |
| 72 self.etag = None | 75 self.etag = None |
| 76 self.gzipped_file = None |
| 73 SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self, *args, **kwargs) | 77 SimpleHTTPServer.SimpleHTTPRequestHandler.__init__(self, *args, **kwargs) |
| 74 | 78 |
| 75 def get_etag(self): | 79 def get_etag(self): |
| 76 if self.etag: | 80 if self.etag: |
| 77 return self.etag | 81 return self.etag |
| 78 | 82 |
| 79 path = self.translate_path(self.path, False) | 83 path = self.translate_path(self.path, False) |
| 80 if not os.path.isfile(path): | 84 if not os.path.isfile(path): |
| 81 return None | 85 return None |
| 82 | 86 |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 140 normalized_path = os.path.relpath( | 144 normalized_path = os.path.relpath( |
| 141 SimpleHTTPServer.SimpleHTTPRequestHandler.translate_path(self, path)) | 145 SimpleHTTPServer.SimpleHTTPRequestHandler.translate_path(self, path)) |
| 142 | 146 |
| 143 for prefix, local_base_path_list in mappings: | 147 for prefix, local_base_path_list in mappings: |
| 144 if normalized_path.startswith(prefix): | 148 if normalized_path.startswith(prefix): |
| 145 for local_base_path in local_base_path_list: | 149 for local_base_path in local_base_path_list: |
| 146 candidate = os.path.join(local_base_path, | 150 candidate = os.path.join(local_base_path, |
| 147 normalized_path[len(prefix):]) | 151 normalized_path[len(prefix):]) |
| 148 if os.path.isfile(candidate): | 152 if os.path.isfile(candidate): |
| 149 if gzipped: | 153 if gzipped: |
| 150 gz_result = candidate + '.gz' | 154 if not self.gzipped_file: |
| 151 if (not os.path.isfile(gz_result) or | 155 self.gzipped_file = tempfile.NamedTemporaryFile(delete=False) |
| 152 os.path.getmtime(gz_result) <= os.path.getmtime(candidate)): | 156 with open(candidate, 'rb') as source: |
| 153 with open(candidate, 'rb') as f: | 157 with gzip.GzipFile(fileobj=self.gzipped_file) as target: |
| 154 with gzip.open(gz_result, 'wb') as zf: | 158 shutil.copyfileobj(source, target) |
| 155 shutil.copyfileobj(f, zf) | 159 self.gzipped_file.close() |
| 156 return gz_result | 160 return self.gzipped_file.name |
| 157 return candidate | 161 return candidate |
| 158 else: | 162 else: |
| 159 self.send_response(404) | 163 self.send_response(404) |
| 160 return None | 164 return None |
| 161 self.send_response(404) | 165 self.send_response(404) |
| 162 return None | 166 return None |
| 163 | 167 |
| 164 def guess_type(self, path): | 168 def guess_type(self, path): |
| 165 # This is needed so that exploded Sky apps without shebang can still run | 169 # This is needed so that exploded Sky apps without shebang can still run |
| 166 # thanks to content-type mappings. | 170 # thanks to content-type mappings. |
| 167 # TODO(ppi): drop this part once we can rely on the Sky files declaring | 171 # TODO(ppi): drop this part once we can rely on the Sky files declaring |
| 168 # correct shebang. | 172 # correct shebang. |
| 169 if path.endswith('.dart') or path.endswith('.dart.gz'): | 173 if path.endswith('.dart') or path.endswith('.dart.gz'): |
| 170 return 'application/dart' | 174 return 'application/dart' |
| 171 return SimpleHTTPServer.SimpleHTTPRequestHandler.guess_type(self, path) | 175 return SimpleHTTPServer.SimpleHTTPRequestHandler.guess_type(self, path) |
| 172 | 176 |
| 173 def log_message(self, *_): | 177 def log_message(self, *_): |
| 174 """Override the base class method to disable logging.""" | 178 """Override the base class method to disable logging.""" |
| 175 pass | 179 pass |
| 176 | 180 |
| 181 def __del__(self): |
| 182 if self.gzipped_file: |
| 183 os.remove(self.gzipped_file.name) |
| 184 |
| 177 RequestHandler.protocol_version = 'HTTP/1.1' | 185 RequestHandler.protocol_version = 'HTTP/1.1' |
| 178 return RequestHandler | 186 return RequestHandler |
| 179 | 187 |
| 180 | 188 |
| 181 def start_http_server(mappings, host_port=0): | 189 def start_http_server(mappings, host_port=0): |
| 182 """Starts an http server serving files from |local_dir_path| on |host_port|. | 190 """Starts an http server serving files from |local_dir_path| on |host_port|. |
| 183 | 191 |
| 184 Args: | 192 Args: |
| 185 mappings: List of tuples (prefix, local_base_path_list) mapping URLs that | 193 mappings: List of tuples (prefix, local_base_path_list) mapping URLs that |
| 186 start with |prefix| to one or more local directories enumerated in | 194 start with |prefix| to one or more local directories enumerated in |
| (...skipping 19 matching lines...) Expand all Loading... |
| 206 return httpd.server_address | 214 return httpd.server_address |
| 207 except socket.error as v: | 215 except socket.error as v: |
| 208 error_code = v[0] | 216 error_code = v[0] |
| 209 print 'Failed to start http server for %s on port %d: %s.' % ( | 217 print 'Failed to start http server for %s on port %d: %s.' % ( |
| 210 str(mappings), host_port, os.strerror(error_code)) | 218 str(mappings), host_port, os.strerror(error_code)) |
| 211 if error_code == errno.EADDRINUSE: | 219 if error_code == errno.EADDRINUSE: |
| 212 print (' Run `fuser %d/tcp` to find out which process is using the port.' | 220 print (' Run `fuser %d/tcp` to find out which process is using the port.' |
| 213 % host_port) | 221 % host_port) |
| 214 print '---' | 222 print '---' |
| 215 raise | 223 raise |
| OLD | NEW |