blob: 69045271732ec0e0de23eba6bfdf2c4b8410bcbf [file] [edit]
# Copyright 2014 The LUCI Authors. All rights reserved.
# Use of this source code is governed under the Apache License, Version 2.0
# that can be found in the LICENSE file.
import json
import logging
import threading
from six.moves import BaseHTTPServer
from six.moves import http_client
_STOP_EVENT = "/fakeserver/__stop__"
class Handler(BaseHTTPServer.BaseHTTPRequestHandler):
"""Handlers implements utility functions to help implementing a fake."""
### Public methods
def send_json(self, data):
"""Sends a JSON response."""
self.send_response(200)
self.send_header("Content-type", "application/json")
self.end_headers()
self.wfile.write(json.dumps(data).encode())
def send_octet_stream(self, data, headers=None):
"""Sends a binary response."""
self.send_response(200)
self.send_header("Content-type", "application/octet-stream")
for key, value in (headers or {}).items():
self.send_header(key, value)
self.end_headers()
self.wfile.write(data)
def read_body(self):
"""Reads the request body."""
return self.rfile.read(int(self.headers["Content-Length"]))
def yield_body(self):
"""Yields the request body as 4kiB chunks."""
size = int(self.headers["Content-Length"])
while size:
chunk = min(4096, size)
yield self.rfile.read(chunk)
size -= chunk
### Overrides from BaseHTTPRequestHandler
def do_OPTIONS(self):
if self.path == _STOP_EVENT:
self.server.parent._stopped = True
self.send_octet_stream(b"")
def log_message(self, fmt, *args):
logging.info(
"%s - - [%s] %s",
self.address_string(),
self.log_date_time_string(),
fmt % args,
)
class Server:
"""Server implements a simple HTTP server to implement a fake."""
_HANDLER_CLS = None
def __init__(self):
assert issubclass(self._HANDLER_CLS, Handler), self._HANDLER_CLS
self._closed = False
self._stopped = False
self._server = BaseHTTPServer.HTTPServer(
("127.0.0.1", 0), self._HANDLER_CLS
)
self._server.parent = self
self._server.url = self.url = "http://127.0.0.1:%d" % (
self._server.server_port
)
self._thread = threading.Thread(target=self._run, name="httpd")
self._thread.daemon = True
self._thread.start()
logging.info("%s", self.url)
def close(self):
assert not self._closed
self._closed = True
self._send_event(_STOP_EVENT)
self._thread.join()
def _run(self):
while not self._stopped:
self._server.handle_request()
self._server.server_close()
def _send_event(self, path):
conn = http_client.HTTPConnection(
"127.0.0.1:%d" % self._server.server_port, timeout=60
)
try:
conn.request("OPTIONS", path)
conn.getresponse()
finally:
conn.close()