A drop-in proxy that translates WebSocket connections to SSE + HTTP POST, for environments where WebSocket is blocked
Project description
ws-sse-proxy
A drop-in reverse proxy that transparently translates WebSocket connections to SSE + HTTP POST, for environments where WebSocket is blocked.
The Problem
Some deployment environments block or drop WebSocket connections: AWS ALBs that strip Connection: Upgrade headers, corporate proxies, reverse proxies with misconfigured WebSocket support, or platforms like AWS SageMaker Studio Lab where the gateway kills WebSocket on certain paths.
Your web application works fine on localhost but shows "connecting..." or blank content when deployed behind one of these proxies — because WebSocket never completes.
How It Works
Browser → Broken Proxy → ws-sse-proxy (port 8081) → WebSocket → Your App (port 8080)
(HTTP only) (localhost, works fine)
The proxy:
- Passes all HTTP through to your application unchanged
- Injects a tiny JavaScript shim into HTML responses that wraps
window.WebSocket - The shim tries real WebSocket first — if it works, there's zero overhead
- If WebSocket fails (code 1006 or connection stall), it falls back to SSE + POST
- The
/__wss/eventsendpoint opens a real WebSocket to your app on localhost and streams messages back as Server-Sent Events - The
/__wss/sendPOST endpoint forwards client messages over the local WebSocket
Your application doesn't need any changes. The proxy handles the translation.
Installation
pip install ws-sse-proxy
Usage
# Your app is running on port 8080
ws-sse-proxy --target-port 8080 --listen-port 8081
Then point your users (or proxy/gateway) at port 8081 instead of 8080.
All Options
ws-sse-proxy --target-port PORT --listen-port PORT [OPTIONS]
Required:
--target-port PORT Port your application is listening on
--listen-port PORT Port for the proxy to listen on
Optional:
--target-host HOST Target host (default: localhost)
--host HOST Bind address (default: 0.0.0.0)
--log-level LEVEL DEBUG, INFO, WARNING, or ERROR (default: INFO)
As a Python module
python -m ws_sse_proxy --target-port 8080 --listen-port 8081
Programmatic
from ws_sse_proxy.proxy import create_proxy
app = create_proxy(target_port=8080)
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8081)
Example: marimo on AWS SageMaker
marimo is a reactive Python notebook that requires WebSocket. On SageMaker Studio Lab, the gateway drops WebSocket on proxy paths. With ws-sse-proxy:
# Start marimo
marimo edit --host 0.0.0.0 --port 2718 --no-token --headless &
# Start the proxy in front of it
ws-sse-proxy --target-port 2718 --listen-port 2719
Access marimo at /proxy/2719/ — the proxy translates WebSocket to SSE automatically.
See aws-marimo-sagemaker for a complete setup.
How Detection Works
The injected JavaScript doesn't blindly replace WebSocket. It:
- Attempts a real WebSocket connection
- Sets a 3-second timeout for stalled connections
- If the WebSocket opens then immediately closes with code 1006 (abnormal closure — the signature of a proxy dropping the connection), falls back to SSE
- If WebSocket connects normally, uses it with zero overhead
This makes the proxy safe to use everywhere. In environments where WebSocket works, the real WebSocket is used.
Technical Details
The proxy uses /__wss/ as its namespace for shim endpoints, chosen to avoid collisions with application routes:
/__wss/events— SSE endpoint (server → client)/__wss/send— POST endpoint (client → server)/__wss/close— POST endpoint (cleanup)
All other paths are proxied to the target application. HTML responses get the JavaScript shim injected before the first <script> tag.
Dependencies: starlette, websockets, httpx, uvicorn — all pure Python, no compiled extensions required.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ws_sse_proxy-0.1.0.tar.gz.
File metadata
- Download URL: ws_sse_proxy-0.1.0.tar.gz
- Upload date:
- Size: 9.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7b4ca5caa1d36633c70ad3fe46b72dfce8dcc8f7cf1d17e1c0cabc2823195c8a
|
|
| MD5 |
45e1e44d5797344aaff326bad181ef41
|
|
| BLAKE2b-256 |
25df5f09defe9b2141a52374fa05e71f8e81643f01361ad5e82867773bc9939c
|
File details
Details for the file ws_sse_proxy-0.1.0-py3-none-any.whl.
File metadata
- Download URL: ws_sse_proxy-0.1.0-py3-none-any.whl
- Upload date:
- Size: 10.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
68fb22e95f679a0ce68fe9afd144192e7cb38b7e4d65601989913930b77ebfcc
|
|
| MD5 |
844936d456101aa72c6072f4c8461e9b
|
|
| BLAKE2b-256 |
40ef257ebcdcba2c0d182beaba7c3303cb63fc75c61a2a0aeb1d365d86deace5
|