fieldpoc/fieldpoc/controller.py

191 lines
5.7 KiB
Python

#!/usr/bin/env python3
import logging
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler, HTTPStatus
import threading
import json
logger = logging.getLogger("fieldpoc.controller")
class Controller:
def __init__(self, fp):
self.fp = fp
def get_handler(self):
"""
Create a handler passed to the HTTP server
Return a subclass of BaseHTTPRequestHandler
"""
# Paths by HTTP request method
PATHS = {
"GET": {},
"POST": {},
}
def index():
"""
Index page
"""
return "FieldPOC HTTP Controller"
PATHS["GET"]["/"] = index
def sync(payload):
"""
Notify all parts of FieldPOC to check configuration of connected components and update it
"""
self.fp.queue_all({"type": "sync"})
return "ok"
PATHS["POST"]["/sync"] = sync
def queues():
"""
Show status of message queues
"""
return {name: queue.qsize() for name, queue in self.fp.queues.items()}
PATHS["GET"]["/queues"] = queues
def reload(payload):
"""
Read extensions specification and apply it
"""
self.fp.reload_extensions()
PATHS["POST"]["/reload"] = reload
def claim(payload):
"""
Bind extension to DECT device
"""
if payload is None:
return {}
try:
self.fp.queue_all({"type": "claim", "extension": payload["extension"], "token": payload["token"]})
return "ok"
except KeyError:
return { "error": "You have to specify calling extension and token" }
PATHS["POST"]["/claim"] = claim
class HandleRequest(BaseHTTPRequestHandler):
def do_GET(self):
if self.path in PATHS["GET"]:
response = PATHS["GET"][self.path]()
return self.make_response(response)
else:
self.send_error(HTTPStatus.NOT_FOUND, "Not found")
return
def do_HEAD(self):
if self.path in PATHS["GET"]:
response = PATHS["GET"][self.path]()
return self.make_response(response, head_only=True)
else:
self.send_error(HTTPStatus.NOT_FOUND, "Not found")
return
def do_POST(self):
if self.path in PATHS["POST"]:
payload = self.prepare_payload()
response = PATHS["POST"][self.path](payload)
return self.make_response(response)
else:
self.send_error(HTTPStatus.NOT_FOUND, "Not found")
return
def make_response(self, content, head_only=False):
"""
Take a dict and return as HTTP json response
"""
# Convert response to json
content = json.dumps(content) + "\n"
# Encode repose as binary
encoded = content.encode("utf-8")
# Send 200 status code
self.send_response(HTTPStatus.OK)
# Set json header
self.send_header("Content-Type", "application/json; charset=utf-8")
# Specify content length
self.send_header("Conten-Length", str(len(encoded)))
# Finish headers
self.end_headers()
# Return early on HEAD request
if head_only:
return
# Send response
self.wfile.write(encoded)
def prepare_payload(self):
"""
Read payload data from HTTP POST request and parse as json.
"""
# Get payload length
length = int(self.headers["Content-Length"])
# Read payload up to length
encoded = self.rfile.read(length)
# Decode payload to string
content = encoded.decode("utf-8")
# Return early if no payload
if not content:
return None
# Parse payload as json
return json.loads(content)
return HandleRequest
def run(self):
logger.info("starting server")
class Server(ThreadingHTTPServer):
"""
Subclass ThreadingHTTPServer to set custom options
"""
# Sometimes sockets are still bound to addresses and ports
# we just ignore that with this
allow_reuse_address = True
# Start the server
with Server(
# Passing address and port from config
(self.fp.config.controller.host, self.fp.config.controller.port),
# With the handler class we generated
self.get_handler()
) as server:
# Starting server loop in another thread
threading.Thread(target=server.serve_forever).start()
# Listen on the message queue
while True:
# Wait for a new message and get it
msg = self.fp.queues["controller"].get()
# Remove message from queue
self.fp.queues["controller"].task_done()
if msg.get("type") == "stop":
logger.info("stopping server")
# Stop http server loop
server.shutdown()
# Exist message queue listening loop
break