Skip to main content

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:

  1. Passes all HTTP through to your application unchanged
  2. Injects a tiny JavaScript shim into HTML responses that wraps window.WebSocket
  3. The shim tries real WebSocket first — if it works, there's zero overhead
  4. If WebSocket fails (code 1006 or connection stall), it falls back to SSE + POST
  5. The /__wss/events endpoint opens a real WebSocket to your app on localhost and streams messages back as Server-Sent Events
  6. The /__wss/send POST 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:

  1. Attempts a real WebSocket connection
  2. Sets a 3-second timeout for stalled connections
  3. If the WebSocket opens then immediately closes with code 1006 (abnormal closure — the signature of a proxy dropping the connection), falls back to SSE
  4. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ws_sse_proxy-0.1.0.tar.gz (9.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ws_sse_proxy-0.1.0-py3-none-any.whl (10.7 kB view details)

Uploaded Python 3

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

Hashes for ws_sse_proxy-0.1.0.tar.gz
Algorithm Hash digest
SHA256 7b4ca5caa1d36633c70ad3fe46b72dfce8dcc8f7cf1d17e1c0cabc2823195c8a
MD5 45e1e44d5797344aaff326bad181ef41
BLAKE2b-256 25df5f09defe9b2141a52374fa05e71f8e81643f01361ad5e82867773bc9939c

See more details on using hashes here.

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

Hashes for ws_sse_proxy-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 68fb22e95f679a0ce68fe9afd144192e7cb38b7e4d65601989913930b77ebfcc
MD5 844936d456101aa72c6072f4c8461e9b
BLAKE2b-256 40ef257ebcdcba2c0d182beaba7c3303cb63fc75c61a2a0aeb1d365d86deace5

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page