Watson Webserver Documentation

repository·main·Indexed 19 days ago

https://github.com/dotnet/watsonwebserver

Watson 7 is a high-performance, asynchronous C# web server for building REST APIs and HTTP services, supporting HTTP/1.1, HTTP/2, and HTTP/3. It features FastAPI-like route handlers with automatic JSON serialization, built-in middleware, structured authentication, health checks, and native WebSocket support via the Watson and Watson.Clients packages.

Tokens
29.5K
Snippets
77
Records
120
Agent score
66%

What's inside Watson Webserver

  1. Overview of the Watson WebSocket Client Architecture

    main

    The Watson WebSocket client is designed as a modern, async-first sibling library to the Watson server, packaged separately as Watson.Clients. Unlike older versions of WatsonWebsocket, the client does not use a hidden background receive loop. Instead, it follows a model where the consumer explicitly manages receiving messages, ensuring whole-message semantics and better lifecycle control.

    Key architectural principles include:

    • Async-First API: All core operations (ConnectAsync, SendTextAsync, ReceiveAsync, etc.) are asynchronous.
    • Explicit Lifecycle: Connection and disconnection are explicit operations.
    • Whole-Message Semantics: The client reassembles fragmented frames so that users receive complete messages rather than raw frames.
    • Separation of Concerns: The client is a separate NuGet package (Watson.Clients) to allow for different framework targeting (e.g., netstandard2.0, net8.0) without constraining the server package.
  2. Understand Watson.Clients limitations and behavior

    main

    When using Watson.Clients, be aware of the following operational constraints:

    • Receive Loop: Only one Watson-managed receive operation (e.g., ReceiveAsync() or ReadMessagesAsync()) may be active at a time.
    • Connection Termination: ReceiveAsync() returns null when the peer closes the connection. Canceling an active Watson-managed receive may end the live connection on current runtimes.
    • Disposal: A disposed client cannot be used again.
    • No Helpers: There is no built-in SendAndWaitAsync() helper in v1; you must perform an explicit send followed by an explicit receive.
    • TLS: Invalid or self-signed TLS certificates are rejected by default.
  3. How WebSocket and HTTP routing interact

    main

    Watson allows you to register the same path for both standard HTTP GET/POST routes and WebSocket routes.

    When a request arrives:

    1. WebSocket upgrade requests are matched against WebSocket routes first.
    2. Ordinary HTTP requests follow normal HTTP route matching.

    This allows you to serve a web page via HTTP on /chat and then upgrade that same path to a WebSocket connection.

    server.Get("/chat", async req => new { Mode = "http" });
    server.WebSocket("/chat", HandleSocketAsync);
  4. How the WatsonWebSocketClient model works

    main

    The WatsonWebSocketClient is an explicit, async-first client. Unlike clients with hidden background pumps, you must manage the lifecycle manually:

    1. Initialization: Create the client using a Uri or by specifying host, port, SSL, and path.
    2. Connection: Call ConnectAsync() to establish the connection.
    3. Communication: Use SendTextAsync() or SendBinaryAsync() to send data, and ReceiveAsync() or ReadMessagesAsync() to receive data.
    4. Termination: Call CloseAsync(...) for a graceful shutdown and Dispose() to release resources.

    Critical Rules:

    • Only ws and wss URIs are supported.
    • Concurrency Rule: Only one Watson-managed receive operation (ReceiveAsync or ReadMessagesAsync) may be active at a time. Attempting concurrent receives will throw an InvalidOperationException.
    • Receive Semantics: ReceiveAsync() returns null when the connection closes.
    • Disposal: Dispose() is terminal; once called, the client cannot be reused.
  5. Configure WebSocket Security and Certificates

    main

    In Watson 7, accepting invalid certificates is an opt-in behavior. This is a breaking change from older versions of WatsonWebsocket to ensure safer defaults.

    To allow invalid certificates, you must explicitly configure the client settings or use a callback before calling ConnectAsync. This ensures that insecure connections are never established by accident.

  6. How Watson traces work

    main

    Watson produces one span per request with a Server kind, named using the pattern {method} {route} (e.g., GET /users/{id}).

    Key behaviors:

    • Error Handling: Spans transition to Error status on 5xx responses or unhandled exceptions. Exception details (type, message, stack) are attached as span events.
    • Trace Propagation: If an inbound request contains a W3C traceparent header, Watson adopts it as the parent span, allowing for distributed tracing across services.
    • Log Correlation: Watson sets Activity.Current for the duration of the handler. This allows ILogger instances used within your routes to automatically include trace and span IDs, enabling log-to-trace correlation.
    • High-Cardinality Data: While metrics use low-cardinality labels (like route templates), spans carry high-cardinality details such as the raw path, client address, user agent, body size, and content type.
  7. Access the raw ClientWebSocket escape hatch

    main

    For advanced scenarios that require direct access to .NET framework features (such as specific socket configurations not covered by the high-level API), WatsonWebSocketClient exposes a raw ClientWebSocket instance.

    Warning: Using the raw socket is an advanced feature and can lead to misuse if mixed with high-level Watson APIs:

    • Ownership: Once you perform raw receive operations on the socket, you own the receive coordination for the lifetime of that connection.
    • Bypassing Watson: Raw sends and receives may bypass Watson-managed counters, serialization, and whole-message reassembly.
    • Unsupported Mixing: Mixing Watson-managed receive APIs (like ReceiveAsync) with raw ClientWebSocket.ReceiveAsync on the same connection is unsupported.
  8. Understand HTTP/2 modes: h2 vs h2c

    main

    WatsonWebserver supports two modes of HTTP/2:

    1. h2: HTTP/2 over TLS. This is the standard mode negotiated via ALPN during the TLS handshake. Most production environments use this.
    2. h2c: HTTP/2 cleartext (no TLS). This is negotiated via the Upgrade: h2c header or via "prior knowledge" (where the client assumes HTTP/2 without negotiation). Use this only for local testing without certificates.
  9. Access raw sockets via the Client escape hatch

    main
    If the high-level WatsonWebSocketClient API does not meet specific low-level requirements, the library provides a raw public ClientWebSocket escape hatch. This allows developers to bypass the managed Watson client logic and interact directly with the underlying WebSocket connection.
  10. Understand the performance cost of Watson telemetry

    main

    Watson's telemetry is designed to be extremely low overhead:

    • When no collector is listening: Add or Record calls are simple enabled-checks with early returns, costing low single-digit nanoseconds and zero allocation. StartActivity on an unsampled source also returns null quickly.
    • When a collector is subscribed: Counters and histograms remain cheap (tens of nanoseconds). Spans only allocate when they are actually sampled.
    • Sampling: The sampling ratio is controlled by your collector, not by Watson.
    • Memory usage: Memory scales with the number of distinct label combinations, not the request count. To keep memory usage low, ensure your labels (such as route templates) are bounded.
  11. Use the WatsonWebSocketClient for WebSocket connections

    main

    The primary public surface for interacting with Watson Webserver as a client is the WatsonWebSocketClient. This client is provided in a dedicated Watson.Clients package, allowing it to be consumed independently of the server package. The API is designed to be async-first, focusing on explicit asynchronous receive semantics.

    // Note: The specific method signatures are part of the async-first API
    // but the primary entry point is WatsonWebSocketClient.
    // Use the Watson.Clients NuGet package to access this.
  12. Configure Access Control modes

    main

    The AccessControlManager (via WebserverSettings.AccessControl) determines how IP addresses are permitted or denied.

    Modes (AccessControlMode):

    • DefaultPermit: Allows all requests unless explicitly denied via the DenyList.
    • DefaultDeny: Denies all requests unless explicitly permitted via the PermitList.

    Management:

    • Assigning null to PermitList or DenyList replaces the current matcher with an empty one.