WebSocket Listener
On this page
- Source Mapping#
- 1. Upgrade & HTTP Request Pipeline#
- Key Architectural Behaviors#
- 2. WebSocketListenerBase API Reference#
- 3. Minimal HTTP Routing Types (HttpRequestView.cs)#
- HttpMethodType & HttpRouteHandler#
- HttpRequestView (readonly ref struct)#
- HttpResponseWriter (ref struct)#
- Usage Example#
- Related APIs#
Namespace: Nalix.Network.Listeners.Web
Assembly: Nalix.Network
WebSocketListenerBase (public abstract partial class WebSocketListenerBase : TcpListenerBase) provides a zero-allocation, raw-socket RFC 6455 WebSocket and HTTP/1.1 control-plane listener built directly on top of TcpListenerBase (it does not use System.Net.HttpListener and does not require Windows URL ACLs or administrator privileges).
Source Mapping
src/Nalix.Network/Listeners/WebListener/WebSocketListener.Core.cssrc/Nalix.Network/Listeners/WebListener/WebSocketListener.Handle.cssrc/Nalix.Network/Listeners/WebListener/WebSocketListener.Headers.cssrc/Nalix.Network/Listeners/WebListener/WebSocketListener.Http.cssrc/Nalix.Network/Listeners/WebListener/WebSocketListener.HttpResponses.cssrc/Nalix.Network/Listeners/WebListener/HttpRequestView.cssrc/Nalix.Network/Options/NetworkWebSocketOptions.cssrc/Nalix.Network/Options/ForwardedHeadersOptions.cs
1. Upgrade & HTTP Request Pipeline
sequenceDiagram
participant Client
participant Socket as TcpListenerBase (Raw Socket)
participant Guard as ConnectionGuard
participant WS as WebSocketListenerBase
participant Conn as WebSocketConnection
Client->>Socket: TCP Connect (or PROXY Protocol v1/v2)
Socket->>Guard: Limiter.TryAccept(ip)
Guard-->>Socket: Accepted
Socket->>WS: BeginWebSocketHandshake(socket)
Client->>WS: HTTP/1.1 GET /ws/ (Upgrade: websocket) or GET /healthz
alt Matches HTTP Route (/healthz, /version, /metrics, MapGet, MapPost)
WS-->>Client: HTTP/1.1 200 OK + Body (Connection: close)
WS->>Guard: Release accept-time limiter slot
else WebSocket Upgrade Request
WS->>WS: Validate Path, AllowedOrigins (CSWSH), SubProtocol & ForwardedHeaders
WS-->>Client: HTTP/1.1 101 Switching Protocols (Sec-WebSocket-Accept)
WS->>Conn: WebSocket.CreateFromStream(NetworkStream) -> new WebSocketConnection
WS->>Conn: Register in TimingWheel + ConnectionHub & Start Receive Loop
endKey Architectural Behaviors
- Handshake Timeout Sweeper: When
Activate()is called,WebSocketListenerBaseschedules a 1-second recurring task (TaskManager.ScheduleRecurring) that sweeps stalled HTTP upgrade contexts exceedingNetworkWebSocketOptions.HandshakeTimeoutMs(5000ms default), closing their sockets outside the lock and releasing theirConnectionGuardslots. - Built-In DevOps HTTP Endpoints: When
NetworkWebSocketOptions.EnableDevOpsEndpoints = true(default), the listener registers zero-allocation HTTPGEThandlers on the same port:/healthz,/health,/livez,/readyz,/startupz,/ping$\rightarrow$200 OK(OK)/version$\rightarrow$200 OKJSON ({"name":"Nalix","version":"1.0.0","status":"running"})/metrics$\rightarrow$200 OKPrometheus text format (nalix_active_connections)
- CSWSH Origin Enforcement: Validates the
Originheader viaNetworkWebSocketOptions.IsOriginAllowed(origin)before sending101 Switching Protocols. Rejected requests receiveHTTP/1.1 403 Forbiddenand close immediately. - L7 Forwarded Headers (
ForwardedHeadersOptions): WhenForwardedHeadersOptions.Enabled = true, extracts the real client IP fromCF-Connecting-IPorX-Forwarded-For(enforcingTrustedProxyOptionswhenRequireTrustedProxy = true) and transfers theConnectionGuardslot from the proxy IP to the real client IP.
2. WebSocketListenerBase API Reference
namespace Nalix.Network.Listeners.Web;
public abstract partial class WebSocketListenerBase : TcpListenerBase
{
protected WebSocketListenerBase(
ushort port,
string path,
IProtocol protocol,
IConnectionHub hub,
IConnectionGuard guard);
protected WebSocketListenerBase(
IProtocol protocol,
IConnectionHub hub,
IConnectionGuard guard);
protected WebSocketListenerBase(
string path,
IProtocol protocol,
IConnectionHub hub,
IConnectionGuard guard);
// Minimal HTTP/1.1 control-plane routing on the WebSocket port
public void MapGet(string path, HttpRouteHandler handler);
public void MapPost(string path, HttpRouteHandler handler);
public override void Activate(CancellationToken cancellationToken = default);
public override void Deactivate(CancellationToken cancellationToken = default);
}
3. Minimal HTTP Routing Types (HttpRequestView.cs)
Nalix.Network.Listeners.Web exposes zero-allocation ref struct types for handling custom HTTP GET and POST routes registered via WebSocketListenerBase.MapGet / MapPost or IWebSocketBindingBuilder.MapGet / MapPost:
HttpMethodType & HttpRouteHandler
public enum HttpMethodType : byte
{
Get = 1,
Post = 2,
Options = 3
}
public delegate void HttpRouteHandler(
in HttpRequestView request,
ref HttpResponseWriter response);
HttpRequestView (readonly ref struct)
| Property | Type | Description |
|---|---|---|
Method | ReadOnlySpan<byte> | Raw HTTP method bytes (e.g., "GET"u8, "POST"u8). |
Path | ReadOnlySpan<byte> | Request path without the query string (e.g., "/metrics"u8). |
Query | ReadOnlySpan<byte> | Raw query string without the leading '?', or empty if absent. |
Body | ReadOnlySpan<byte> | Request payload body bytes (for POST requests, up to MaxUpgradeRequestSize), or empty. |
ContentType | ReadOnlySpan<byte> | Content-Type header value, or empty if absent. |
Origin | ReadOnlySpan<byte> | Origin header value, or empty if absent. |
RemoteEndPoint | EndPoint? | Resolved client endpoint (accounting for PROXY Protocol v1/v2 or trusted ForwardedHeadersOptions). |
HttpResponseWriter (ref struct)
Rents buffers from BufferLease.ByteArrayPool (up to 1_048_576 bytes max body capacity) and returns them automatically after the response is sent.
| Member | Signature | Description |
|---|---|---|
StatusCode | public int StatusCode { get; set; } | HTTP status code (default 200). |
ContentType | public ReadOnlySpan<byte> ContentType { readonly get; set; } | Content-Type header bytes (default "text/plain; charset=utf-8"u8). |
WrittenCount | public int WrittenCount { get; } | Number of body bytes written so far. |
WrittenSpan | public readonly ReadOnlySpan<byte> WrittenSpan { get; } | Written response body as a read-only byte span. |
GetSpan(int) | public Span<byte> GetSpan(int sizeHint = 0) | Returns a writable Span<byte> of at least sizeHint bytes. |
Advance(int) | public void Advance(int count) | Advances WrittenCount after writing into GetSpan. |
Write(ReadOnlySpan<byte>) | public void Write(ReadOnlySpan<byte> bytes) | Appends raw UTF-8 or binary bytes to the response body. |
WriteUtf8(ReadOnlySpan<char>) | public void WriteUtf8(ReadOnlySpan<char> text) | Encodes and appends a character span as UTF-8. |
WriteJson(ReadOnlySpan<char>) | public void WriteJson(ReadOnlySpan<char> json) | Sets ContentType to "application/json; charset=utf-8"u8 and writes the UTF-8 JSON payload. |
WriteInt32(int) / WriteInt64(long) | public void WriteInt32(int value) / WriteInt64(long value) | Formats an integer as UTF-8 digits into the response body with zero allocations. |
Usage Example
using Nalix.Hosting;
using Nalix.Hosting.Protocols;
using Nalix.Network.Listeners.Web;
var app = NetworkApplication.CreateBuilder()
.MapWebSocket<DefaultProtocol>()
.OnPort(57207)
.WithPath("/ws/")
.MapGet("/status", (in HttpRequestView req, ref HttpResponseWriter res) =>
{
res.StatusCode = 200;
res.WriteJson("{\"ready\":true}");
})
.Bind()
.Build();