Wiring the bridge¶
HandleWS is the single entry point. Mount it on any net/http route that can
give you a language key and the request:
func HandleWS(w http.ResponseWriter, r *http.Request, langKey string,
logger *slog.Logger, acceptOpts *websocket.AcceptOptions)
langKey— the key into the server registry ("latex","go", …). Extract it however your router exposes it; the bridge returns404on an unknown key.logger— a*slog.Logger; the bridge logs spawn/exit and framing errors.acceptOpts— the same*websocket.AcceptOptionsyou use for your other WebSocket endpoints, so the Origin allowlist stays uniform. Passnilfor a permissive dev-mode default. Compression is always forced off (largeinitializeresponses).
A complete handler¶
package main
import (
"log/slog"
"net/http"
"github.com/coder/websocket"
"github.com/go-lsp-bridge/lspbridge"
)
func main() {
logger := slog.Default()
accept := &websocket.AcceptOptions{
OriginPatterns: []string{"editor.example.com"},
}
mux := http.NewServeMux()
mux.HandleFunc("GET /lsp/{lang}", func(w http.ResponseWriter, r *http.Request) {
// Tag the request so the per-user cap keys by identity, not token.
r = r.WithContext(lspbridge.WithSubject(r.Context(), subjectOf(r)))
lspbridge.HandleWS(w, r, r.PathValue("lang"), logger, accept)
})
// Let the client light up LSP only where a server is installed.
mux.HandleFunc("GET /lsp-langs", func(w http.ResponseWriter, r *http.Request) {
writeJSON(w, lspbridge.AvailableLanguages()) // ["go", "latex", ...]
})
http.ListenAndServe(":8080", mux)
}
subjectOf(r) is your own function returning the authenticated identity (user
id, session subject, …). If you don't tag the request, the per-user cap buckets
everything under "anon".
The client side¶
Any CodeMirror LSP client that speaks JSON-RPC over a WebSocket works unchanged,
because the WS payload is the bare JSON object. Point it at
wss://your-host/lsp/go (or whichever language) and it will send initialize,
textDocument/didOpen, textDocument/completion, and so on — the bridge relays
each to the language server and streams the responses (and server-initiated
notifications like publishDiagnostics) back.
Error surfaces¶
The bridge fails visibly rather than silently dropping traffic:
| Condition | Response |
|---|---|
| Global or per-user concurrency cap hit | HTTP 429 Too Many Requests |
Unknown langKey |
HTTP 404 Not Found |
Server binary not on $PATH |
HTTP 503 Service Unavailable |
| stdin/stdout pipe or spawn failure | a JSON-RPC error frame, then WS close |
EncodeError(id, code, msg) is exported if you want to synthesise the same
JSON-RPC error shape yourself before handing a connection to the bridge.