Files
Joseph Doherty e553db6d40 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.
2026-02-23 10:14:18 -05:00

9.1 KiB

Operations Overview

This document covers running the server, graceful shutdown, connecting clients, and the test suite.


Running the Server

The host application is src/NATS.Server.Host. Run it with dotnet run:

# Default (port 4222, bind 0.0.0.0)
dotnet run --project src/NATS.Server.Host

# Custom port
dotnet run --project src/NATS.Server.Host -- -p 14222

# Custom bind address and name
dotnet run --project src/NATS.Server.Host -- -a 127.0.0.1 -n my-server

Arguments after -- are passed to the program. The three supported flags are -p/--port, -a/--addr, and -n/--name. See Configuration Overview for the full CLI reference.

On startup, the server logs the address it is listening on:

[14:32:01 INF] Listening on 0.0.0.0:4222

Full host setup

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):

// 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;
    }
}

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);
using var server = new NatsServer(options, loggerFactory);

server.HandleSignals();

Console.CancelKeyPress += (_, e) =>
{
    e.Cancel = true;
    _ = Task.Run(async () => await server.ShutdownAsync());
};

_ = 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 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.

server.HandleSignals() registers additional OS signal handlers (SIGHUP for config reload, SIGUSR1 for log file reopen on Unix) before the main loop starts.


Testing

Run the full test suite with:

dotnet test

The test project is at tests/NATS.Server.Tests/. It uses xUnit with Shouldly for assertions.

Test summary

The test project contains 99 test files across seven areas:

  • 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[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[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.

ParserTestsasync [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.

ClientTestsasync [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.

ServerTestsasync [Fact] tests that start NatsServer on a random port. Verifies INFO on connect, basic pub/sub delivery (MSG format), and wildcard subscription matching.

IntegrationTestsasync [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.


Connecting with Standard NATS Clients

The server speaks the standard NATS wire protocol. Any NATS client library can connect to it.

# Using the nats CLI tool
nats sub "test.>" --server nats://127.0.0.1:4222
nats pub test.hello "world" --server nats://127.0.0.1:4222

From .NET, using NATS.Client.Core:

var opts = new NatsOpts { Url = "nats://127.0.0.1:4222" };
await using var conn = new NatsConnection(opts);
await conn.ConnectAsync();
await conn.PublishAsync("test.hello", "world");

Go Reference Server

The Go reference implementation can be built from golang/nats-server/ for comparison testing:

# Build
cd golang/nats-server && go build

# Run on default port
./nats-server

# Run on custom port
./nats-server -p 14222

The Go server is useful for verifying that the .NET port produces identical protocol output and behaves the same way under edge-case client interactions.


Known Gaps vs Go Reference

The following areas have partial or stub implementations compared to the Go reference server:

  • 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