Files
spothole/server/webserver.py
2025-12-23 11:51:00 +00:00

94 lines
3.8 KiB
Python

import asyncio
import os
import tornado
from tornado.web import StaticFileHandler
from server.handlers.api.addspot import APISpotHandler
from server.handlers.api.alerts import APIAlertsHandler, APIAlertsStreamHandler
from server.handlers.api.lookups import APILookupCallHandler, APILookupSIGRefHandler
from server.handlers.api.options import APIOptionsHandler
from server.handlers.api.spots import APISpotsHandler, APISpotsStreamHandler
from server.handlers.api.status import APIStatusHandler
from server.handlers.metrics import PrometheusMetricsHandler
from server.handlers.pagetemplate import PageTemplateHandler
# Provides the public-facing web server.
# TODO alerts API
# TODO lookup APIs
# TODO post spot API
# TODO SSE API responses
# TODO clean_up_sse_queues
# TODO page & API access counters - how to do from a subclass handler? e.g.
# self.last_api_access_time = datetime.now(pytz.UTC)
# self.api_access_counter += 1
# api_requests_counter.inc()
# self.status = "OK"
#
# self.last_page_access_time = datetime.now(pytz.UTC)
# self.page_access_counter += 1
# page_requests_counter.inc()
# self.status = "OK"
class WebServer:
# Constructor
def __init__(self, spots, alerts, status_data, port):
self.last_page_access_time = None
self.last_api_access_time = None
self.page_access_counter = 0
self.api_access_counter = 0
self.spots = spots
self.alerts = alerts
self.sse_spot_queues = []
self.sse_alert_queues = []
self.status_data = status_data
self.port = port
self.status = "Starting"
self.shutdown_event = asyncio.Event()
# Start the web server
def start(self):
asyncio.run(self.start_inner())
# Stop the web server
def stop(self):
self.shutdown_event.set()
# Start method (async). Sets up the Tornado application.
async def start_inner(self):
app = tornado.web.Application([
# Routes for API calls
(r"/api/v1/spots", APISpotsHandler, {"spots": self.spots}),
(r"/api/v1/alerts", APIAlertsHandler),
(r"/api/v1/spots/stream", APISpotsStreamHandler),
(r"/api/v1/alerts/stream", APIAlertsStreamHandler),
(r"/api/v1/options", APIOptionsHandler, {"status_data": self.status_data}),
(r"/api/v1/status", APIStatusHandler, {"status_data": self.status_data}),
(r"/api/v1/lookup/call", APILookupCallHandler),
(r"/api/v1/lookup/sigref", APILookupSIGRefHandler),
(r"/api/v1/spot", APISpotHandler),
# Routes for templated pages
(r"/", PageTemplateHandler, {"template_name": "spots"}),
(r"/map", PageTemplateHandler, {"template_name": "map"}),
(r"/bands", PageTemplateHandler, {"template_name": "bands"}),
(r"/alerts", PageTemplateHandler, {"template_name": "alerts"}),
(r"/add-spot", PageTemplateHandler, {"template_name": "add_spot"}),
(r"/status", PageTemplateHandler, {"template_name": "status"}),
(r"/about", PageTemplateHandler, {"template_name": "about"}),
(r"/apidocs", PageTemplateHandler, {"template_name": "apidocs"}),
# Route for Prometheus metrics
(r"/metrics", PrometheusMetricsHandler),
# Default route to serve from "webassets"
(r"/(.*)", StaticFileHandler, {"path": os.path.join(os.path.dirname(__file__), "../webassets")}),
],
template_path=os.path.join(os.path.dirname(__file__), "../templates"),
debug=True) # todo set false
app.listen(self.port)
await self.shutdown_event.wait()
# Clean up any SSE queues that are growing too large; probably their client disconnected.
def clean_up_sse_queues(self):
# todo
pass