This document covers the protocol-specific server implementations that serve as the entry points for client connections to the relay system. The relay provides two primary HTTP-based server implementations: WSServer for WebSocket/HTTP connections and WTServer for WebTransport/HTTP3 connections. Both servers implement the Relayer interface and share a common request dispatch pattern that routes incoming requests to appropriate handlers based on request characteristics.
For information about the core interfaces these servers implement, see Key Interfaces. For details on how requests are routed after initial dispatch, see Request Routing. For the underlying protocol upgraders, see Protocol Upgraders.
Both WSServer and WTServer follow a composition-based architecture that embeds an Ingress implementation and wraps a protocol-specific upgrader. This design allows them to implement the Relayer interface while delegating most HTTP handling to the embedded Ingress component.
| Aspect | WSServer | WTServer |
|---|---|---|
| File | wsserver.go | wtserver.go |
| Primary Protocol | WebSocket over HTTP/1.1 or HTTP/2 | WebTransport over HTTP/3 |
| Upgrader Type | edge.HTTPUpgrader (websocket.Upgrader) | *webtransport.Upgrader |
| Default Port | 8080 | 8082/8083 |
| Upgrade Header | websocket.UpgradeHeader | webtransport.UpgradeHeader |
| Upgrade Query | websocket.UpgradeQuery | webtransport.UpgradeQuery |
| Additional Config | RootPatterns | H3 Server, EnableDatagrams, TLSConfig |
Diagram: Server Implementation Class Structure
This diagram shows how WSServer and WTServer both implement the Relayer interface through composition, embedding Ingress and wrapping protocol-specific upgraders.
Sources: wsserver.go13-34 wtserver.go15-54
The WSServer provides WebSocket connectivity over standard HTTP/1.1 or HTTP/2 connections. It is the primary entry point for clients that cannot use QUIC-based protocols.
The WSServer is constructed through two factory functions:
DefaultWSServer(host string) - Creates a server using the DefaultIngress wsserver.go15-17NewWSServer(host string, ingress Ingress) - Creates a server with a custom Ingress implementation wsserver.go19-29During construction, a websocket.Upgrader is created with the specified root patterns, and the server subscribes this upgrader to the ingress asynchronously via go ingress.Subscribe(hu) wsserver.go27 This subscription enables the upgrader to receive and handle upgrade requests.
WSServer implements several methods to classify incoming requests:
*IsUpgrade(r http.Request) bool wsserver.go66-71
Determines if a request is a WebSocket upgrade request by checking:
s.IsRootExternal(r)r.Header.Get(websocket.UpgradeHeader) != ""r.URL.Query().Get(websocket.UpgradeQuery) != ""*IsRootExternal(r http.Request) bool wsserver.go58-60
Checks if the request host matches one of the configured root patterns by stripping the port and comparing against the upgrader's root patterns.
*IsRootInternal(r http.Request) bool wsserver.go62-64
Checks if the request targets the special ROOT_INTERNAL host ("root.internal"), which is used for internal APIs like debug endpoints, session listings, and alias management.
Sources: wsserver.go58-71
The WTServer provides WebTransport connectivity over HTTP/3, enabling multiplexed bidirectional streams and datagram support over QUIC.
The WTServer is constructed similarly to WSServer:
DefaultWTServer(host string) - Creates a server using the DefaultIngress wtserver.go17-19NewWTServer(host string, ingress Ingress) - Creates a server with custom configuration wtserver.go21-39The constructor performs additional setup for WebTransport:
webtransport.Upgrader with a wt.Server configured to accept all origins wtserver.go22-27http3.Server with datagram support enabled wtserver.go32-36WTServer provides builder-style configuration methods:
*WithAddr(a string) WTServer wtserver.go41-44
Sets the listening address for the HTTP/3 server by modifying s.Upgrader.Server.H3.Addr.
**WithTLSConfig(tlsConfig tls.Config) WTServer wtserver.go46-49
Configures TLS settings for the HTTP/3 server. This is essential for WebTransport, which requires TLS 1.3 or later.
WTServer implements the same classification interface as WSServer:
*IsUpgrade(r http.Request) bool wtserver.go80-85
Checks for WebTransport upgrade requests by verifying:
*IsRootExternal(r http.Request) bool wtserver.go72-74
Identical logic to WSServer - checks if the stripped host matches root patterns.
*IsRootInternal(r http.Request) bool wtserver.go76-78
Identical logic to WSServer - checks for the ROOT_INTERNAL host.
Sources: wtserver.go21-85
Both servers implement a common dispatch pattern through their Dispatch(r *http.Request) http.Handler method and ServeHTTP(w http.ResponseWriter, r *http.Request) implementation.
Diagram: Request Dispatch Flow
This diagram shows the complete decision tree used by WSServer.Dispatch (the most comprehensive) and WTServer.Dispatch (simplified version) to route requests to appropriate handlers.
Sources: wsserver.go36-52 wtserver.go56-66
The WSServer.Dispatch method wsserver.go36-52 implements the full dispatch decision tree:
switch {
case s.IsUpgrade(r): → s.HTTPUpgrader
case s.IsRootInternal(r): → RootInternalHandler
case IsInternal(r): → handleInternal
case s.IsRootExternal(r): → RootHandler
case proxy.IsProxy(r): → AuthenticatedProxyHandler
default: → s.Ingress
}
The order is significant: upgrade requests take highest priority, followed by internal endpoints, then root external paths, proxy requests, and finally the default ingress handler.
The WTServer.Dispatch method wtserver.go56-66 uses a simplified decision tree:
switch {
case s.IsUpgrade(r): → s.Upgrader
case proxy.IsProxy(r): → AuthenticatedProxyHandler
default: → s.Ingress
}
The WTServer omits the IsRootInternal, IsInternal, and IsRootExternal checks present in WSServer. This is because WebTransport connections typically establish a session first through an upgrade, and subsequent request routing happens within that session context.
Sources: wsserver.go36-52 wtserver.go56-66
Both servers distinguish between external and internal requests using special hostname patterns.
The constant ROOT_INTERNAL = "root.internal" misc.go13 defines a special hostname reserved for internal administrative endpoints.
The RootInternalHandler misc.go38-55 routes requests to internal APIs based on environment variable configuration:
| Environment Variable | Endpoint | Handler |
|---|---|---|
INTERNAL_DEBUG_VARS_PATH | Debug metrics | expvar.Handler() |
INTERNAL_API_SESSIONS_PATH | Session listing | s.RecordsHandler(w, r) |
INTERNAL_ALIASES_PATH | Alias management | s.AliasHandler(w, r) |
If the request path doesn't match any configured internal endpoint, it returns 404 Not Found misc.go54
The IsInternal(r *http.Request) bool function misc.go22-24 checks if a request targets any .internal subdomain by examining if the stripped host has the suffix ".internal". This allows for general internal service routing beyond the specific root.internal administrative endpoints.
The handleInternal function misc.go16-20 provides a simple handler for general .internal requests, returning a plain text response with the request method and host.
Sources: misc.go13-24 misc.go38-55
The RootHandler misc.go58-78 implements a special routing mode where requests to the root host with path prefixes are rewritten to subdomain-style routing.
When a request targets the root host (e.g., example.com/sub/path), the handler:
leadingComponent(r.URL.Path) misc.go59RoundTripper for that component via s.GetRoundTripper(rpath) misc.go60The reverse proxy is configured with a Rewrite function that:
X-Forwarded-* headers via req.SetXForwarded() misc.go68req.Out.URL.Host misc.go70"http" misc.go74The handler strips the path prefix before forwarding: http.StripPrefix("/"+rpath, rp) misc.go76
After serving the request, it increments the WebteleportRelayStreamsClosed metric misc.go77
Sources: misc.go34-78
Both servers implement ServeHTTP identically wsserver.go54-56 wtserver.go68-70:
This delegates to the dispatcher.DispatcherFunc adapter, which calls the Dispatch method to determine the appropriate handler and then invokes that handler's ServeHTTP method. This pattern allows for dynamic handler selection based on request characteristics while maintaining the standard http.Handler interface.
Sources: wsserver.go54-56 wtserver.go68-70
Diagram: Complete Server Integration Architecture
This diagram shows how WSServer and WTServer integrate with the core relay system, routing requests to appropriate handlers and delegating session management to the embedded Ingress and Storage components.
Sources: wsserver.go19-56 wtserver.go21-70 misc.go38-78
Refresh this wiki