wstransport

package module
v0.0.0-...-e208e98 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 7, 2026 License: BSD-3-Clause Imports: 9 Imported by: 0

README

grpc-transports/websocket

websocket

Go Reference CI

WebSocket transport layer for gRPC — the carrier that works inside the browser. The server exposes a net.Listener for a standard grpc.Server; the client provides a grpc.DialOption that tunnels every gRPC channel over a WebSocket. The same client compiles and runs under GOOS=js/GOARCH=wasm, so a Go program in the browser speaks full gRPC — including client-streaming and bidirectional streaming — with no sidecar proxy.

Two client implementations ship side-by-side behind one signature; the build tag picks which.

native (!js) browser (js && wasm)
Dialer github.com/coder/websocketnet.Conn syscall/js WebSocketnet.Conn
Dependencies one Go module zero third-party (stdlib only)
Transport security wss:// via ClientConfig.TLSConfig wss://, owned by the browser
When services, CLIs, tests wasm front-ends, wasm workers

Module

github.com/grpc-transports/websocket

Why WebSocket — the wasm story

grpc-go runs its HTTP/2 framing in userspace over whatever net.Conn the dialer hands it; it never needs an OS socket or access to HTTP/2 trailers. A browser can do neither — no raw TCP, no readable HTTP/2 trailers — which is exactly why grpc-web is limited to unary and server-streaming and needs an Envoy / grpcwebproxy sidecar in front of your server.

Wrapping a browser WebSocket as a net.Conn sidesteps both limits at once:

  • the unmodified grpc-go client transport runs under js/wasm;
  • you get client-streaming and bidirectional streaming, not just unary;
  • the server is a plain grpc.Server behind this package's net.Listener — no sidecar, no second protocol.

For inter-VM gRPC across hosts, prefer wireguard; for human-driven CLI clients, prefer ssh. Reach for websocket whenever one end lives in a browser (or any host that only offers a WebSocket).

When to use

  • A Go wasm front-end (or web worker) that must call gRPC services directly, with streaming
  • Sharing the same gRPC API between a native client and a browser client instead of maintaining a parallel REST/JSON portal
  • Environments where the only reachable channel is an HTTP(S)/WebSocket endpoint (corporate proxies, edge, PaaS)

API

Server
type ServerConfig struct {
    Path           string      // upgrade path (default "/")
    TLSConfig      *tls.Config // non-nil ⇒ serve wss:// (TLS)
    OriginPatterns []string    // allowed browser Origins ("*" to allow all; empty = same-origin)
    Logger         *log.Logger
}

// ListenWebSocket binds addr, serves the upgrade handler, and returns a
// net.Listener of upgraded WebSocket conns for grpc.Server.Serve. Closing it
// stops the server. Addr() reports the real TCP address (resolves ":0").
func ListenWebSocket(addr string, cfg ServerConfig) (net.Listener, error)

// HandlerListener returns an http.Handler + net.Listener pair, so the gRPC
// endpoint can be mounted on an existing mux — e.g. to serve a wasm client and
// its gRPC endpoint from one origin.
func HandlerListener(cfg ServerConfig) (http.Handler, net.Listener)
Client
type ClientConfig struct {
    Subprotocols []string
    TLSConfig    *tls.Config // native only (browser owns TLS)
    HTTPHeader   http.Header // native only
    Logger       *log.Logger
}

// DialOption returns a grpc.DialOption that tunnels gRPC over a WebSocket to
// wsURL (ws:// or wss://). Identical signature on native and js/wasm. Pair it
// with insecure transport credentials — security is provided by wss.
func DialOption(wsURL string, cfg ClientConfig) (grpc.DialOption, error)

Usage

Server:

lis, err := wstransport.ListenWebSocket("0.0.0.0:8080", wstransport.ServerConfig{
    OriginPatterns: []string{"app.example"},
    TLSConfig:      myTLS, // wss://
})
if err != nil {
    log.Fatal(err)
}
grpcServer.Serve(lis)

Client (native and wasm — the same code):

opt, err := wstransport.DialOption("wss://app.example:8080", wstransport.ClientConfig{})
if err != nil {
    log.Fatal(err)
}
cc, err := grpc.NewClient("passthrough:///svc",
    grpc.WithTransportCredentials(insecure.NewCredentials()), opt)

Build the browser client with:

GOOS=js GOARCH=wasm go build -o app.wasm ./cmd/app

