A tiny, fast Rust bridge that lets WebSocket clients talk to plain TCP servers. It was built for roBrowser and the RagnarokRebuildTcp client, but works with any TCP backend.
The target server is encoded directly in the WebSocket URL path:
ws://proxy:5999/127.0.0.1:6900
- Zero client changes for roBrowser: the original WebSocket path convention is preserved.
- Secure by default: configure an allow-list so clients can only reach approved targets.
- Flexible routing: rewrite addresses on the fly with a redirect map.
- TLS ready: terminate WSS with rustls, or run behind a reverse proxy.
- Container & Kubernetes friendly: packaged as a Docker image and shipped with K8s manifests.
# Plain HTTP WebSocket on port 5999
cargo run
# With TLS, allow-list, and a redirect
cargo run -- \
-p 8080 -s \
-k ./default.key -c ./default.crt \
-a "127.0.0.1:6900,127.0.0.1:5121" \
-r "localhost:6900=login:6900"docker run --rm -p 5999:5999 \
ghcr.io/bouroo/rs-wsProxy:latest \
-a "127.0.0.1:6900,127.0.0.1:5121"docker compose up --buildEdit compose.yaml to set the allow-list or other flags via command or environment variables.
kubectl apply -f deploy/k8s/
kubectl port-forward svc/rs-wsProxy 5999:5999| Client | Server URL field |
|---|---|
| roBrowser | ws://proxy:5999/127.0.0.1:6900 |
| RagnarokRebuildTcp RebuildClient | ws://proxy:5999/ws |
For RebuildClient, set the real TCP server on the proxy with --default-target:
cargo run -- -d "127.0.0.1:5000" -a "127.0.0.1:5000"Or use the legacy redirect key ws:
cargo run -- -r "ws=127.0.0.1:5000" -a "127.0.0.1:5000"Every flag has an equivalent WSPROXY_* environment variable.
| Flag | Description | Default | Environment Variable |
|---|---|---|---|
-p, --port |
Port to bind | 5999 |
WSPROXY_PORT |
-t, --threads |
Tokio worker threads | 1 |
WSPROXY_THREADS |
-s, --ssl |
Enable TLS | false |
WSPROXY_SSL |
-k, --key |
SSL private key path | ./default.key |
WSPROXY_KEY |
-c, --cert |
SSL certificate path | ./default.crt |
WSPROXY_CERT |
-a, --allow |
Comma-separated allowed targets (ip:port) |
empty (open proxy) | WSPROXY_ALLOW |
-r, --redirect |
Comma-separated redirects (source=target) |
empty | WSPROXY_REDIRECT |
-d, --default-target |
Default target for RebuildClient /ws route |
empty | WSPROXY_DEFAULT_TARGET |
- The client opens
ws://proxy:port/<target>. - wsProxy validates the
host:portformat. - Any matching redirect is applied.
- The resolved target is checked against the allow-list (empty list = open proxy).
- The connection upgrades to WebSocket and a plain TCP socket is opened to the target.
- Binary frames flow in both directions. Closing the WebSocket half-closes the TCP connection (the server sees EOF) and the proxy drains the server's remaining output so the session unwinds cleanly; a TCP side close — whether a graceful EOF or a read error such as a reset — produces a graceful WebSocket Close frame either way.
Running without an allow-list turns wsProxy into an open TCP relay. A warning is logged at startup. In production, always set -a/WSPROXY_ALLOW to the exact backends you want exposed.
Images are built and published to GitHub Container Registry automatically when a v* tag is pushed.
docker pull ghcr.io/bouroo/rs-wsProxy:latest
docker run -d --name wsproxy -p 5999:5999 \
ghcr.io/bouroo/rs-wsProxy:latest \
-a "10.0.0.10:6900,10.0.0.11:5121"Manifests live in deploy/k8s/. To deploy a released version, edit the image tag in deploy/k8s/deployment.yaml or let the CD workflow do it for you.
Required secret for automatic K8s deployment:
KUBECONFIG— base64-encoded kubeconfig for the target cluster.
The deploy-k8s job is skipped when the secret is absent, so container-only users are unaffected.
# Run tests
cargo test
# Lint
cargo fmt -- --check
cargo clippy --all-targets -- -D warnings
# Build release binary
cargo build --release- TCP connections use
TCP_NODELAYand IPv4 resolution to match the upstream Node.js behavior. - The bidirectional pump splits the TCP stream into independent read/write halves, avoiding lock contention.
- Half-close aware teardown: a WebSocket close shuts down only the TCP write half and drains remaining server data, so closing mid-stream never discards in-flight bytes.
- Tune
-t/--threadsto match available CPU cores for high-throughput workloads. - Use
RUST_LOG=debugfor request-level tracing; default level isinfo.
Client (WebSocket) → wsProxy → Target Server (TCP)
│
┌────┴─────┐
│ Verify │
│ Pipeline │
└──────────┘
│
Redirect? → Allow List? → Accept/Reject
AGPL-3.0 — see LICENSE.