diff --git a/README.md b/README.md index 6831291..d081871 100644 --- a/README.md +++ b/README.md @@ -246,8 +246,9 @@ To set up nginx as a reverse proxy that sits in front of Spothole, first ensure Create a file at `/etc/nginx/sites-available/` called `spothole`. Give it the following contents, replacing `spothole.app` with the domain name on which you want to run Spothole. If you changed the port on which Spothole runs, update that on the "proxy_pass" line too. ```nginx -map $request_uri $xssorigin { +map $request_uri $cors_origin { ~^/api *; + default ""; } server { @@ -258,12 +259,46 @@ server { alias /var/www/html/.well-known/; } + # SSE endpoints + location ~ ^/api/v1/(spots|alerts)/stream { + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_pass http://127.0.0.1:8081; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 24h; + proxy_connect_timeout 10s; + proxy_send_timeout 24h; + proxy_set_header X-Accel-Buffering no; + proxy_hide_header Access-Control-Allow-Origin; + add_header Access-Control-Allow-Origin $cors_origin always; + add_header Cache-Control no-store always; + add_header Content-Type text/event-stream always; + } + + # Other API endpoints + location /api/ { + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_pass http://127.0.0.1:8081; + proxy_buffering on; + proxy_cache off; + proxy_read_timeout 30s; + proxy_connect_timeout 10s; + proxy_hide_header Access-Control-Allow-Origin; + add_header Access-Control-Allow-Origin $cors_origin always; + add_header Cache-Control no-store always; + } + + # Static assets location / { proxy_http_version 1.1; proxy_set_header Connection ""; - proxy_pass http://127.0.0.1:8080; - proxy_hide_header Access-Control-Allow-Origin; - add_header Access-Control-Allow-Origin $xssorigin; + proxy_pass http://127.0.0.1:8081; + proxy_buffering on; + proxy_read_timeout 30s; + proxy_connect_timeout 10s; + add_header Cache-Control "public, max-age=3600, must-revalidate" always; } } ``` diff --git a/server/handlers/api/alerts.py b/server/handlers/api/alerts.py index 3ca8a22..45ef63e 100644 --- a/server/handlers/api/alerts.py +++ b/server/handlers/api/alerts.py @@ -98,6 +98,9 @@ class APIAlertsStreamHandler(tornado_eventsource.handler.EventSourceHandler): self._heartbeat = tornado.ioloop.PeriodicCallback(self._callback, SSE_HANDLER_QUEUE_CHECK_INTERVAL) self._heartbeat.start() + # Flush headers immediately so nginx doesn't time out waiting for a response + self.write_message(msg="", event="keepalive") + except Exception as e: logging.warning("Exception when serving SSE socket", e) @@ -122,14 +125,19 @@ class APIAlertsStreamHandler(tornado_eventsource.handler.EventSourceHandler): try: if self._alert_queue: - while not self._alert_queue.empty(): - alert = self._alert_queue.get() - # If the new alert matches our param filters, send it to the client. If not, ignore it. - if alert_allowed_by_query(alert, self._query_params): - if self._credentials: - alert = copy.deepcopy(alert) - alert.infer_missing(self._credentials) - self.write_message(msg=json.dumps(alert, default=serialize_everything)) + if not self._alert_queue.empty(): + while not self._alert_queue.empty(): + alert = self._alert_queue.get() + # If the new alert matches our param filters, send it to the client. If not, ignore it. + if alert_allowed_by_query(alert, self._query_params): + if self._credentials: + alert = copy.deepcopy(alert) + alert.infer_missing(self._credentials) + self.write_message(msg=json.dumps(alert, default=serialize_everything)) + + else: + # Send a keepalive comment if the queue was empty + self.write_message(msg="", event="keepalive") if self._alert_queue not in self._sse_alert_queues: logging.error("Web server cleared up a queue of an active connection!") diff --git a/server/handlers/api/spots.py b/server/handlers/api/spots.py index 9287f50..d407e1d 100644 --- a/server/handlers/api/spots.py +++ b/server/handlers/api/spots.py @@ -100,6 +100,9 @@ class APISpotsStreamHandler(tornado_eventsource.handler.EventSourceHandler): self._heartbeat = tornado.ioloop.PeriodicCallback(self._callback, SSE_HANDLER_QUEUE_CHECK_INTERVAL) self._heartbeat.start() + # Flush headers immediately so nginx doesn't time out waiting for a response + self.write_message(msg="", event="keepalive") + except Exception as e: logging.warning("Exception when serving SSE socket", e) @@ -124,14 +127,19 @@ class APISpotsStreamHandler(tornado_eventsource.handler.EventSourceHandler): try: if self._spot_queue: - while not self._spot_queue.empty(): - spot = self._spot_queue.get() - # If the new spot matches our param filters, send it to the client. If not, ignore it. - if spot_allowed_by_query(spot, self._query_params): - if self._credentials: - spot = copy.deepcopy(spot) - spot.infer_missing(self._credentials) - self.write_message(msg=json.dumps(spot, default=serialize_everything)) + if not self._spot_queue.empty(): + while not self._spot_queue.empty(): + spot = self._spot_queue.get() + # If the new spot matches our param filters, send it to the client. If not, ignore it. + if spot_allowed_by_query(spot, self._query_params): + if self._credentials: + spot = copy.deepcopy(spot) + spot.infer_missing(self._credentials) + self.write_message(msg=json.dumps(spot, default=serialize_everything)) + + else: + # Send a keepalive comment if the queue was empty + self.write_message(msg="", event="keepalive") if self._spot_queue not in self._sse_spot_queues: logging.error("Web server cleared up a queue of an active connection!") diff --git a/templates/about.html b/templates/about.html index 84224ab..0bf67ce 100644 --- a/templates/about.html +++ b/templates/about.html @@ -69,7 +69,7 @@