Testing

  • 100% statement coverage of the native code (task test).
  • A Node-driven end-to-end test compiles the real js/wasm client and runs a full bidirectional stream against ListenWebSocket (task wasm-e2e) — it proves the browser path runs, not merely that it compiles.
  • CI exercises six architectures (amd64, arm64 native; riscv64, loong64, ppc64le, s390x under QEMU) plus the js/wasm end-to-end job.

License

BSD-3-Clause. See LICENSE.

Documentation

Overview

Package wstransport tunnels gRPC over a WebSocket, so a standard google.golang.org/grpc client and server speak to each other across a carrier the browser can actually use.

Like the sibling grpc-transports carriers (ssh, wireguard), the shape is "one net.Listener, one client dialer":

Why WebSocket, and why it matters for wasm

grpc-go runs its HTTP/2 framing in userspace over whatever net.Conn the dialer returns — it never needs an OS socket or access to HTTP/2 trailers. A browser cannot open raw TCP and cannot read HTTP/2 trailers, which is why grpc-web is limited to unary and server-streaming and needs an Envoy/ grpcwebproxy sidecar. Wrapping a browser WebSocket as a net.Conn sidesteps both limits: the same grpc-go transport runs unmodified under GOOS=js/GOARCH=wasm and gets full client-streaming and bidirectional streaming, with no sidecar — the server is just a grpc.Server behind this package's net.Listener.

Build targets

DialOption has two implementations selected by build tag. On native targets it dials with github.com/coder/websocket; on js/wasm it dials the browser's WebSocket via syscall/js with zero third-party dependencies. The public signature is identical on both, mirroring wireguard's userspace/kernel backend split.

Transport security

The WebSocket layer carries transport security: use a wss:// URL (set ServerConfig.TLSConfig on the server; the browser or ClientConfig.TLSConfig on the client). grpc-go therefore sees a plain net.Conn and should be dialed with insecure transport credentials:

opt, _ := wstransport.DialOption("wss://svc.example/grpc", wstransport.ClientConfig{})
cc, _ := grpc.NewClient("passthrough:///svc",
    grpc.WithTransportCredentials(insecure.NewCredentials()), opt)

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DialOption

func DialOption(wsURL string, cfg ClientConfig) (grpc.DialOption, error)

DialOption returns a grpc.DialOption that tunnels every gRPC channel over a WebSocket to wsURL (ws:// or wss://). Combine it with insecure transport credentials — transport security is provided by the WebSocket layer (wss).

func HandlerListener

func HandlerListener(cfg ServerConfig) (http.Handler, net.Listener)

HandlerListener returns an http.Handler that upgrades WebSocket requests on cfg.Path into net.Conns, together with the net.Listener those conns are delivered on. Mount the handler on an existing http.ServeMux (e.g. to serve a wasm client and its gRPC endpoint from one origin) and pass the listener to grpc.Server.Serve.

func ListenWebSocket

func ListenWebSocket(addr string, cfg ServerConfig) (net.Listener, error)

ListenWebSocket binds addr, serves the WebSocket upgrade handler on it, and returns a net.Listener that yields one net.Conn per accepted WebSocket — ready for grpc.Server.Serve. When cfg.TLSConfig is set the server speaks wss:// (TLS). Closing the returned listener stops the server.

Types

type ClientConfig

type ClientConfig struct {
	// Subprotocols requested during the WebSocket handshake.
	Subprotocols []string
	// TLSConfig customizes TLS for wss:// dials (native only).
	TLSConfig *tls.Config
	// HTTPHeader is sent with the handshake request (native only).
	HTTPHeader http.Header
	// Logger, when non-nil, receives non-fatal dial diagnostics.
	Logger *log.Logger
}

ClientConfig configures the dialing side of the transport. It is shared by both the native and js/wasm implementations of DialOption; fields noted as native-only are ignored on js/wasm, where the browser owns TLS and headers.

type ServerConfig

type ServerConfig struct {
	// Path is the HTTP path WebSocket upgrades are accepted on. Empty means "/".
	Path string
	// TLSConfig, when non-nil, makes [ListenWebSocket] serve over TLS (wss://).
	TLSConfig *tls.Config
	// OriginPatterns is passed to the WebSocket handshake to authorize browser
	// Origins. Empty means same-origin only; use []string{"*"} to allow all.
	OriginPatterns []string
	// Logger, when non-nil, receives non-fatal handshake diagnostics.
	Logger *log.Logger
}

ServerConfig configures the WebSocket-accepting side of the transport.

Directories

Path Synopsis
Command wasmtest is the browser-side half of the wasm end-to-end test.
Command wasmtest is the browser-side half of the wasm end-to-end test.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL