How WebServer.cs Works in ChocolateLMLite: ASP.NET Core HTTP and WebSocket Server

WebServer.cs implements the core HTTP and WebSocket server for ChocolateLMLite using ASP.NET Core's minimal API model on Kestrel, handling static file serving, REST API endpoints for persona management, and real-time bidirectional communication via WebSockets.

The WebServer.cs component serves as the HTTP façade for ChocolateLMLite, exposing a RESTful API and WebSocket endpoint that bridge the desktop application's core logic with its web-based UI. Built with ASP.NET Core's minimal API model and running on the Kestrel web server, this component is instantiated in src/Program.cs and orchestrates all network communication for the application.

Server Bootstrap and Kestrel Configuration

The server lifecycle begins in the RunSync method, which constructs a WebApplicationBuilder and configures Kestrel to listen on the specified port—either loopback or any IP address depending on the localOnly parameter. The configuration enforces a 100 MiB request body size limit to prevent resource exhaustion.

To reduce console noise, the bootstrap replaces the default logger with a custom MyLogProvider and sets the minimum log level to Warning. This initialization occurs in the early lines of RunSync within src/WebServer.cs.

// Server instantiation in Program.cs
WebServer server = new WebServer(consoleMonitor, persona, voiceVox);
server.RunSync(port, localOnly, systemSettingsLocalOnly, cts.Token);

Middleware Pipeline and Request Processing

The middleware chain configures the HTTP pipeline with several critical components. First, exception-handling middleware catches uncaught exceptions and converts them to JSON error responses. This is followed by request-logging middleware and IP-address monitoring that tracks the last client IP for the UI display.

WebSocket activation middleware enables the upgrade pipeline, while custom middleware injects cache-busting headers to prevent stale static assets. The pipeline concludes with UseStaticFiles, which serves content from the static folder under the root path.

REST API Endpoints for Persona Management

The server exposes a comprehensive REST API using MapGet, MapPost, and MapDelete endpoints. These routes handle server control, global settings management, and full CRUD operations for personas.

System Control and Settings

The /api/system/restart endpoint triggers application restart, while /api/setting provides access to global configuration values. The system settings page at /system.htm can be restricted to localhost only via the systemSettingsLocalOnly flag, preventing remote administration.

Persona CRUD Operations

Active persona endpoints manage file uploads and message history at /api/persona/active/message, while dedicated routes handle persona creation at /api/persona/new. A separate endpoint exposes VoiceVox speaker lists at /api/voicevox/speakers.

All endpoints utilize two helper methods: ParseRequestBodyAsync deserializes JSON request bodies to Dictionary<string, JsonElement> while redacting API keys from logs, and DictionaryToJson serializes responses with output truncated to 200 characters in log entries.


# Query active persona messages

curl -s http://localhost:8080/api/persona/active/message?index=0\&count=20

# Create a new persona

curl -X POST http://localhost:8080/api/persona/new \
     -H "Content-Type: application/json" \
     -d '{"name":"MyAssistant"}'

WebSocket Handling and Real-Time Broadcasting

The /ws route handles WebSocket upgrades and maintains connections in a WebSocketList collection. Each connection is stored as a tuple containing the socket and a TaskCompletionSource that signals when the connection should terminate.

Connection Management and Limits

To prevent resource exhaustion, the server limits concurrent connections to 16 sockets. When this limit is exceeded, the oldest connections are gracefully closed before accepting new upgrades. Each socket's lifetime is tied to its TaskCompletionSource, which completes when the server shuts down or the socket aborts.

Broadcasting and Keep-Alive

The Broadcast method iterates over a snapshot of active sockets, sending JSON messages via WebSocket.SendAsync and removing any sockets that have closed or failed. A background task started in RunSync emits a { "ping": true } payload every second to keep client connections alive and detect stale sockets.

// Browser WebSocket connection
const ws = new WebSocket(`ws://${location.host}/ws`);
ws.onmessage = e => console.log('Server event:', JSON.parse(e.data));
ws.onopen = () => console.log('WebSocket ready');

Static Files and HTML Entry Points

The server serves the web UI through specific HTML entry points. The root path / redirects to index.htm, serving as the main application interface. Static assets including HTML, CSS, and JavaScript are served from the static folder using the standard UseStaticFiles middleware, allowing the UI to function as a single-page application (SPA) that communicates with the backend API.

Graceful Shutdown Implementation

The Stop method orchestrates clean server termination. It first sets an isRunning flag to false, then iterates through WebSocketList to close each socket and resolve its associated TaskCompletionSource. This ensures that awaiting tasks complete rather than hanging.

After closing all WebSocket connections, the method calls app.StopAsync() to shut down the Kestrel server. Comprehensive logging surrounds the shutdown process to provide visibility during termination sequences.

// Triggered by /api/system/restart endpoint
Program.Stop();   // Cancels token and calls server.Stop()

Summary

  • WebServer.cs in ChocolateLMLite implements an ASP.NET Core minimal API server running on Kestrel, handling both HTTP requests and WebSocket connections in src/WebServer.cs.
  • The RunSync method configures the server with custom logging, 100 MiB request size limits, and middleware for exception handling, IP monitoring, and static file serving.
  • REST endpoints provide CRUD operations for personas, system control, and VoiceVox integration, utilizing ParseRequestBodyAsync and DictionaryToJson for safe JSON handling with API key redaction.
  • The /ws endpoint supports up to 16 concurrent WebSocket connections with automatic ping broadcasting every second and graceful cleanup of stale connections.
  • The Stop method ensures graceful shutdown by closing all sockets, resolving pending TaskCompletionSource tasks, and terminating the Kestrel host.

Frequently Asked Questions

How does WebServer.cs handle authentication for sensitive endpoints?

The WebServer.cs component does not implement traditional authentication mechanisms. Instead, it relies on network-level security through the localOnly and systemSettingsLocalOnly flags. When enabled, the /system.htm endpoint and certain configuration interfaces are restricted to localhost connections only, preventing remote access to administrative functions while allowing local management.

What is the maximum request body size allowed by the server?

According to the source code in src/WebServer.cs, the server limits request body sizes to 100 MiB during the Kestrel configuration phase in RunSync. This limit prevents memory exhaustion from oversized uploads while accommodating large persona data or file transfers within the ChocolateLMLite application.

How does the server manage WebSocket connection limits?

The implementation maintains a hard cap of 16 concurrent WebSocket connections stored in WebSocketList. When a new connection exceeds this limit, the server identifies and gracefully closes the oldest connections before accepting the new upgrade request, ensuring resource availability for active clients.

Can the WebSocket broadcast functionality be called from other classes?

Yes, the Broadcast method is exposed publicly and initialized through a static Broadcaster class. In src/Program.cs, the initialization Broadcaster.Initialize(server.Broadcast) wires the server's broadcast capability to the rest of the application, allowing domain logic like Persona or VoiceVox to push real-time updates to connected browsers without direct coupling to the HTTP server implementation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →