|
1 | 1 | --- |
2 | 2 | name: shiny-httpserver |
3 | | -description: Generate code using Shiny.Net.HttpServer — a dependency-light, AOT/trim-clean HTTP/1.1, HTTP/2 & HTTP/3 server that runs anywhere .NET runs, including .NET MAUI and native tvOS, where ASP.NET Core cannot. Covers routing, middleware, source-generated typed endpoints, results and JSON, content negotiation with XML/MessagePack/protobuf formatters in both directions, static files and Blazor WASM, uploads/downloads, WebSockets, SSE, sessions, OpenAPI, authentication (Basic/API key/cookie/JWT), authorization, CORS, rate limiting, IP filtering, TLS and self-signed certificates, tunnelling (relay, SSH, quick tunnels, Azure Relay, and supervised cloudflared/ngrok/tailscale agents), serving a directory over WebDAV, serving gRPC and gRPC-Web, hosting an MCP server with RFC 9728 OAuth discovery, health checks, OpenTelemetry-shaped metrics and tracing, W3C access logs, request timeouts, output caching and conditional requests, request decompression, antiforgery and browser security headers, reverse-proxy routes, mDNS/Bonjour advertising and discovery, MAUI lifecycle (background/foreground, Android foreground service, network rebinding), and an in-memory test harness. |
| 3 | +description: Generate code using Shiny.Net.HttpServer — a dependency-light, AOT/trim-clean HTTP/1.1, HTTP/2 & HTTP/3 server that runs anywhere .NET runs, including .NET MAUI and native tvOS, where ASP.NET Core cannot. Covers routing, middleware, source-generated typed endpoints, results and JSON, content negotiation with XML/MessagePack/protobuf formatters in both directions, static files and Blazor WASM, uploads/downloads, WebSockets, SSE, sessions, OpenAPI, authentication (Basic/API key/cookie/JWT), authorization, CORS, rate limiting, IP filtering, TLS and self-signed certificates, tunnelling (relay, SSH, quick tunnels, Azure Relay, and supervised cloudflared/ngrok/tailscale agents), serving a directory over WebDAV, serving gRPC and gRPC-Web, hosting an MCP server with RFC 9728 OAuth discovery, health checks, OpenTelemetry-shaped metrics and tracing, W3C access logs, request timeouts, output caching and conditional requests, request decompression, antiforgery and browser security headers, a reverse proxy with destination clusters, load balancing, health checks, session affinity, transforms, WebSocket forwarding and IConfiguration-driven routes, mDNS/Bonjour advertising and discovery, MAUI lifecycle (background/foreground, Android foreground service, network rebinding), and an in-memory test harness. |
4 | 4 | auto_invoke: true |
5 | 5 | triggers: |
6 | 6 | - Shiny.Net.HttpServer |
@@ -106,6 +106,23 @@ triggers: |
106 | 106 | - ValidateAntiforgery |
107 | 107 | - MapProxy |
108 | 108 | - ProxyOptions |
| 109 | +- MapReverseProxy |
| 110 | +- AddReverseProxy |
| 111 | +- reverse proxy |
| 112 | +- YARP |
| 113 | +- ProxyCluster |
| 114 | +- ProxyDestination |
| 115 | +- LoadBalancingPolicy |
| 116 | +- ILoadBalancingPolicy |
| 117 | +- SessionAffinityMode |
| 118 | +- TransformBuilder |
| 119 | +- HttpForwarder |
| 120 | +- ReverseProxyRuntime |
| 121 | +- ReverseProxyConfiguration |
| 122 | +- ForwardUpgrades |
| 123 | +- load balancing |
| 124 | +- health checks for destinations |
| 125 | +- Shiny.Net.HttpServer.Proxy |
109 | 126 | - AddHttpServerAdvertisement |
110 | 127 | - IHttpServerAdvertiser |
111 | 128 | - AddHttpServerLocator |
@@ -296,6 +313,7 @@ must hold that line, or it fails on a trimmed device build. |
296 | 313 | ```bash |
297 | 314 | dotnet add package Shiny.Net.HttpServer # the server + the typed-endpoint generator |
298 | 315 | dotnet add package Shiny.Net.HttpServer.Jwt # JWT auth |
| 316 | +dotnet add package Shiny.Net.HttpServer.Proxy # reverse proxy: clusters, LB, health, transforms |
299 | 317 | dotnet add package Shiny.Net.HttpServer.Ssh # SSH + quick tunnels |
300 | 318 | dotnet add package Shiny.Net.HttpServer.AzureRelay # Azure Relay tunnel (NOT AOT-clean) |
301 | 319 | dotnet add package Shiny.Net.HttpServer.Mcp # Model Context Protocol transport |
@@ -1020,14 +1038,74 @@ var tokens = ctx.GetRequiredService<IAntiforgery>().GetTokens(ctx); |
1020 | 1038 |
|
1021 | 1039 | ## Proxying to another server |
1022 | 1040 |
|
| 1041 | +Needs `Shiny.Net.HttpServer.Proxy` and `using Shiny.Net.HttpServer.Proxy;` — it is **not** in the core |
| 1042 | +package. This is tier 2 (routes): a proxy route is an ordinary route with a generated handler, so |
| 1043 | +middleware, authentication and rate limiting apply to it exactly as they do to anything else. |
| 1044 | + |
| 1045 | +**One destination** — reach for this first, and only escalate when the ask names more than one: |
| 1046 | + |
1023 | 1047 | ```csharp |
1024 | 1048 | app.MapProxy("/api/{*path}", "https://api.example.com"); |
1025 | 1049 | app.MapProxy("/printer/{*path}", "http://192.168.1.50", o => o.RewriteHost = false); |
1026 | 1050 | ``` |
1027 | 1051 |
|
1028 | 1052 | Bodies stream both ways, `X-Forwarded-*` describe the original caller, an unreachable upstream is a |
1029 | | -**502** and one that will not answer is a **504**. A protocol upgrade is not forwarded — a WebSocket |
1030 | | -through this route will not work. |
| 1053 | +**502**, one that will not answer is a **504**, and a WebSocket (or any HTTP/1.1 upgrade) is forwarded |
| 1054 | +end to end — `o.ForwardUpgrades = false` turns that off. |
| 1055 | + |
| 1056 | +**A cluster** when there is more than one instance behind the route: |
| 1057 | + |
| 1058 | +```csharp |
| 1059 | +app.MapProxy("/api/{*path}", cluster => |
| 1060 | +{ |
| 1061 | + cluster.AddDestination("a", "https://a.internal"); |
| 1062 | + cluster.AddDestination("b", "https://b.internal"); |
| 1063 | + |
| 1064 | + cluster.LoadBalancing = LoadBalancingPolicy.PowerOfTwoChoices; // default; also RoundRobin, |
| 1065 | + // LeastRequests, Random, First |
| 1066 | + cluster.HealthCheck.Active.Enabled = true; // off by default — probes cost battery |
| 1067 | + cluster.HealthCheck.Active.Path = "/health"; |
| 1068 | + cluster.SessionAffinity.Mode = SessionAffinityMode.Cookie; // or Header |
| 1069 | +}); |
| 1070 | +``` |
| 1071 | + |
| 1072 | +Passive health (on for a cluster, off for the single-destination call) takes a destination out after |
| 1073 | +`FailureThreshold` consecutive transport failures for `ReactivationPeriod`. When every destination is |
| 1074 | +out, the route answers **503**. |
| 1075 | + |
| 1076 | +**Transforms** instead of `BeforeSend`/`AfterReceive` whenever the change is a path, query or header: |
| 1077 | + |
| 1078 | +```csharp |
| 1079 | +cluster.Transforms |
| 1080 | + .RemovePathPrefix("/api") |
| 1081 | + .SetQueryValue("tenant", "acme") |
| 1082 | + .SetRequestHeader("X-Api-Key", key) |
| 1083 | + .RemoveResponseHeader("Server"); |
| 1084 | +``` |
| 1085 | + |
| 1086 | +**From configuration** when the ask is routes that change without a rebuild. The shape matches YARP's, |
| 1087 | +including `{**rest}` catch-alls, and it is parsed by hand rather than reflection-bound, so it stays |
| 1088 | +AOT-clean: |
| 1089 | + |
| 1090 | +```csharp |
| 1091 | +builder.AddReverseProxy(configuration.GetSection("ReverseProxy")); |
| 1092 | +// no container: var proxy = app.MapReverseProxy(configuration.GetSection("ReverseProxy")); |
| 1093 | +``` |
| 1094 | + |
| 1095 | +```json |
| 1096 | +{ "ReverseProxy": { |
| 1097 | + "Routes": { "api": { "ClusterId": "backend", "Match": { "Path": "/api/{**rest}" }, |
| 1098 | + "Transforms": [ { "PathRemovePrefix": "/api" } ] } }, |
| 1099 | + "Clusters": { "backend": { "LoadBalancingPolicy": "LeastRequests", |
| 1100 | + "Destinations": { "d1": { "Address": "https://a.internal" } } } } } } |
| 1101 | +``` |
| 1102 | + |
| 1103 | +Reload is applied to the running server: routes swap atomically, clusters keep their health state, and |
| 1104 | +routes mapped in code are untouched. |
| 1105 | + |
| 1106 | +- **Do not put a proxy on a tunnel without authentication and rate limiting in front of it**, and keep |
| 1107 | + the destination fixed rather than reading it from the request. |
| 1108 | +- `HttpForwarder.ForwardAsync(ctx, "http://…")` forwards from inside a hand-written handler. |
1031 | 1109 |
|
1032 | 1110 | ## Tunnelling |
1033 | 1111 |
|
|
0 commit comments