summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ara/clients/offline.py130
-rw-r--r--setup.cfg2
2 files changed, 80 insertions, 52 deletions
diff --git a/ara/clients/offline.py b/ara/clients/offline.py
index dd44695..3dc5cd8 100644
--- a/ara/clients/offline.py
+++ b/ara/clients/offline.py
@@ -18,19 +18,23 @@
18# This is an "offline" API client that does not require standing up 18# This is an "offline" API client that does not require standing up
19# an API server and does not execute actual HTTP calls. 19# an API server and does not execute actual HTTP calls.
20 20
21import json
22import logging 21import logging
23import os 22import os
23import threading
24 24
25from django.core.handlers.wsgi import WSGIHandler
26from django.core.servers.basehttp import ServerHandler as BaseServerHandler, ThreadedWSGIServer, WSGIRequestHandler
25 27
26class AraOfflineClient(object): 28from .http import AraHttpClient
29
30
31class AraOfflineClient(AraHttpClient):
27 def __init__(self): 32 def __init__(self):
28 self.log = logging.getLogger(__name__) 33 self.log = logging.getLogger(__name__)
29 34
30 try: 35 try:
31 from django import setup as django_setup 36 from django import setup as django_setup
32 from django.core.management import execute_from_command_line 37 from django.core.management import execute_from_command_line
33 from django.test import Client
34 38
35 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ara.server.settings") 39 os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ara.server.settings")
36 40
@@ -39,52 +43,76 @@ class AraOfflineClient(object):
39 43
40 # Set up the things Django needs 44 # Set up the things Django needs
41 django_setup() 45 django_setup()
42 except ImportError as e:
43 self.log.error("The offline client requires ara-server to be installed")
44 raise e
45
46 self.client = Client()
47
48 def _request(self, method, endpoint, **kwargs):
49 func = getattr(self.client, method)
50 # TODO: Is there a better way than doing this if/else ?
51 if kwargs:
52 response = func(endpoint, json.dumps(kwargs), content_type="application/json")
53 else:
54 response = func(endpoint, content_type="application/json")
55
56 if response.status_code >= 500:
57 self.log.error(
58 "Failed to {method} on {endpoint}: {content}".format(method=method, endpoint=endpoint, content=kwargs)
59 )
60
61 self.log.debug(
62 "HTTP {status}: {method} on {endpoint}".format(
63 status=response.status_code, method=method, endpoint=endpoint
64 )
65 )
66
67 if response.status_code not in [200, 201, 204]:
68 self.log.error(
69 "Failed to {method} on {endpoint}: {content}".format(method=method, endpoint=endpoint, content=kwargs)
70 )
71 46
72 if response.status_code == 204: 47 self._start_server()
73 return response 48 super().__init__(endpoint="http://localhost:%d" % self.server_thread.port)
74 49 except ImportError:
75 return response.json() 50 self.log.error("The offline client requires ara-server to be installed")
76 51 raise
77 def get(self, endpoint, **kwargs): 52
78 return self._request("get", endpoint, **kwargs) 53 def _start_server(self):
79 54 self.server_thread = ServerThread("localhost")
80 def patch(self, endpoint, **kwargs): 55 self.server_thread.start()
81 return self._request("patch", endpoint, **kwargs) 56
82 57 # Wait for the live server to be ready
83 def post(self, endpoint, **kwargs): 58 self.server_thread.is_ready.wait()
84 return self._request("post", endpoint, **kwargs) 59 if self.server_thread.error:
85 60 raise self.server_thread.error
86 def put(self, endpoint, **kwargs): 61
87 return self._request("put", endpoint, **kwargs) 62
88 63class ServerHandler(BaseServerHandler):
89 def delete(self, endpoint, **kwargs): 64 def cleanup_headers(self):
90 return self._request("delete", endpoint, **kwargs) 65 super().cleanup_headers()
66 self.headers["Connection"] = "close"
67
68
69class QuietWSGIRequestHandler(WSGIRequestHandler):
70 def log_message(*args):
71 pass
72
73 def handle(self):
74 """Copy of WSGIRequestHandler.handle() but with different ServerHandler"""
75 self.raw_requestline = self.rfile.readline(65537)
76 if len(self.raw_requestline) > 65536:
77 self.requestline = ""
78 self.request_version = ""
79 self.command = ""
80 self.send_error(414)
81 return
82
83 if not self.parse_request(): # An error code has been sent, just exit
84 return
85
86 handler = ServerHandler(self.rfile, self.wfile, self.get_stderr(), self.get_environ())
87 handler.request_handler = self # backpointer for logging
88 handler.run(self.server.get_app())
89
90
91class ServerThread(threading.Thread):
92 def __init__(self, host, port=0):
93 self.host = host
94 self.port = port
95 self.is_ready = threading.Event()
96 self.error = None
97 super().__init__(daemon=True)
98
99 def run(self):
100 """
101 Set up the live server and databases, and then loop over handling
102 HTTP requests.
103 """
104 try:
105 # Create the handler for serving static and media files
106 self.httpd = self._create_server()
107 # If binding to port zero, assign the port allocated by the OS.
108 if self.port == 0:
109 self.port = self.httpd.server_address[1]
110 self.httpd.set_app(WSGIHandler())
111 self.is_ready.set()
112 self.httpd.serve_forever()
113 except Exception as e:
114 self.error = e
115 self.is_ready.set()
116
117 def _create_server(self):
118 return ThreadedWSGIServer((self.host, self.port), QuietWSGIRequestHandler, allow_reuse_address=False)
diff --git a/setup.cfg b/setup.cfg
index 2a8d1ea..2cf594a 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -74,4 +74,4 @@ multi_line_output=3
74include_trailing_comma=True 74include_trailing_comma=True
75force_grid_wrap=0 75force_grid_wrap=0
76combine_as_imports=True 76combine_as_imports=True
77line_length=88 77line_length=120