using Microsoft.AspNetCore.Server.Kestrel.Core; namespace ZB.MOM.WW.ScadaBridge.Host; /// /// Kestrel configuration for a Site node: the h2c gRPC listener, the HTTP/1.1 metrics listener, and /// the HTTP/2 keepalive that governs both. /// /// /// Extracted from Program.cs's Site branch so the keepalive settings are assertable — the /// existing Host wiring tests re-declare their own ConfigureKestrel lambda and therefore /// prove nothing about the one that ships. /// internal static class SiteKestrelSetup { /// /// How long an HTTP/2 connection may sit idle before Kestrel sends a keepalive PING. /// /// /// /// Kestrel's default is — no server-initiated pings at all — and /// every long-lived stream this port carries is legitimately silent for long stretches: the /// LocalDb replication sync stream on a converged pair, and a SiteStreamService /// subscription for an idle site. A hard crash or network partition of the peer therefore leaves /// this node holding a half-open stream that will never produce another byte and never fail /// either. For LocalDb sync that is the expensive one: the handler sits inside its single-stream /// guard, so the rebooted peer's reconnects are refused while this node still reports itself /// replication-connected. /// /// /// 60s costs one frame per idle minute per connection and bounds detection at roughly /// + . Pings are /// ordinary HTTP/2 frames, so this is unrelated to the listener being h2c rather than TLS. /// /// internal static readonly TimeSpan Http2KeepAlivePingDelay = TimeSpan.FromSeconds(60); /// How long Kestrel waits for a keepalive PING ack before closing the connection. internal static readonly TimeSpan Http2KeepAlivePingTimeout = TimeSpan.FromSeconds(20); /// /// Binds the site node's two listeners and applies the server-wide HTTP/2 keepalive. /// /// /// The keepalive lives on Limits, which is per-server rather than per-listener, so it /// governs the gRPC port and the metrics port alike. That is the intent: the metrics listener /// also speaks HTTP/2, and a scrape connection has nothing to lose from a ping. /// /// The Kestrel server options to configure. /// Port for the HTTP/2 gRPC listener. /// Port for the metrics listener. public static void Configure(KestrelServerOptions options, int grpcPort, int metricsPort) { options.Limits.Http2.KeepAlivePingDelay = Http2KeepAlivePingDelay; options.Limits.Http2.KeepAlivePingTimeout = Http2KeepAlivePingTimeout; options.ListenAnyIP(grpcPort, listenOptions => { listenOptions.Protocols = HttpProtocols.Http2; }); // Dedicated HTTP/1.1 (and HTTP/2) listener for the Prometheus /metrics // scrape endpoint, reachable by an HTTP/1.1 scraper. options.ListenAnyIP(metricsPort, listenOptions => { listenOptions.Protocols = HttpProtocols.Http1AndHttp2; }); } }