docs: add Authentication, Clustering, JetStream, Monitoring overviews; update existing docs
New files: - Documentation/Authentication/Overview.md — all 7 auth mechanisms with real source snippets (NKey/JWT/username-password/token/TLS mapping), nonce generation, account system, permissions, JWT permission templates - Documentation/Clustering/Overview.md — route TCP handshake, in-process subscription propagation, gateway/leaf node stubs, honest gaps list - Documentation/JetStream/Overview.md — API surface (4 handled subjects), streams, consumers, storage (MemStore/FileStore), in-process RAFT, mirror/source, gaps list - Documentation/Monitoring/Overview.md — all 12 endpoints with real field tables, Go compatibility notes Updated files: - GettingStarted/Architecture.md — 14-subdirectory tree, real NatsClient/NatsServer field snippets, 9 new Go reference rows, Channel write queue design choice - GettingStarted/Setup.md — xUnit 3, 100 test files grouped by area - Operations/Overview.md — 99 test files, accurate Program.cs snippet, limitations section renamed to "Known Gaps vs Go Reference" with 7 real gaps - Server/Overview.md — grouped fields, TLS/WS accept path, lame-duck mode, POSIX signals - Configuration/Overview.md — 14 subsystem option tables, 24-row CLI table, LogOverrides - Server/Client.md — Channel write queue, 4-task RunAsync, CommandMatrix, real fields All docs verified against codebase 2026-02-23; 713 tests pass.
This commit is contained in:
@@ -29,64 +29,70 @@ On startup, the server logs the address it is listening on:
|
||||
|
||||
### Full host setup
|
||||
|
||||
`Program.cs` initializes Serilog, parses CLI arguments, starts the server, and handles graceful shutdown:
|
||||
`Program.cs` initializes Serilog, parses CLI arguments, starts the server, and handles graceful shutdown. The startup sequence does two passes over `args`: the first scans for `-c` to load a config file as the base `NatsOptions`, and the second applies remaining CLI flags on top (CLI flags always win over the config file):
|
||||
|
||||
```csharp
|
||||
using NATS.Server;
|
||||
using Serilog;
|
||||
// First pass: scan args for -c flag to get config file path
|
||||
string? configFile = null;
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
if (args[i] == "-c" && i + 1 < args.Length)
|
||||
{
|
||||
configFile = args[++i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Debug()
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}")
|
||||
.CreateLogger();
|
||||
|
||||
var options = new NatsOptions();
|
||||
var options = configFile != null
|
||||
? ConfigProcessor.ProcessConfigFile(configFile)
|
||||
: new NatsOptions();
|
||||
|
||||
// Second pass: apply CLI args on top of config-loaded options
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
switch (args[i])
|
||||
{
|
||||
case "-p" or "--port" when i + 1 < args.Length:
|
||||
options.Port = int.Parse(args[++i]);
|
||||
options.InCmdLine.Add("Port");
|
||||
break;
|
||||
case "-a" or "--addr" when i + 1 < args.Length:
|
||||
options.Host = args[++i];
|
||||
options.InCmdLine.Add("Host");
|
||||
break;
|
||||
case "-n" or "--name" when i + 1 < args.Length:
|
||||
options.ServerName = args[++i];
|
||||
options.InCmdLine.Add("ServerName");
|
||||
break;
|
||||
// ... additional flags: -m, --tls*, -D/-V/-DV, -l, -c, --pid, etc.
|
||||
}
|
||||
}
|
||||
|
||||
using var loggerFactory = new Serilog.Extensions.Logging.SerilogLoggerFactory(Log.Logger);
|
||||
var server = new NatsServer(options, loggerFactory);
|
||||
using var server = new NatsServer(options, loggerFactory);
|
||||
|
||||
server.HandleSignals();
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
Console.CancelKeyPress += (_, e) =>
|
||||
{
|
||||
e.Cancel = true;
|
||||
cts.Cancel();
|
||||
_ = Task.Run(async () => await server.ShutdownAsync());
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
await server.StartAsync(cts.Token);
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
finally
|
||||
{
|
||||
Log.CloseAndFlush();
|
||||
}
|
||||
_ = server.StartAsync(CancellationToken.None);
|
||||
await server.WaitForReadyAsync();
|
||||
server.WaitForShutdown();
|
||||
```
|
||||
|
||||
`InCmdLine` tracks which options were supplied on the command line so that a subsequent config-file reload does not overwrite them.
|
||||
|
||||
---
|
||||
|
||||
## Graceful Shutdown
|
||||
|
||||
Pressing Ctrl+C triggers `Console.CancelKeyPress`. The handler sets `e.Cancel = true` — this prevents the process from terminating immediately — and calls `cts.Cancel()` to signal the `CancellationToken` passed to `server.StartAsync`.
|
||||
Pressing Ctrl+C triggers `Console.CancelKeyPress`. The handler sets `e.Cancel = true` — this prevents the process from terminating immediately — and dispatches `server.ShutdownAsync()` on a background task. `WaitForShutdown()` blocks the main thread until shutdown completes. The `finally` block runs `Log.CloseAndFlush()` to ensure all buffered log output is written before the process exits.
|
||||
|
||||
`NatsServer.StartAsync` exits its accept loop on cancellation. In-flight client connections are left to drain naturally. After `StartAsync` returns (via `OperationCanceledException` which is caught), the `finally` block runs `Log.CloseAndFlush()` to ensure all buffered log output is written before the process exits.
|
||||
`server.HandleSignals()` registers additional OS signal handlers (SIGHUP for config reload, SIGUSR1 for log file reopen on Unix) before the main loop starts.
|
||||
|
||||
---
|
||||
|
||||
@@ -102,30 +108,30 @@ The test project is at `tests/NATS.Server.Tests/`. It uses xUnit with Shouldly f
|
||||
|
||||
### Test summary
|
||||
|
||||
69 tests across 6 test files:
|
||||
The test project contains 99 test files across seven areas:
|
||||
|
||||
| File | Tests | Coverage |
|
||||
|------|-------|----------|
|
||||
| `SubjectMatchTests.cs` | 33 | Subject validation and wildcard matching |
|
||||
| `SubListTests.cs` | 12 | Trie insert, remove, match, queue groups, cache |
|
||||
| `ParserTests.cs` | 14 | All command types, split packets, case insensitivity |
|
||||
| `ClientTests.cs` | 2 | Socket-level INFO on connect, PING/PONG |
|
||||
| `ServerTests.cs` | 3 | End-to-end accept, pub/sub, wildcard delivery |
|
||||
| `IntegrationTests.cs` | 5 | NATS.Client.Core protocol compatibility |
|
||||
- **Auth/TLS** (23 files) — authenticators (token, username/password, NKey, JWT), client permissions, OCSP, TLS connection wrapping, TLS rate limiting, account isolation, permission integration
|
||||
- **JetStream/RAFT** (23 files) — stream API, consumer API, publish, pull/push delivery, ack redelivery, retention policies, mirroring/sourcing, config validation, FileStore, MemStore, store contract, RAFT election, replication, and snapshot catchup
|
||||
- **Monitoring/Config** (15 files) — HTTP monitor endpoints, `/jsz`, config file parsing (lexer + parser), config reload, `NatsOptions`, server stats, subsz, account stats, account resolver, logging, Go parity runner
|
||||
- **Client lifecycle** (12 files) — `NatsClient` flags, closed-reason tracking, trace mode, write loop, no-responders, verbose mode, RTT, response tracker, internal client, event system, import/export, response routing
|
||||
- **Protocol/Parser** (7 files) — `NatsParser` commands, subject validation and wildcard matching, `SubList` trie, NATS header parser, subject transforms
|
||||
- **Clustering** (4 files) — route handshake, route subscription propagation, gateway/leaf bootstrap, cluster JetStream config processor
|
||||
- **WebSocket** (9 files in `WebSocket/`) — frame read/write, compression, upgrade handshake, origin checking, connection handling, integration, options, constants
|
||||
- **Integration** (6 files) — end-to-end tests using `NATS.Client.Core`, system events, system request-reply, auth integration, NKey integration, permission integration
|
||||
|
||||
### Test categories
|
||||
|
||||
**SubjectMatchTests** — 33 `[Theory]` cases verifying `SubjectMatch.IsValidSubject` (16 cases), `SubjectMatch.IsValidPublishSubject` (6 cases), and `SubjectMatch.MatchLiteral` (11 cases). Covers empty strings, leading/trailing dots, embedded spaces, `>` in non-terminal position, and all wildcard combinations.
|
||||
**SubjectMatchTests** — `[Theory]` cases verifying `SubjectMatch.IsValidSubject`, `SubjectMatch.IsValidPublishSubject`, and `SubjectMatch.MatchLiteral`. Covers empty strings, leading/trailing dots, embedded spaces, `>` in non-terminal position, and all wildcard combinations.
|
||||
|
||||
**SubListTests** — 12 `[Fact]` tests exercising the `SubList` trie directly: literal insert and match, empty result, `*` wildcard at various token levels, `>` wildcard, root `>`, multiple overlapping subscriptions, remove, queue group grouping, `Count` tracking, and cache invalidation after a wildcard insert.
|
||||
**SubListTests** — `[Fact]` tests exercising the `SubList` trie directly: literal insert and match, empty result, `*` wildcard at various token levels, `>` wildcard, root `>`, multiple overlapping subscriptions, remove, queue group grouping, `Count` tracking, and cache invalidation after a wildcard insert.
|
||||
|
||||
**ParserTests** — 14 `async [Fact]` tests that write protocol bytes into a `Pipe` and assert on the resulting `ParsedCommand` list. Covers `PING`, `PONG`, `CONNECT`, `SUB` (with and without queue group), `UNSUB` (with and without `max-messages`), `PUB` (with payload, with reply-to, zero payload), `HPUB` (with header), `INFO`, multiple commands in a single buffer, and case-insensitive parsing.
|
||||
**ParserTests** — `async [Fact]` tests that write protocol bytes into a `Pipe` and assert on the resulting `ParsedCommand` list. Covers `PING`, `PONG`, `CONNECT`, `SUB` (with and without queue group), `UNSUB` (with and without `max-messages`), `PUB` (with payload, with reply-to, zero payload), `HPUB` (with header), `INFO`, multiple commands in a single buffer, and case-insensitive parsing.
|
||||
|
||||
**ClientTests** — 2 `async [Fact]` tests using a real loopback socket pair. Verifies that `NatsClient` sends an `INFO` frame immediately on connection, and that it responds `PONG` to a `PING` after `CONNECT`.
|
||||
**ClientTests** — `async [Fact]` tests using a real loopback socket pair. Verifies that `NatsClient` sends an `INFO` frame immediately on connection, and that it responds `PONG` to a `PING` after `CONNECT`.
|
||||
|
||||
**ServerTests** — 3 `async [Fact]` tests that start `NatsServer` on a random port. Verifies `INFO` on connect, basic pub/sub delivery (`MSG` format), and wildcard subscription matching.
|
||||
**ServerTests** — `async [Fact]` tests that start `NatsServer` on a random port. Verifies `INFO` on connect, basic pub/sub delivery (`MSG` format), and wildcard subscription matching.
|
||||
|
||||
**IntegrationTests** — 5 `async [Fact]` tests using the official `NATS.Client.Core` v2.7.2 NuGet package. Verifies end-to-end protocol compatibility with a real NATS client library: basic pub/sub, `*` wildcard delivery, `>` wildcard delivery, fan-out to two subscribers, and `PingAsync`.
|
||||
**IntegrationTests** — `async [Fact]` tests using the official `NATS.Client.Core` NuGet package. Verifies end-to-end protocol compatibility with a real NATS client library: basic pub/sub, `*` wildcard delivery, `>` wildcard delivery, fan-out to two subscribers, and `PingAsync`.
|
||||
|
||||
Integration tests use `NullLoggerFactory.Instance` for the server so test output is not cluttered with server logs.
|
||||
|
||||
@@ -171,16 +177,17 @@ The Go server is useful for verifying that the .NET port produces identical prot
|
||||
|
||||
---
|
||||
|
||||
## Current Limitations
|
||||
## Known Gaps vs Go Reference
|
||||
|
||||
The following features present in the Go reference server are not yet ported:
|
||||
The following areas have partial or stub implementations compared to the Go reference server:
|
||||
|
||||
- Authentication — no username/password, token, NKey, or JWT support
|
||||
- Clustering — no routes, gateways, or leaf nodes
|
||||
- JetStream — no persistent streaming, streams, consumers, or RAFT
|
||||
- Monitoring — no HTTP endpoints (`/varz`, `/connz`, `/healthz`, etc.)
|
||||
- TLS — all connections are plaintext
|
||||
- WebSocket — no WebSocket transport
|
||||
- **MQTT listener** — config is parsed and the option is recognized, but no MQTT transport is implemented
|
||||
- **Route message routing** — the route TCP connection and handshake are established, but `RMSG` forwarding is not implemented; messages are not relayed to peer nodes
|
||||
- **Gateways** — the listener stub accepts connections, but no inter-cluster bridging or interest-only filtering is implemented
|
||||
- **Leaf nodes** — the listener stub accepts connections, but no hub-and-spoke topology or subject sharing is implemented
|
||||
- **JetStream API surface** — only `STREAM.CREATE`, `STREAM.INFO`, `CONSUMER.CREATE`, and `CONSUMER.INFO` API subjects are handled; all others return a not-found error response
|
||||
- **FileStore durability** — the file store maintains a full in-memory index, performs per-write I/O without batching, and rewrites the full block on trim; it is not production-safe under load
|
||||
- **RAFT network transport** — the RAFT implementation uses in-process message passing only; there is no network transport, so consensus does not survive process restarts or span multiple server instances
|
||||
|
||||
---
|
||||
|
||||
@@ -190,4 +197,4 @@ The following features present in the Go reference server are not yet ported:
|
||||
- [Server Overview](../Server/Overview.md)
|
||||
- [Protocol Overview](../Protocol/Overview.md)
|
||||
|
||||
<!-- Last verified against codebase: 2026-02-22 -->
|
||||
<!-- Last verified against codebase: 2026-02-23 -->
|
||||
|
||||
Reference in New Issue
Block a user