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