Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(12)

Side by Side Diff: mojo/devtools/common/devtoolslib/http_server.py

Issue 1269863004: Support mapping multiple local paths under the same url prefix. (Closed) Base URL: git@github.com:domokit/mojo.git@master
Patch Set: Fix typos in tests. Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
OLDNEW
« no previous file with comments | « mojo/devtools/common/devtoolslib/android_shell.py ('k') | mojo/devtools/common/devtoolslib/http_server_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698