mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-06-24 05:35:10 +00:00
SSE server reliability improvements
This commit is contained in:
43
README.md
43
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.
|
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
|
```nginx
|
||||||
map $request_uri $xssorigin {
|
map $request_uri $cors_origin {
|
||||||
~^/api *;
|
~^/api *;
|
||||||
|
default "";
|
||||||
}
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
@@ -258,12 +259,46 @@ server {
|
|||||||
alias /var/www/html/.well-known/;
|
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 / {
|
location / {
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Connection "";
|
proxy_set_header Connection "";
|
||||||
proxy_pass http://127.0.0.1:8080;
|
proxy_pass http://127.0.0.1:8081;
|
||||||
proxy_hide_header Access-Control-Allow-Origin;
|
proxy_buffering on;
|
||||||
add_header Access-Control-Allow-Origin $xssorigin;
|
proxy_read_timeout 30s;
|
||||||
|
proxy_connect_timeout 10s;
|
||||||
|
add_header Cache-Control "public, max-age=3600, must-revalidate" always;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -98,6 +98,9 @@ class APIAlertsStreamHandler(tornado_eventsource.handler.EventSourceHandler):
|
|||||||
self._heartbeat = tornado.ioloop.PeriodicCallback(self._callback, SSE_HANDLER_QUEUE_CHECK_INTERVAL)
|
self._heartbeat = tornado.ioloop.PeriodicCallback(self._callback, SSE_HANDLER_QUEUE_CHECK_INTERVAL)
|
||||||
self._heartbeat.start()
|
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:
|
except Exception as e:
|
||||||
logging.warning("Exception when serving SSE socket", e)
|
logging.warning("Exception when serving SSE socket", e)
|
||||||
|
|
||||||
@@ -122,6 +125,7 @@ class APIAlertsStreamHandler(tornado_eventsource.handler.EventSourceHandler):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if self._alert_queue:
|
if self._alert_queue:
|
||||||
|
if not self._alert_queue.empty():
|
||||||
while not self._alert_queue.empty():
|
while not self._alert_queue.empty():
|
||||||
alert = self._alert_queue.get()
|
alert = self._alert_queue.get()
|
||||||
# If the new alert matches our param filters, send it to the client. If not, ignore it.
|
# If the new alert matches our param filters, send it to the client. If not, ignore it.
|
||||||
@@ -131,6 +135,10 @@ class APIAlertsStreamHandler(tornado_eventsource.handler.EventSourceHandler):
|
|||||||
alert.infer_missing(self._credentials)
|
alert.infer_missing(self._credentials)
|
||||||
self.write_message(msg=json.dumps(alert, default=serialize_everything))
|
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:
|
if self._alert_queue not in self._sse_alert_queues:
|
||||||
logging.error("Web server cleared up a queue of an active connection!")
|
logging.error("Web server cleared up a queue of an active connection!")
|
||||||
self.close()
|
self.close()
|
||||||
|
|||||||
@@ -100,6 +100,9 @@ class APISpotsStreamHandler(tornado_eventsource.handler.EventSourceHandler):
|
|||||||
self._heartbeat = tornado.ioloop.PeriodicCallback(self._callback, SSE_HANDLER_QUEUE_CHECK_INTERVAL)
|
self._heartbeat = tornado.ioloop.PeriodicCallback(self._callback, SSE_HANDLER_QUEUE_CHECK_INTERVAL)
|
||||||
self._heartbeat.start()
|
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:
|
except Exception as e:
|
||||||
logging.warning("Exception when serving SSE socket", e)
|
logging.warning("Exception when serving SSE socket", e)
|
||||||
|
|
||||||
@@ -124,6 +127,7 @@ class APISpotsStreamHandler(tornado_eventsource.handler.EventSourceHandler):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if self._spot_queue:
|
if self._spot_queue:
|
||||||
|
if not self._spot_queue.empty():
|
||||||
while not self._spot_queue.empty():
|
while not self._spot_queue.empty():
|
||||||
spot = self._spot_queue.get()
|
spot = self._spot_queue.get()
|
||||||
# If the new spot matches our param filters, send it to the client. If not, ignore it.
|
# If the new spot matches our param filters, send it to the client. If not, ignore it.
|
||||||
@@ -133,6 +137,10 @@ class APISpotsStreamHandler(tornado_eventsource.handler.EventSourceHandler):
|
|||||||
spot.infer_missing(self._credentials)
|
spot.infer_missing(self._credentials)
|
||||||
self.write_message(msg=json.dumps(spot, default=serialize_everything))
|
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:
|
if self._spot_queue not in self._sse_spot_queues:
|
||||||
logging.error("Web server cleared up a queue of an active connection!")
|
logging.error("Web server cleared up a queue of an active connection!")
|
||||||
self.close()
|
self.close()
|
||||||
|
|||||||
@@ -69,7 +69,7 @@
|
|||||||
<p>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.</p>
|
<p>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.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/js/common.js?v=1780424170"></script>
|
<script src="/js/common.js?v=1780671994"></script>
|
||||||
<script>$(document).ready(function() { $("#nav-link-about").addClass("active"); }); <!-- highlight active page in nav --></script>
|
<script>$(document).ready(function() { $("#nav-link-about").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||||
|
|
||||||
{% end %}
|
{% end %}
|
||||||
@@ -69,8 +69,8 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/js/common.js?v=1780424170"></script>
|
<script src="/js/common.js?v=1780671994"></script>
|
||||||
<script src="/js/add-spot.js?v=1780424170"></script>
|
<script src="/js/add-spot.js?v=1780671994"></script>
|
||||||
<script>$(document).ready(function() { $("#nav-link-add-spot").addClass("active"); }); <!-- highlight active page in nav --></script>
|
<script>$(document).ready(function() { $("#nav-link-add-spot").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||||
|
|
||||||
{% end %}
|
{% end %}
|
||||||
@@ -70,8 +70,8 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/js/common.js?v=1780424170"></script>
|
<script src="/js/common.js?v=1780671994"></script>
|
||||||
<script src="/js/alerts.js?v=1780424170"></script>
|
<script src="/js/alerts.js?v=1780671994"></script>
|
||||||
<script>$(document).ready(function() { $("#nav-link-alerts").addClass("active"); }); <!-- highlight active page in nav --></script>
|
<script>$(document).ready(function() { $("#nav-link-alerts").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||||
|
|
||||||
{% end %}
|
{% end %}
|
||||||
@@ -76,9 +76,9 @@
|
|||||||
<script>
|
<script>
|
||||||
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
|
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
|
||||||
</script>
|
</script>
|
||||||
<script src="/js/common.js?v=1780424170"></script>
|
<script src="/js/common.js?v=1780671994"></script>
|
||||||
<script src="/js/spotsbandsandmap.js?v=1780424170"></script>
|
<script src="/js/spotsbandsandmap.js?v=1780671994"></script>
|
||||||
<script src="/js/bands.js?v=1780424170"></script>
|
<script src="/js/bands.js?v=1780671994"></script>
|
||||||
<script>$(document).ready(function() { $("#nav-link-bands").addClass("active"); }); <!-- highlight active page in nav --></script>
|
<script>$(document).ready(function() { $("#nav-link-bands").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||||
|
|
||||||
{% end %}
|
{% end %}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{% extends "skeleton.html" %}
|
{% extends "skeleton.html" %}
|
||||||
{% block head_extra %}
|
{% block head_extra %}
|
||||||
<link rel="stylesheet" href="/css/style.css?v=1780424170" type="text/css">
|
<link rel="stylesheet" href="/css/style.css?v=1780671993" type="text/css">
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet"
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||||
integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
|
integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
|
||||||
<link href="/fa/css/fontawesome.min.css" rel="stylesheet" />
|
<link href="/fa/css/fontawesome.min.css" rel="stylesheet" />
|
||||||
@@ -19,9 +19,9 @@
|
|||||||
integrity="sha384-L1eE4eD41kpBIWe2I0eHy+GnEUC4RIpcvibVW2JCminuPlTl+2Bc528iPdVMg5Dn"
|
integrity="sha384-L1eE4eD41kpBIWe2I0eHy+GnEUC4RIpcvibVW2JCminuPlTl+2Bc528iPdVMg5Dn"
|
||||||
crossorigin="anonymous"></script>
|
crossorigin="anonymous"></script>
|
||||||
|
|
||||||
<script src="https://misc.ianrenton.com/jsutils/utils.js?v=1780424170"></script>
|
<script src="https://misc.ianrenton.com/jsutils/utils.js?v=1780671993"></script>
|
||||||
<script src="https://misc.ianrenton.com/jsutils/ui-ham.js?v=1780424170"></script>
|
<script src="https://misc.ianrenton.com/jsutils/ui-ham.js?v=1780671993"></script>
|
||||||
<script src="https://misc.ianrenton.com/jsutils/geo.js?v=1780424170"></script>
|
<script src="https://misc.ianrenton.com/jsutils/geo.js?v=1780671993"></script>
|
||||||
{% end %}
|
{% end %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|||||||
@@ -271,8 +271,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.9/dist/chart.umd.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.9/dist/chart.umd.min.js"></script>
|
||||||
<script src="/js/common.js?v=1780424170"></script>
|
<script src="/js/common.js?v=1780671994"></script>
|
||||||
<script src="/js/conditions.js?v=1780424170"></script>
|
<script src="/js/conditions.js?v=1780671994"></script>
|
||||||
<script>$(document).ready(function () {
|
<script>$(document).ready(function () {
|
||||||
$("#nav-link-conditions").addClass("active");
|
$("#nav-link-conditions").addClass("active");
|
||||||
}); <!-- highlight active page in nav --></script>
|
}); <!-- highlight active page in nav --></script>
|
||||||
|
|||||||
@@ -94,9 +94,9 @@
|
|||||||
<script>
|
<script>
|
||||||
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
|
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
|
||||||
</script>
|
</script>
|
||||||
<script src="/js/common.js?v=1780424170"></script>
|
<script src="/js/common.js?v=1780671994"></script>
|
||||||
<script src="/js/spotsbandsandmap.js?v=1780424170"></script>
|
<script src="/js/spotsbandsandmap.js?v=1780671994"></script>
|
||||||
<script src="/js/map.js?v=1780424170"></script>
|
<script src="/js/map.js?v=1780671994"></script>
|
||||||
<script>$(document).ready(function() { $("#nav-link-map").addClass("active"); }); <!-- highlight active page in nav --></script>
|
<script>$(document).ready(function() { $("#nav-link-map").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||||
|
|
||||||
{% end %}
|
{% end %}
|
||||||
@@ -104,9 +104,9 @@
|
|||||||
<script>
|
<script>
|
||||||
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
|
let spotProvidersEnabledByDefault = {% raw json_encode(web_ui_options["spot-providers-enabled-by-default"]) %};
|
||||||
</script>
|
</script>
|
||||||
<script src="/js/common.js?v=1780424170"></script>
|
<script src="/js/common.js?v=1780671993"></script>
|
||||||
<script src="/js/spotsbandsandmap.js?v=1780424170"></script>
|
<script src="/js/spotsbandsandmap.js?v=1780671993"></script>
|
||||||
<script src="/js/spots.js?v=1780424170"></script>
|
<script src="/js/spots.js?v=1780671993"></script>
|
||||||
<script>$(document).ready(function() { $("#nav-link-spots").addClass("active"); }); <!-- highlight active page in nav --></script>
|
<script>$(document).ready(function() { $("#nav-link-spots").addClass("active"); }); <!-- highlight active page in nav --></script>
|
||||||
|
|
||||||
{% end %}
|
{% end %}
|
||||||
@@ -59,8 +59,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/js/common.js?v=1780424170"></script>
|
<script src="/js/common.js?v=1780671994"></script>
|
||||||
<script src="/js/status.js?v=1780424170"></script>
|
<script src="/js/status.js?v=1780671994"></script>
|
||||||
<script>
|
<script>
|
||||||
$(document).ready(function() { $("#nav-link-status").addClass("active"); }); <!-- highlight active page in nav -->
|
$(document).ready(function() { $("#nav-link-status").addClass("active"); }); <!-- highlight active page in nav -->
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user