This software is dedicated to the memory of Tom G1PJB, SK, a friend and colleague who sadly passed away around the time I started writing it in Autumn 2025. I was looking forward to showing it to you when it was done.

- + {% end %} \ No newline at end of file diff --git a/templates/add_spot.html b/templates/add_spot.html index 9b2643c..c150923 100644 --- a/templates/add_spot.html +++ b/templates/add_spot.html @@ -69,8 +69,8 @@ - - + + {% end %} \ No newline at end of file diff --git a/templates/alerts.html b/templates/alerts.html index b21a66d..51acbaf 100644 --- a/templates/alerts.html +++ b/templates/alerts.html @@ -70,8 +70,8 @@ - - + + {% end %} \ No newline at end of file diff --git a/templates/bands.html b/templates/bands.html index 1b0e025..881a438 100644 --- a/templates/bands.html +++ b/templates/bands.html @@ -76,9 +76,9 @@ - - - + + + {% end %} \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 380e677..bb458e0 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,6 +1,6 @@ {% extends "skeleton.html" %} {% block head_extra %} - + @@ -19,9 +19,9 @@ integrity="sha384-L1eE4eD41kpBIWe2I0eHy+GnEUC4RIpcvibVW2JCminuPlTl+2Bc528iPdVMg5Dn" crossorigin="anonymous"> - - - + + + {% end %} {% block body %}
diff --git a/templates/conditions.html b/templates/conditions.html index 013c693..a23b9c0 100644 --- a/templates/conditions.html +++ b/templates/conditions.html @@ -271,8 +271,8 @@
- - + + diff --git a/templates/map.html b/templates/map.html index 2866783..7cd8cdc 100644 --- a/templates/map.html +++ b/templates/map.html @@ -94,9 +94,9 @@ - - - + + + {% end %} \ No newline at end of file diff --git a/templates/spots.html b/templates/spots.html index 2cc31e2..4e30820 100644 --- a/templates/spots.html +++ b/templates/spots.html @@ -104,9 +104,9 @@ - - - + + + {% end %} \ No newline at end of file diff --git a/templates/status.html b/templates/status.html index 610424f..a6f5611 100644 --- a/templates/status.html +++ b/templates/status.html @@ -59,8 +59,8 @@ - - + +