1 # Copyright 2013 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
10 class Responder(object):
11 """Sends a HTTP response. Used with TestWebServer."""
13 def __init__(self, handler):
14 self._handler = handler
16 def SendResponse(self, body):
17 """Sends OK response with body."""
18 self.SendHeaders(len(body))
21 def SendResponseFromFile(self, path):
22 """Sends OK response with the given file as the body."""
23 with open(path, 'r') as f:
24 self.SendResponse(f.read())
26 def SendHeaders(self, content_length=None):
27 """Sends headers for OK response."""
28 self._handler.send_response(200)
30 self._handler.send_header('Content-Length', content_length)
31 self._handler.end_headers()
33 def SendError(self, code):
34 """Sends response for the given HTTP error code."""
35 self._handler.send_error(code)
37 def SendBody(self, body):
38 """Just sends the body, no headers."""
39 self._handler.wfile.write(body)
42 class Request(object):
43 """An HTTP request."""
45 def __init__(self, handler):
46 self._handler = handler
49 return self._handler.path
52 class _BaseServer(BaseHTTPServer.HTTPServer):
53 """Internal server that throws if timed out waiting for a request."""
55 def __init__(self, on_request, server_cert_and_key_path=None):
58 It is an HTTP server if parameter server_cert_and_key_path is not provided.
59 Otherwise, it is an HTTPS server.
62 server_cert_and_key_path: path to a PEM file containing the cert and key.
63 if it is None, start the server as an HTTP one.
65 class _Handler(BaseHTTPServer.BaseHTTPRequestHandler):
66 """Internal handler that just asks the server to handle the request."""
69 if self.path.endswith('favicon.ico'):
72 on_request(Request(self), Responder(self))
74 def log_message(self, *args, **kwargs):
75 """Overriddes base class method to disable logging."""
78 BaseHTTPServer.HTTPServer.__init__(self, ('127.0.0.1', 0), _Handler)
80 if server_cert_and_key_path is not None:
81 self._is_https_enabled = True
82 self._server.socket = ssl.wrap_socket(
83 self._server.socket, certfile=server_cert_and_key_path,
86 self._is_https_enabled = False
88 def handle_timeout(self):
89 """Overridden from SocketServer."""
90 raise RuntimeError('Timed out waiting for http request')
93 """Returns the base URL of the server."""
94 postfix = '://127.0.0.1:%s' % self.server_port
95 if self._is_https_enabled:
96 return 'https' + postfix
97 return 'http' + postfix
100 class WebServer(object):
101 """An HTTP or HTTPS server that serves on its own thread.
103 Serves files from given directory but may use custom data for specific paths.
106 def __init__(self, root_dir, server_cert_and_key_path=None):
107 """Starts the server.
109 It is an HTTP server if parameter server_cert_and_key_path is not provided.
110 Otherwise, it is an HTTPS server.
113 root_dir: root path to serve files from. This parameter is required.
114 server_cert_and_key_path: path to a PEM file containing the cert and key.
115 if it is None, start the server as an HTTP one.
117 self._root_dir = os.path.abspath(root_dir)
118 self._server = _BaseServer(self._OnRequest, server_cert_and_key_path)
119 self._thread = threading.Thread(target=self._server.serve_forever)
120 self._thread.daemon = True
122 self._path_data_map = {}
123 self._path_data_lock = threading.Lock()
125 def _OnRequest(self, request, responder):
126 path = request.GetPath().split('?')[0]
128 # Serve from path -> data map.
129 self._path_data_lock.acquire()
131 if path in self._path_data_map:
132 responder.SendResponse(self._path_data_map[path])
135 self._path_data_lock.release()
138 path = os.path.normpath(
139 os.path.join(self._root_dir, *path.split('/')))
140 if not path.startswith(self._root_dir):
141 responder.SendError(403)
143 if not os.path.exists(path):
144 responder.SendError(404)
146 responder.SendResponseFromFile(path)
148 def SetDataForPath(self, path, data):
149 self._path_data_lock.acquire()
151 self._path_data_map[path] = data
153 self._path_data_lock.release()
156 """Returns the base URL of the server."""
157 return self._server.GetUrl()
160 """Shuts down the server synchronously."""
161 self._server.shutdown()
165 class SyncWebServer(object):
166 """WebServer for testing.
168 Incoming requests are blocked until explicitly handled.
169 This was designed for single thread use. All requests should be handled on
174 self._server = _BaseServer(self._OnRequest)
175 # Recognized by SocketServer.
176 self._server.timeout = 10
177 self._on_request = None
179 def _OnRequest(self, request, responder):
180 self._on_request(responder)
181 self._on_request = None
183 def Respond(self, on_request):
184 """Blocks until request comes in, then calls given handler function.
187 on_request: Function that handles the request. Invoked with single
188 parameter, an instance of Responder.
191 raise RuntimeError('Must handle 1 request at a time.')
193 self._on_request = on_request
194 while self._on_request:
195 # Don't use handle_one_request, because it won't work with the timeout.
196 self._server.handle_request()
198 def RespondWithContent(self, content):
199 """Blocks until request comes in, then handles it with the given content."""
200 def SendContent(responder):
201 responder.SendResponse(content)
202 self.Respond(SendContent)
205 return self._server.GetUrl()