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

> Discover how WebServer.cs in ChocolateLMLite leverages ASP.NET Core Kestrel for HTTP and WebSocket servers. Explore static files, persona API, and real-time communication.

- Repository: [Segment (gpsnmeajp)/chocolatelmlite](https://github.com/gpsnmeajp/chocolatelmlite)
- Tags: internals
- Published: 2026-03-02

---

**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`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/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`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/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`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/src/WebServer.cs).

```csharp
// 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.

```bash

# 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.

```javascript
// 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.

```csharp
// 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`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/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`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/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`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/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`](https://github.com/gpsnmeajp/chocolatelmlite/blob/main/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.