Switched the offline client to use an actual threaded http server.

Sadly it has no support for keep-alive, gotta see if we can use a
different server. Either way, this should be prefered over the usage of
Django's TestClient since it has the exact same request flow like a
normal client would have.

Change-Id: Ic7065ffbe260701728e9d01213fe3a0fd5f0a6d2
This commit is contained in:
Florian Apolloner 2018-11-04 15:21:31 +01:00
parent 001112df9f
commit f9f75778e2
2 changed files with 68 additions and 40 deletions

View File

@ -18,19 +18,23 @@
# This is an "offline" API client that does not require standing up # This is an "offline" API client that does not require standing up
# an API server and does not execute actual HTTP calls. # an API server and does not execute actual HTTP calls.
import json
import logging import logging
import os import os
import threading
from django.core.handlers.wsgi import WSGIHandler
from django.core.servers.basehttp import ServerHandler as BaseServerHandler, ThreadedWSGIServer, WSGIRequestHandler
from .http import AraHttpClient
class AraOfflineClient(object): class AraOfflineClient(AraHttpClient):
def __init__(self): def __init__(self):
self.log = logging.getLogger(__name__) self.log = logging.getLogger(__name__)
try: try:
from django import setup as django_setup from django import setup as django_setup
from django.core.management import execute_from_command_line from django.core.management import execute_from_command_line
from django.test import Client
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ara.server.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ara.server.settings")
@ -39,52 +43,76 @@ class AraOfflineClient(object):
# Set up the things Django needs # Set up the things Django needs
django_setup() django_setup()
except ImportError as e:
self._start_server()
super().__init__(endpoint="http://localhost:%d" % self.server_thread.port)
except ImportError:
self.log.error("The offline client requires ara-server to be installed") self.log.error("The offline client requires ara-server to be installed")
raise e raise
self.client = Client() def _start_server(self):
self.server_thread = ServerThread("localhost")
self.server_thread.start()
def _request(self, method, endpoint, **kwargs): # Wait for the live server to be ready
func = getattr(self.client, method) self.server_thread.is_ready.wait()
# TODO: Is there a better way than doing this if/else ? if self.server_thread.error:
if kwargs: raise self.server_thread.error
response = func(endpoint, json.dumps(kwargs), content_type="application/json")
else:
response = func(endpoint, content_type="application/json")
if response.status_code >= 500:
self.log.error(
"Failed to {method} on {endpoint}: {content}".format(method=method, endpoint=endpoint, content=kwargs)
)
self.log.debug( class ServerHandler(BaseServerHandler):
"HTTP {status}: {method} on {endpoint}".format( def cleanup_headers(self):
status=response.status_code, method=method, endpoint=endpoint super().cleanup_headers()
) self.headers["Connection"] = "close"
)
if response.status_code not in [200, 201, 204]:
self.log.error(
"Failed to {method} on {endpoint}: {content}".format(method=method, endpoint=endpoint, content=kwargs)
)
if response.status_code == 204: class QuietWSGIRequestHandler(WSGIRequestHandler):
return response def log_message(*args):
pass
return response.json() def handle(self):
"""Copy of WSGIRequestHandler.handle() but with different ServerHandler"""
self.raw_requestline = self.rfile.readline(65537)
if len(self.raw_requestline) > 65536:
self.requestline = ""
self.request_version = ""
self.command = ""
self.send_error(414)
return
def get(self, endpoint, **kwargs): if not self.parse_request(): # An error code has been sent, just exit
return self._request("get", endpoint, **kwargs) return
def patch(self, endpoint, **kwargs): handler = ServerHandler(self.rfile, self.wfile, self.get_stderr(), self.get_environ())
return self._request("patch", endpoint, **kwargs) handler.request_handler = self # backpointer for logging
handler.run(self.server.get_app())
def post(self, endpoint, **kwargs):
return self._request("post", endpoint, **kwargs)
def put(self, endpoint, **kwargs): class ServerThread(threading.Thread):
return self._request("put", endpoint, **kwargs) def __init__(self, host, port=0):
self.host = host
self.port = port
self.is_ready = threading.Event()
self.error = None
super().__init__(daemon=True)
def delete(self, endpoint, **kwargs): def run(self):
return self._request("delete", endpoint, **kwargs) """
Set up the live server and databases, and then loop over handling
HTTP requests.
"""
try:
# Create the handler for serving static and media files
self.httpd = self._create_server()
# If binding to port zero, assign the port allocated by the OS.
if self.port == 0:
self.port = self.httpd.server_address[1]
self.httpd.set_app(WSGIHandler())
self.is_ready.set()
self.httpd.serve_forever()
except Exception as e:
self.error = e
self.is_ready.set()
def _create_server(self):
return ThreadedWSGIServer((self.host, self.port), QuietWSGIRequestHandler, allow_reuse_address=False)

View File

@ -74,4 +74,4 @@ multi_line_output=3
include_trailing_comma=True include_trailing_comma=True
force_grid_wrap=0 force_grid_wrap=0
combine_as_imports=True combine_as_imports=True
line_length=88 line_length=120