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

Side by Side Diff: Tools/Scripts/webkitpy/thirdparty/wpt/wpt/tools/wptserve/wptserve/server.py

Issue 1154373005: Introduce WPTServe for running W3C Blink Layout tests (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: Add executable bit to pass permchecks. Created 5 years, 6 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
(Empty)
1 import BaseHTTPServer
2 import errno
3 import os
4 import re
5 import socket
6 from SocketServer import ThreadingMixIn
7 import ssl
8 import sys
9 import threading
10 import time
11 import traceback
12 import types
13 import urlparse
14
15 import routes as default_routes
16 from logger import get_logger
17 from request import Server, Request
18 from response import Response
19 from router import Router
20 from utils import HTTPException
21
22
23 """HTTP server designed for testing purposes.
24
25 The server is designed to provide flexibility in the way that
26 requests are handled, and to provide control both of exactly
27 what bytes are put on the wire for the response, and in the
28 timing of sending those bytes.
29
30 The server is based on the stdlib HTTPServer, but with some
31 notable differences in the way that requests are processed.
32 Overall processing is handled by a WebTestRequestHandler,
33 which is a subclass of BaseHTTPRequestHandler. This is responsible
34 for parsing the incoming request. A RequestRewriter is then
35 applied and may change the request data if it matches a
36 supplied rule.
37
38 Once the request data had been finalised, Request and Reponse
39 objects are constructed. These are used by the other parts of the
40 system to read information about the request and manipulate the
41 response.
42
43 Each request is handled by a particular handler function. The
44 mapping between Request and the appropriate handler is determined
45 by a Router. By default handlers are installed to interpret files
46 under the document root with .py extensions as executable python
47 files (see handlers.py for the api for such files), .asis files as
48 bytestreams to be sent literally and all other files to be served
49 statically.
50
51 The handler functions are responsible for either populating the
52 fields of the response object, which will then be written when the
53 handler returns, or for directly writing to the output stream.
54 """
55
56
57 class RequestRewriter(object):
58 def __init__(self, rules):
59 """Object for rewriting the request path.
60
61 :param rules: Initial rules to add; a list of three item tuples
62 (method, input_path, output_path), defined as for
63 register()
64 """
65 self.rules = {}
66 for rule in reversed(rules):
67 self.register(*rule)
68 self.logger = get_logger()
69
70 def register(self, methods, input_path, output_path):
71 """Register a rewrite rule.
72
73 :param methods: Set of methods this should match. "*" is a
74 special value indicating that all methods should
75 be matched.
76
77 :param input_path: Path to match for the initial request.
78
79 :param output_path: Path to replace the input path with in
80 the request.
81 """
82 if type(methods) in types.StringTypes:
83 methods = [methods]
84 self.rules[input_path] = (methods, output_path)
85
86 def rewrite(self, request_handler):
87 """Rewrite the path in a BaseHTTPRequestHandler instance, if
88 it matches a rule.
89
90 :param request_handler: BaseHTTPRequestHandler for which to
91 rewrite the request.
92 """
93 split_url = urlparse.urlsplit(request_handler.path)
94 if split_url.path in self.rules:
95 methods, destination = self.rules[split_url.path]
96 if "*" in methods or request_handler.command in methods:
97 self.logger.debug("Rewriting request path %s to %s" %
98 (request_handler.path, destination))
99 new_url = list(split_url)
100 new_url[2] = destination
101 new_url = urlparse.urlunsplit(new_url)
102 request_handler.path = new_url
103
104
105 class WebTestServer(ThreadingMixIn, BaseHTTPServer.HTTPServer):
106 allow_reuse_address = True
107 acceptable_errors = (errno.EPIPE, errno.ECONNABORTED)
108 request_queue_size = 2000
109
110 # Ensure that we don't hang on shutdown waiting for requests
111 daemon_threads = True
112
113 def __init__(self, server_address, RequestHandlerClass, router, rewriter, bi nd_hostname,
114 config=None, use_ssl=False, key_file=None, certificate=None,
115 encrypt_after_connect=False, latency=None, **kwargs):
116 """Server for HTTP(s) Requests
117
118 :param server_address: tuple of (server_name, port)
119
120 :param RequestHandlerClass: BaseHTTPRequestHandler-like class to use for
121 handling requests.
122
123 :param router: Router instance to use for matching requests to handler
124 functions
125
126 :param rewriter: RequestRewriter-like instance to use for preprocessing
127 requests before they are routed
128
129 :param config: Dictionary holding environment configuration settings for
130 handlers to read, or None to use the default values.
131
132 :param use_ssl: Boolean indicating whether the server should use SSL
133
134 :param key_file: Path to key file to use if SSL is enabled.
135
136 :param certificate: Path to certificate to use if SSL is enabled.
137
138 :param encrypt_after_connect: For each connection, don't start encryptio n
139 until a CONNECT message has been received.
140 This enables the server to act as a
141 self-proxy.
142
143 :param bind_hostname True to bind the server to both the hostname and
144 port specified in the server_address parameter.
145 False to bind the server only to the port in the
146 server_address parameter, but not to the hostname.
147 :param latency: Delay in ms to wait before seving each response, or
148 callable that returns a delay in ms
149 """
150 self.router = router
151 self.rewriter = rewriter
152
153 self.scheme = "https" if use_ssl else "http"
154 self.logger = get_logger()
155
156 self.latency = latency
157
158 if bind_hostname:
159 hostname_port = server_address
160 else:
161 hostname_port = ("",server_address[1])
162
163 #super doesn't work here because BaseHTTPServer.HTTPServer is old-style
164 BaseHTTPServer.HTTPServer.__init__(self, hostname_port, RequestHandlerCl ass, **kwargs)
165
166 if config is not None:
167 Server.config = config
168 else:
169 self.logger.debug("Using default configuration")
170 Server.config = {"host": server_address[0],
171 "domains": {"": server_address[0]},
172 "ports": {"http": [self.server_address[1]]}}
173
174
175 self.key_file = key_file
176 self.certificate = certificate
177 self.encrypt_after_connect = use_ssl and encrypt_after_connect
178
179 if use_ssl and not encrypt_after_connect:
180 self.socket = ssl.wrap_socket(self.socket,
181 keyfile=self.key_file,
182 certfile=self.certificate,
183 server_side=True)
184
185 def handle_error(self, request, client_address):
186 error = sys.exc_value
187
188 if ((isinstance(error, socket.error) and
189 isinstance(error.args, tuple) and
190 error.args[0] in self.acceptable_errors)
191 or
192 (isinstance(error, IOError) and
193 error.errno in self.acceptable_errors)):
194 pass # remote hang up before the result is sent
195 else:
196 self.logger.error(traceback.format_exc())
197
198
199 class WebTestRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
200 """RequestHandler for WebTestHttpd"""
201
202 protocol_version = "HTTP/1.1"
203
204 def handle_one_request(self):
205 response = None
206 self.logger = get_logger()
207 try:
208 self.close_connection = False
209 request_line_is_valid = self.get_request_line()
210
211 if self.close_connection:
212 return
213
214 request_is_valid = self.parse_request()
215 if not request_is_valid:
216 #parse_request() actually sends its own error responses
217 return
218
219 self.server.rewriter.rewrite(self)
220
221 request = Request(self)
222 response = Response(self, request)
223
224 if request.method == "CONNECT":
225 self.handle_connect(response)
226 return
227
228 if not request_line_is_valid:
229 response.set_error(414)
230 response.write()
231 return
232
233 self.logger.debug("%s %s" % (request.method, request.request_path))
234 handler = self.server.router.get_handler(request)
235
236 if self.server.latency is not None:
237 if callable(self.server.latency):
238 latency = self.server.latency()
239 else:
240 latency = self.server.latency
241 self.logger.warning("Latency enabled. Sleeping %i ms" % latency)
242 time.sleep(latency / 1000.)
243
244 if handler is None:
245 response.set_error(404)
246 else:
247 try:
248 handler(request, response)
249 except HTTPException as e:
250 response.set_error(e.code, e.message)
251 except Exception as e:
252 if e.message:
253 err = [e.message]
254 else:
255 err = []
256 err.append(traceback.format_exc())
257 response.set_error(500, "\n".join(err))
258 self.logger.debug("%i %s %s (%s) %i" % (response.status[0],
259 request.method,
260 request.request_path,
261 request.headers.get('Referer '),
262 request.raw_input.length))
263
264 if not response.writer.content_written:
265 response.write()
266
267 # If we want to remove this in the future, a solution is needed for
268 # scripts that produce a non-string iterable of content, since these
269 # can't set a Content-Length header. A notable example of this kind of
270 # problem is with the trickle pipe i.e. foo.js?pipe=trickle(d1)
271 if response.close_connection:
272 self.close_connection = True
273
274 if not self.close_connection:
275 # Ensure that the whole request has been read from the socket
276 request.raw_input.read()
277
278 except socket.timeout, e:
279 self.log_error("Request timed out: %r", e)
280 self.close_connection = True
281 return
282
283 except Exception as e:
284 err = traceback.format_exc()
285 if response:
286 response.set_error(500, err)
287 response.write()
288 self.logger.error(err)
289
290 def get_request_line(self):
291 try:
292 self.raw_requestline = self.rfile.readline(65537)
293 except socket.error:
294 self.close_connection = True
295 return False
296 if len(self.raw_requestline) > 65536:
297 self.requestline = ''
298 self.request_version = ''
299 self.command = ''
300 return False
301 if not self.raw_requestline:
302 self.close_connection = True
303 return True
304
305 def handle_connect(self, response):
306 self.logger.debug("Got CONNECT")
307 response.status = 200
308 response.write()
309 if self.server.encrypt_after_connect:
310 self.logger.debug("Enabling SSL for connection")
311 self.request = ssl.wrap_socket(self.connection,
312 keyfile=self.server.key_file,
313 certfile=self.server.certificate,
314 server_side=True)
315 self.setup()
316 return
317
318
319 class WebTestHttpd(object):
320 """
321 :param host: Host from which to serve (default: 127.0.0.1)
322 :param port: Port from which to serve (default: 8000)
323 :param server_cls: Class to use for the server (default depends on ssl vs no n-ssl)
324 :param handler_cls: Class to use for the RequestHandler
325 :param use_ssl: Use a SSL server if no explicit server_cls is supplied
326 :param key_file: Path to key file to use if ssl is enabled
327 :param certificate: Path to certificate file to use if ssl is enabled
328 :param encrypt_after_connect: For each connection, don't start encryption
329 until a CONNECT message has been received.
330 This enables the server to act as a
331 self-proxy.
332 :param router_cls: Router class to use when matching URLs to handlers
333 :param doc_root: Document root for serving files
334 :param routes: List of routes with which to initialize the router
335 :param rewriter_cls: Class to use for request rewriter
336 :param rewrites: List of rewrites with which to initialize the rewriter_cls
337 :param config: Dictionary holding environment configuration settings for
338 handlers to read, or None to use the default values.
339 :param bind_hostname: Boolean indicating whether to bind server to hostname.
340 :param latency: Delay in ms to wait before seving each response, or
341 callable that returns a delay in ms
342
343 HTTP server designed for testing scenarios.
344
345 Takes a router class which provides one method get_handler which takes a Req uest
346 and returns a handler function.
347
348 .. attribute:: host
349
350 The host name or ip address of the server
351
352 .. attribute:: port
353
354 The port on which the server is running
355
356 .. attribute:: router
357
358 The Router object used to associate requests with resources for this serve r
359
360 .. attribute:: rewriter
361
362 The Rewriter object used for URL rewriting
363
364 .. attribute:: use_ssl
365
366 Boolean indicating whether the server is using ssl
367
368 .. attribute:: started
369
370 Boolean indictaing whether the server is running
371
372 """
373 def __init__(self, host="127.0.0.1", port=8000,
374 server_cls=None, handler_cls=WebTestRequestHandler,
375 use_ssl=False, key_file=None, certificate=None, encrypt_after_c onnect=False,
376 router_cls=Router, doc_root=os.curdir, routes=None,
377 rewriter_cls=RequestRewriter, bind_hostname=True, rewrites=None ,
378 latency=None, config=None):
379
380 if routes is None:
381 routes = default_routes.routes
382
383 self.host = host
384
385 self.router = router_cls(doc_root, routes)
386 self.rewriter = rewriter_cls(rewrites if rewrites is not None else [])
387
388 self.use_ssl = use_ssl
389 self.logger = get_logger()
390
391 if server_cls is None:
392 server_cls = WebTestServer
393
394 if use_ssl:
395 if key_file is not None:
396 assert os.path.exists(key_file)
397 assert certificate is not None and os.path.exists(certificate)
398
399 try:
400 self.httpd = server_cls((host, port),
401 handler_cls,
402 self.router,
403 self.rewriter,
404 config=config,
405 bind_hostname=bind_hostname,
406 use_ssl=use_ssl,
407 key_file=key_file,
408 certificate=certificate,
409 encrypt_after_connect=encrypt_after_connect,
410 latency=latency)
411 self.started = False
412
413 _host, self.port = self.httpd.socket.getsockname()
414 except Exception:
415 self.logger.error('Init failed! You may need to modify your hosts fi le. Refer to README.md.');
416 raise
417
418 def start(self, block=False):
419 """Start the server.
420
421 :param block: True to run the server on the current thread, blocking,
422 False to run on a separate thread."""
423 self.logger.info("Starting http server on %s:%s" % (self.host, self.port ))
424 self.started = True
425 if block:
426 self.httpd.serve_forever()
427 else:
428 self.server_thread = threading.Thread(target=self.httpd.serve_foreve r)
429 self.server_thread.setDaemon(True) # don't hang on exit
430 self.server_thread.start()
431
432 def stop(self):
433 """
434 Stops the server.
435
436 If the server is not running, this method has no effect.
437 """
438 if self.started:
439 try:
440 self.httpd.shutdown()
441 self.httpd.server_close()
442 self.server_thread.join()
443 self.server_thread = None
444 self.logger.info("Stopped http server on %s:%s" % (self.host, se lf.port))
445 except AttributeError:
446 pass
447 self.started = False
448 self.httpd = None
449
450 def get_url(self, path="/", query=None, fragment=None):
451 if not self.started:
452 return None
453
454 return urlparse.urlunsplit(("http" if not self.use_ssl else "https",
455 "%s:%s" % (self.host, self.port),
456 path, query, fragment))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698