Shiny.Net.HttpServer.CommandLine
1.0.0-beta.20
Prefix Reserved
See the version list below for details.
dotnet tool install --global Shiny.Net.HttpServer.CommandLine --version 1.0.0-beta.20
dotnet new tool-manifest
dotnet tool install --local Shiny.Net.HttpServer.CommandLine --version 1.0.0-beta.20
#tool dotnet:?package=Shiny.Net.HttpServer.CommandLine&version=1.0.0-beta.20&prerelease
nuke :add-package Shiny.Net.HttpServer.CommandLine --version 1.0.0-beta.20
Shiny HTTP Server
ASP.NET Core is heavyweight and does not run on .NET MAUI or in several embedded server scenarios. This is a dependency-light, fully AOT/trim-clean HTTP/1.1, HTTP/2 & HTTP/3 server that runs anywhere .NET runs — plus tunnelling so a server embedded in a phone app is reachable from the public internet.
Only Microsoft.Extensions.* abstractions are taken as dependencies. Everything else — JSON, crypto,
JWT, OpenAPI, HPACK, QPACK — is built on what is in the box.
Packages
| Package | Description |
|---|---|
| Shiny.Net.HttpServer | The server: HTTP/1.1, HTTP/2 & HTTP/3, routing, middleware, DI scopes, static files, WebSockets, SSE, sessions, OpenAPI, CORS, rate limiting, IP filtering, tunnelling. Includes the typed-endpoint source generator |
| Shiny.Net.HttpServer.Jwt | JWT authentication on in-box crypto — no Microsoft.IdentityModel dependency |
| Shiny.Net.HttpServer.AzureRelay | Azure Relay tunnel provider |
| Shiny.Net.HttpServer.Ssh | SSH remote-forwarding tunnel provider, including zero-account quick tunnels |
| Shiny.Net.HttpServer.Mcp | Model Context Protocol (Streamable HTTP) transport — host an MCP server without ASP.NET Core, including inside a MAUI app |
| Shiny.Net.HttpServer.Mediator | Publishes Shiny.Mediator requests, commands and streams as endpoints generated at compile time. Generator included |
| Shiny.Net.HttpServer.DocumentDb | Publishes a Shiny.DocumentDb type as a REST resource — list, by-id, count, CRUD, merge-patch and a live SSE tail |
| Shiny.Net.HttpServer.WebDav | A WebDAV (RFC 4918) class 1 & 2 server over a directory — mount an app's storage in Finder, Windows Explorer or any WebDAV client, and open the same URL in a browser for a file manager with upload, rename and delete |
| Shiny.Net.HttpServer.Grpc | gRPC and gRPC-Web — unary, streaming and bidirectional methods over the same HTTP/2 stack, with serialization you supply |
| Shiny.Net.HttpServer.Discovery | mDNS/DNS-SD (Bonjour) — advertises the server on the local link and finds the ones other devices advertise, so nobody has to type an IP address |
| Shiny.Net.HttpServer.Mobile | Mobile lifecycle on Shiny.Core — stop on background and start on resume, an Android foreground service to keep serving, rebind when the device changes network, and a check for the manifest entries that silently break local networking. iOS and Android, with or without MAUI |
| Shiny.Net.HttpServer.Testing | An HttpClient wired to the server through memory — endpoint tests with no port, no listener and no socket, but the real parser, router and middleware |
| Shiny.Net.HttpServer.Tunnels | Agent-backed tunnels — supervises cloudflared, ngrok or tailscale and reports the public URL. Desktop, server and CLI; on a phone use the SSH provider or the relay |
| Shiny.Net.HttpServer.CommandLine | A .NET tool — shinyhttpserver — that serves a directory over WebDAV, so one address is both a browser file manager (browse, upload, rename, delete) and a drive Finder or Explorer can mount, with basic auth, per-operation permissions, and a QR code in the banner so a phone can scan its way in — --tunnel swaps the LAN address for a public pinggy.io tunnel so the phone need not be on the same network |
Getting Started
var server = new HttpServer(new HttpServerOptions { Port = 8080 });
server.MapGet("/ping", ctx => ctx.Response.WriteAsync("pong"));
await server.RunAsync();
Every registration this library owns hangs off one builder, in both hosting shapes:
var builder = HttpServer.CreateBuilder();
builder.Options.Port = 8080;
builder.AddAuthentication().AddJwtBearer(o => o.SigningKey = key);
builder.AddRateLimiter(o => o.GlobalPolicy = new FixedWindowRateLimitPolicy(100, TimeSpan.FromMinutes(1)));
builder.AddHealthChecks().AddServerCheck();
var app = builder.Build();
// …or, inside an app that already owns a container — the same calls:
services.AddShinyHttpServer(http =>
{
http.Options.Port = 8080;
http.AddHealthChecks().AddServerCheck();
http.Configure(server => server.MapMyAppEndpoints());
});
Typed endpoints, generated at compile time:
[Route("/api/users")]
public class UserEndpoints(IUserService users, ILogger<UserEndpoints> logger)
{
[Get("/{id:int}")]
public async Task<IActionResult> GetUser(int id, CancellationToken ct)
=> await users.FindAsync(id, ct) is { } u ? new OkObjectResult(u) : new NotFoundResult();
}
app.MapMyAppEndpoints(); // emitted for every [Route] class in the assembly
An MCP server, on the same host, reachable from a MAUI app:
builder.Services
.AddMcpServer(o => o.ServerInfo = new() { Name = "thermostat", Version = "1.0.0" })
.WithTools<ThermostatTools>()
.WithHttpTransport();
var app = builder.Build();
app.MapMcp(); // POST/GET/DELETE/OPTIONS on /mcp
The MCP package is trim- and AOT-clean like the rest, with one thing the compiler cannot check for
you: a tool's parameter and return types are published as a JSON schema, and building that schema by
reflection does not survive trimming. Tools that trade only in primitives need nothing extra; give
the rest a source-generated context, and MapMcp() will tell you if you missed one.
[JsonSerializable(typeof(Query))]
[JsonSerializable(typeof(IReadOnlyList<Reading>))]
public partial class ToolJson : JsonSerializerContext;
.WithTools<ThermostatTools>(ToolJson.Default.Options)
On a phone, where the server has to be found and has to survive the device moving:
builder.Services.AddShinyHttpServer(
http =>
{
http.Options.Address = IPAddress.Any;
// Stops on background, starts on resume, rebinds when the network changes.
http.AddHttpServerLifecycle(o => o.BackgroundMode = BackgroundServerMode.KeepAlive);
// Advertises on the local link, so the other device does not need an IP address.
http.AddHttpServerAdvertisement(o => o.ServiceType = "_myapp._tcp");
},
autoStart: false
);
and on the other device:
var found = await locator.FindFirstAsync("_myapp._tcp");
using var client = new HttpClient { BaseAddress = found!.BaseAddress };
What is in the box
The four tiers — one delegate, raw routes, middleware, and source-generated typed endpoints. Each is built on the one below and they compose in the same app.
| Core | Routing with constraints and runtime-mutable routes, ASP.NET-shaped middleware that can read and rewrite both bodies of an exchange, a real IServiceScope per request, results in both Results.* and IActionResult spellings, RFC 9457 problem details and an exception-handler chain, per-endpoint request timeouts, and a reverse proxy route |
| Caching | Conditional requests for handlers that are not serving a file — If-Match, If-None-Match, If-Modified-Since, 304 and 412 — plus output caching with a bounded in-memory store, where the saving is battery rather than bandwidth |
| Diagnostics | Health checks with liveness/readiness tags, telemetry on the in-box primitives — one Activity per request continuing the caller's traceparent, and the OpenTelemetry HTTP metrics an ASP.NET dashboard already reads — and W3C access logs, rolled and pruned, written off the request path |
| Formats | Content negotiation in both directions — responses chosen from Accept, request bodies from Content-Type. JSON out of the box; XML, MessagePack and protobuf are one line each, and a format of your own is an IOutputFormatter/IInputFormatter pair. XML and MessagePack need no dependency and no attributes on your DTOs: they read the same JsonTypeInfo the JSON path reads, which is what keeps them AOT-clean where XmlSerializer cannot be |
| Protocols | HTTP/1.1, HTTP/2 (own HPACK), HTTP/3 (own QPACK), WebSockets, Server-Sent Events, trailing headers on all three versions. Never guessed — ALPN over TLS, connection preface over cleartext. WebSockets carry permessage-deflate, keepalive pings, and a registry for broadcasting to a group |
| Content | Static files from disk or embedded resources, a published Blazor WebAssembly app, streaming multipart uploads, downloads with byte ranges and conditional GETs, a file browser over a directory, and brotli/gzip/deflate compression in both directions |
| Security | Authentication and authorization split ASP.NET-style, with Basic, API key, cookie and JWT schemes; policies, roles and claims; CORS, rate limiting and IP filtering, all with per-endpoint policies; signed double-submit antiforgery, the browser security headers, HSTS and an HTTPS redirect |
| TLS | Several endpoints with per-endpoint TLS, self-signed certificates generated in managed code (iOS and Android included), client certificates, and SPKI pinning for the app's own HttpClient |
| OpenAPI | An OpenAPI 3.0.3 document built entirely from compile-time metadata and your JsonSerializerContext — no reflection, no document object model |
| Tunnelling | A pluggable ITunnelProvider, the reference relay (both ends), SSH remote forwarding, zero-account quick tunnels, and Azure Relay |
| Mediator | Shiny.Mediator handlers published as endpoints — requests as JSON, commands as a status code, stream requests as Server-Sent Events, all bound at compile time |
| DocumentDb | A document type as a complete HTTP resource, with filtering, cursor paging, sparse fieldsets, ETag/If-Match, RFC 7396 merge-patch, a live SSE tail, and server-side scopes enforced on both sides of a write |
| gRPC | Unary, client-streaming, server-streaming and bidirectional methods, deadlines, per-message compression and status in trailers — plus gRPC-Web for browsers and anything on HTTP/1.1. Marshalling is yours, so nothing reflects over your messages |
| WebDAV | RFC 4918 classes 1 and 2 over a directory — PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK/UNLOCK, the If header and dead properties — so an app's storage mounts as a drive with no client to write |
| Lifecycle | Start, stop and restart at runtime, serialized and idempotent, with an observable state — an embedded server gets toggled, not just booted. It also follows the device: rebinding when the addresses change, and following the app between foreground and background |
| Discovery | The other half of hosting on a phone. mDNS advertises the server as the device moves and withdraws it when the server stops; the locator turns what is on the link into a base address a client can call |
| Testing | An in-memory transport, so an endpoint test costs no port and leaks no listener while still going through the real parser, router, middleware and framing |
Everything shipping targets net10.0 with the trim, AOT and single-file analyzers enabled, so
"AOT-clean" is enforced by the build rather than claimed in a readme.
Documentation
Full docs are at shinylib.net/httpserver.
Support
Shiny is free and will continue to be, but maintenance and support take a heavy toll on sustainability. If you or your company have the resources, please consider becoming a GitHub Sponsor.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.1 | 0 | 8/24/2026 |
| 1.0.1-g2fad83336d | 0 | 8/24/2026 |
| 1.0.0 | 38 | 8/24/2026 |
| 1.0.0-beta.20 | 28 | 8/24/2026 |
| 1.0.0-beta.18 | 46 | 8/22/2026 |
| 1.0.0-beta.17 | 45 | 8/21/2026 |
| 1.0.0-beta.15 | 44 | 8/21/2026 |
| 1.0.0-beta.14 | 49 | 8/21/2026 |
| 1.0.0-beta.13 | 49 | 8/21/2026 |