A workaround that works better is using socket.io. You store data on the socket before the connection closes; when the app is closed, the connection dies and you can do whatever you want with that data on the backend side. The most important limitation is that this works only with one worker on your web server (see Gunicorn details below). If you need more workers you start getting into sticky session territory.
Everything would be easier if we could just have an "onClose" event somewhere, or intercept "web_app_close" on the app side...
MWE
On the JS side:
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
<script type="text/javascript">
let socket = io.connect(window.location.origin);
socket.on("connect", function() {
alert("Connected to the server");
socket.emit("store_client_data", {
"field": "content"
});
});
</script>
On the server side, example using Flask:
from flask import Flask, request
from flask_socketio import SocketIO
import requests
app = Flask(__name__)
socketio = SocketIO(app)
# ...other routes...
clients_data = {}
@socketio.on("store_client_data")
def handle_store_client_data(data):
# Save client's data using their session ID (or any other unique identifier)
clients_data[request.sid] = data
print(f"Data stored for client {request.sid}")
@socketio.on("connect")
def handle_connect():
print("Client connected", flush=True)
@socketio.on("disconnect")
def handle_disconnect():
print("Client disconnected", flush=True)
client_data = clients_data.get(request.sid, None)
# Send POST request
response = requests.post("http://localhost/onclose", json=client_data)
# Remove data from dictionary to free up memory
del clients_data[request.sid]
if __name__ == "__main__":
socketio.run(app, async_mode="gevent", host="0.0.0.0", port=8000)
Where /onclose
is a route that does the thing you want.
Example web server configuration using Nginx and Let's Encrypt certificates:
server {
listen 80;
location / {
proxy_pass http://0.0.0.0:8000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
server {
listen 443 ssl;
server_name test.mywebsite.org;
ssl_certificate /etc/letsencrypt/live/test.mywebsite.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/test.mywebsite.org/privkey.pem;
location / {
proxy_pass http://0.0.0.0:8000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /socket.io/ {
proxy_pass http://0.0.0.0:8000/socket.io/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Example Gunicorn command to start the web app:
/usr/bin/env gunicorn -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker --workers 1 --bind 0.0.0.0:8000 app.app:app