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 |