Skip to main content

ws-sse-proxy

PyPI version PyPI - Python Version conda-forge License: MIT

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.

Development

# Install with test extras (using uv)
uv pip install -e '.[test]'

# Run the test suite
pytest

The suite covers HTTP pass-through, JavaScript shim injection (including gzip-encoded HTML), and the SSE↔WebSocket bridge end-to-end against a live proxy+target stack — text and binary round-trips, query-parameter forwarding, and connection cleanup.

Changelog

0.1.3

  • Fix (#2): SSE fallback now works when the proxy is served under a sub-path prefix (jupyter-server-proxy, JupyterHub, SageMaker Studio). The injected shim previously assumed a root mount and requested /__wss/* at the wrong path, causing a 404 retry loop. It now derives the mount prefix from the page and WebSocket URLs and forwards the app-relative WebSocket path.

0.1.1

  • Fix: query parameters forwarded to the target WebSocket are now percent-encoded. Values containing spaces, &, or = (e.g. auth tokens) previously corrupted the WebSocket handshake.
  • Add: test suite.

0.1.0

  • Initial release.

License

MIT

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.3.tar.gz (47.9 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.3-py3-none-any.whl (11.8 kB view details)

Uploaded Python 3

File details

Details for the file ws_sse_proxy-0.1.3.tar.gz.

File metadata

  • Download URL: ws_sse_proxy-0.1.3.tar.gz
  • Upload date:
  • Size: 47.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for ws_sse_proxy-0.1.3.tar.gz
Algorithm Hash digest
SHA256 17f208542fb5be14479f97e2b39f6f49b0bc98e7cc49e9abd04931878142df36
MD5 3a84b67d8534d82725cfb0c086d1be24
BLAKE2b-256 45114e9717d115f88f45553f6109c65d3d484f5df97795bc65e6b60d8a506927

See more details on using hashes here.

Provenance

The following attestation bundles were made for ws_sse_proxy-0.1.3.tar.gz:

Publisher: publish.yml on scttfrdmn/ws-sse-proxy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ws_sse_proxy-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: ws_sse_proxy-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 11.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for ws_sse_proxy-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 f296ebcad9ea3d7d5df1b7565aa77ca2cfc5f237ce2f970dd13c366729bde8f2
MD5 b798eb43cd1936010455977869bb4c95
BLAKE2b-256 2f9c1220d62fc1f070014d13863316d59e3cbb505161cbe5087134f8eb0fe539

See more details on using hashes here.

Provenance

The following attestation bundles were made for ws_sse_proxy-0.1.3-py3-none-any.whl:

Publisher: publish.yml on scttfrdmn/ws-sse-proxy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.3 This release

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page