191 lines
5.7 KiB
Python
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
|