< Summary - Kestrun — Combined Coverage

Information
Class: Kestrun.Hosting.KestrunHttpMiddlewareExtensions
Assembly: Kestrun
File(s): /home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Hosting/KestrunHttpMiddlewareExtensions.cs
Tag: Kestrun/Kestrun@2d87023b37eb91155071c91dd3d6a2eeb3004705
Line coverage
55%
Covered lines: 88
Uncovered lines: 71
Coverable lines: 159
Total lines: 403
Line coverage: 55.3%
Branch coverage
61%
Covered branches: 42
Total branches: 68
Branch coverage: 61.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 08/26/2025 - 01:25:22 Line coverage: 67.8% (59/87) Branch coverage: 39.4% (15/38) Total lines: 253 Tag: Kestrun/Kestrun@07f821172e5dc3657f1be7e6818f18d6721cf38a09/12/2025 - 21:57:57 Line coverage: 33.1% (59/178) Branch coverage: 27.7% (15/54) Total lines: 403 Tag: Kestrun/Kestrun@7f3035804f966a691bd6936a199a8086730a784509/14/2025 - 21:23:16 Line coverage: 35.5% (59/166) Branch coverage: 31.2% (15/48) Total lines: 375 Tag: Kestrun/Kestrun@c9d2f0b3dd164d7dc0dc2407a9f006293d92422310/13/2025 - 16:52:37 Line coverage: 58.4% (135/231) Branch coverage: 54% (53/98) Total lines: 597 Tag: Kestrun/Kestrun@10d476bee71c71ad215bb8ab59f219887b5b4a5e10/17/2025 - 15:48:30 Line coverage: 55.3% (88/159) Branch coverage: 61.7% (42/68) Total lines: 403 Tag: Kestrun/Kestrun@b8199aff869a847b75e185d0527ba45e04a43d86 08/26/2025 - 01:25:22 Line coverage: 67.8% (59/87) Branch coverage: 39.4% (15/38) Total lines: 253 Tag: Kestrun/Kestrun@07f821172e5dc3657f1be7e6818f18d6721cf38a09/12/2025 - 21:57:57 Line coverage: 33.1% (59/178) Branch coverage: 27.7% (15/54) Total lines: 403 Tag: Kestrun/Kestrun@7f3035804f966a691bd6936a199a8086730a784509/14/2025 - 21:23:16 Line coverage: 35.5% (59/166) Branch coverage: 31.2% (15/48) Total lines: 375 Tag: Kestrun/Kestrun@c9d2f0b3dd164d7dc0dc2407a9f006293d92422310/13/2025 - 16:52:37 Line coverage: 58.4% (135/231) Branch coverage: 54% (53/98) Total lines: 597 Tag: Kestrun/Kestrun@10d476bee71c71ad215bb8ab59f219887b5b4a5e10/17/2025 - 15:48:30 Line coverage: 55.3% (88/159) Branch coverage: 61.7% (42/68) Total lines: 403 Tag: Kestrun/Kestrun@b8199aff869a847b75e185d0527ba45e04a43d86

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
AddCommonAccessLog(...)100%210%
AddCommonAccessLog(...)100%66100%
AddResponseCompression(...)50%8660%
AddResponseCompression(...)50%4488.88%
AddResponseCaching(...)0%620%
ValidateCachingInput(...)100%44100%
RegisterCachingServices(...)0%2280%
ApplyCacheHeaders(...)90%212088.88%
CreateCachingMiddleware(...)0%4215%
AddResponseCaching(...)100%210%
AddHttpsRedirection(...)50%10653.84%
AddHttpsRedirection(...)25%4471.42%
AddHsts(...)50%4485.71%
AddHsts(...)37.5%24836.84%

File(s)

/home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Hosting/KestrunHttpMiddlewareExtensions.cs

#LineLine coverage
 1using Kestrun.Middleware;
 2using Microsoft.AspNetCore.ResponseCaching;
 3using Microsoft.AspNetCore.ResponseCompression;
 4using Microsoft.Net.Http.Headers;
 5using Serilog.Events;
 6using Microsoft.Extensions.DependencyInjection.Extensions;
 7using Microsoft.AspNetCore.HttpsPolicy;
 8
 9namespace Kestrun.Hosting;
 10
 11/// <summary>
 12/// Provides extension methods for configuring common HTTP middleware in Kestrun.
 13/// </summary>
 14public static class KestrunHttpMiddlewareExtensions
 15{
 16    /// <summary>
 17    /// Adds Apache-style common access logging using a configured <see cref="CommonAccessLogOptions"/> instance.
 18    /// </summary>
 19    /// <param name="host">The <see cref="KestrunHost"/> instance to configure.</param>
 20    /// <param name="configure">Optional pre-configured <see cref="CommonAccessLogOptions"/> instance.</param>
 21    /// <returns>The configured <see cref="KestrunHost"/> instance.</returns>
 22    public static KestrunHost AddCommonAccessLog(this KestrunHost host, CommonAccessLogOptions configure)
 23    {
 024        return host.AddCommonAccessLog(opts =>
 025        {
 026            opts.Level = configure.Level;
 027            opts.IncludeQueryString = configure.IncludeQueryString;
 028            opts.IncludeProtocol = configure.IncludeProtocol;
 029            opts.IncludeElapsedMilliseconds = configure.IncludeElapsedMilliseconds;
 030            opts.UseUtcTimestamp = configure.UseUtcTimestamp;
 031            opts.TimestampFormat = configure.TimestampFormat;
 032            opts.ClientAddressHeader = configure.ClientAddressHeader;
 033            opts.TimeProvider = configure.TimeProvider;
 034            opts.Logger = configure.Logger;
 035        });
 36    }
 37
 38    /// <summary>
 39    /// Adds Apache-style common access logging using <see cref="CommonAccessLogMiddleware"/>.
 40    /// </summary>
 41    /// <param name="host">The <see cref="KestrunHost"/> instance to configure.</param>
 42    /// <param name="configure">Optional delegate to configure <see cref="CommonAccessLogOptions"/>.</param>
 43    /// <returns>The configured <see cref="KestrunHost"/> instance.</returns>
 44    public static KestrunHost AddCommonAccessLog(this KestrunHost host, Action<CommonAccessLogOptions>? configure = null
 45    {
 146        if (host.Logger.IsEnabled(LogEventLevel.Debug))
 47        {
 148            host.Logger.Debug(
 149                "Adding common access log middleware (custom configuration supplied: {HasConfig})",
 150                configure != null);
 51        }
 52
 153        _ = host.AddService(services =>
 154        {
 155            // Ensure a Serilog.ILogger is available for middleware constructor injection.
 156            // We don't overwrite a user-provided registration.
 157            services.TryAddSingleton(_ => host.Logger);
 158
 159            var builder = services.AddOptions<CommonAccessLogOptions>();
 160            if (configure != null)
 161            {
 162                _ = builder.Configure(configure);
 163            }
 264        });
 65
 266        return host.Use(app => app.UseMiddleware<CommonAccessLogMiddleware>());
 67    }
 68
 69
 70
 71    /// <summary>
 72    /// Adds response compression to the application.
 73    /// This overload allows you to specify configuration options.
 74    /// </summary>
 75    /// <param name="host">The KestrunHost instance to configure.</param>
 76    /// <param name="options">The configuration options for response compression.</param>
 77    /// <returns>The current KestrunHost instance.</returns>
 78    public static KestrunHost AddResponseCompression(this KestrunHost host, ResponseCompressionOptions? options)
 79    {
 280        if (host.Logger.IsEnabled(LogEventLevel.Debug))
 81        {
 082            host.Logger.Debug("Adding response compression with options: {@Options}", options);
 83        }
 84
 285        if (options == null)
 86        {
 187            return host.AddResponseCompression(); // no options, use defaults
 88        }
 89
 90        // delegate shim – re‑use the existing pipeline
 191        return host.AddResponseCompression(o =>
 192        {
 093            o.EnableForHttps = options.EnableForHttps;
 094            o.MimeTypes = options.MimeTypes;
 095            o.ExcludedMimeTypes = options.ExcludedMimeTypes;
 196            // copy provider lists, levels, etc. if you expose them
 097            foreach (var p in options.Providers)
 198            {
 099                o.Providers.Add(p);
 1100            }
 1101        });
 102    }
 103
 104    /// <summary>
 105    /// Adds response compression to the application.
 106    /// This overload allows you to specify configuration options.
 107    /// </summary>
 108    /// <param name="host">The KestrunHost instance to configure.</param>
 109    /// <param name="cfg">The configuration options for response compression.</param>
 110    /// <returns>The current KestrunHost instance.</returns>
 111    public static KestrunHost AddResponseCompression(this KestrunHost host, Action<ResponseCompressionOptions>? cfg = nu
 112    {
 5113        if (host.Logger.IsEnabled(LogEventLevel.Debug))
 114        {
 0115            host.Logger.Debug("Adding response compression with configuration: {HasConfig}", cfg != null);
 116        }
 117        // Service side
 5118        _ = host.AddService(services =>
 5119        {
 1120            _ = cfg == null ? services.AddResponseCompression() : services.AddResponseCompression(cfg);
 5121            // replace the default provider with our opt-out decorator
 1122            _ = services.AddSingleton<IResponseCompressionProvider, Compression.KestrunResponseCompressionProvider>();
 6123        });
 124
 125        // Middleware side
 5126        return host.Use(app => app.UseResponseCompression());
 127    }
 128
 129    /// <summary>
 130    /// Adds response caching to the application.
 131    /// This overload allows you to specify configuration options.
 132    /// </summary>
 133    /// <param name="host">The KestrunHost instance to configure.</param>
 134    /// <param name="options">The configuration options for response caching.</param>
 135    /// <param name="cacheControl">
 136    /// Optional default Cache-Control to apply (only if the response didn't set one).
 137    /// </param>
 138    public static KestrunHost AddResponseCaching(this KestrunHost host, ResponseCachingOptions options, CacheControlHead
 139    {
 0140        if (host.Logger.IsEnabled(LogEventLevel.Debug))
 141        {
 0142            host.Logger.Debug("Adding response caching with options: {@Options}", options);
 143        }
 144
 145        // delegate shim – re‑use the existing pipeline
 0146        return host.AddResponseCaching(o =>
 0147        {
 0148            o.SizeLimit = options.SizeLimit;
 0149            o.MaximumBodySize = options.MaximumBodySize;
 0150            o.UseCaseSensitivePaths = options.UseCaseSensitivePaths;
 0151        }, cacheControl);
 152    }
 153
 154    /// <summary>
 155    /// Validates inputs and performs initial logging for response caching configuration.
 156    /// </summary>
 157    /// <param name="host">The KestrunHost instance to configure.</param>
 158    /// <param name="cfg">Optional configuration for response caching.</param>
 159    /// <param name="cacheControl">Optional default Cache-Control to apply.</param>
 160    internal static void ValidateCachingInput(KestrunHost host, Action<ResponseCachingOptions>? cfg, CacheControlHeaderV
 161    {
 2162        if (host.Logger.IsEnabled(LogEventLevel.Debug))
 163        {
 1164            host.Logger.Debug("Adding response caching with Action<ResponseCachingOptions>{HasConfig} and Cache-Control:
 165        }
 166
 167        // Remember the default Cache-Control if provided
 2168        if (cacheControl is not null)
 169        {
 1170            host.Logger.Information("Setting default Cache-Control: {CacheControl}", cacheControl.ToString());
 171            // Save for reference
 1172            host.DefaultCacheControl = cacheControl;
 173        }
 2174    }
 175
 176    /// <summary>
 177    /// Registers response caching services with the dependency injection container.
 178    /// </summary>
 179    /// <param name="host">The KestrunHost instance to configure.</param>
 180    /// <param name="cfg">Optional configuration for response caching.</param>
 181    internal static void RegisterCachingServices(KestrunHost host, Action<ResponseCachingOptions>? cfg)
 182    {
 2183        _ = host.AddService(services =>
 2184        {
 0185            _ = cfg == null ? services.AddResponseCaching() : services.AddResponseCaching(cfg);
 2186        });
 2187    }
 188
 189    /// <summary>
 190    /// Applies cache control headers to the HTTP response if conditions are met.
 191    /// </summary>
 192    /// <param name="context">The HTTP context.</param>
 193    /// <param name="cacheControl">The cache control header value to apply.</param>
 194    /// <param name="logger">The Serilog logger instance for debugging.</param>
 195    /// <returns>True if headers were applied, false otherwise.</returns>
 196    internal static bool ApplyCacheHeaders(HttpContext context, CacheControlHeaderValue? cacheControl, Serilog.ILogger l
 197    {
 198        // Gate: only for successful cacheable responses on GET/HEAD
 6199        var method = context.Request.Method;
 6200        if (!(HttpMethods.IsGet(method) || HttpMethods.IsHead(method)))
 201        {
 1202            return false;
 203        }
 204
 5205        var status = context.Response.StatusCode;
 5206        if (status is < 200 or >= 300)
 207        {
 1208            return false;
 209        }
 210
 211        // ResponseCaching won't cache if Set-Cookie is present; don't add headers in that case
 4212        if (context.Response.Headers.ContainsKey(HeaderNames.SetCookie))
 213        {
 1214            return false;
 215        }
 216
 217        // Only apply default Cache-Control if none was set and caller provided one
 3218        if (cacheControl is not null)
 219        {
 2220            context.Response.Headers.CacheControl = cacheControl.ToString();
 221
 222            // If you expect compression variability elsewhere, add Vary only if absent
 2223            if (!context.Response.Headers.ContainsKey(HeaderNames.Vary))
 224            {
 2225                context.Response.Headers.Append(HeaderNames.Vary, "Accept-Encoding");
 226            }
 227
 2228            if (logger.IsEnabled(LogEventLevel.Debug))
 229            {
 0230                logger.Debug("Applied default Cache-Control: {CacheControl}", cacheControl.ToString());
 231            }
 2232            return true;
 233        }
 234        else
 235        {
 1236            if (logger.IsEnabled(LogEventLevel.Debug))
 237            {
 0238                logger.Debug("No default cache Control provided; skipping.");
 239            }
 1240            return false;
 241        }
 242    }
 243
 244    /// <summary>
 245    /// Creates the caching middleware that applies cache headers.
 246    /// </summary>
 247    /// <param name="host">The KestrunHost instance.</param>
 248    /// <param name="cacheControl">Optional cache control header value.</param>
 249    /// <returns>The configured middleware action.</returns>
 250    internal static Action<IApplicationBuilder> CreateCachingMiddleware(KestrunHost host, CacheControlHeaderValue? cache
 251    {
 1252        return app =>
 1253        {
 0254            _ = app.UseResponseCaching();
 0255            _ = app.Use(async (context, next) =>
 0256            {
 0257                try
 0258                {
 0259                    _ = ApplyCacheHeaders(context, cacheControl, host.Logger);
 0260                }
 0261                catch (Exception ex)
 0262                {
 0263                    // Never let caching decoration break the response
 0264                    host.Logger.Warning(ex, "Failed to apply default cache headers.");
 0265                }
 0266                finally
 0267                {
 0268                    await next(context);
 0269                }
 0270            });
 1271        };
 272    }
 273
 274    /// <summary>
 275    /// Adds response caching to the application.
 276    /// This overload allows you to specify a configuration delegate.
 277    /// </summary>
 278    /// <param name="host">The KestrunHost instance to configure.</param>
 279    /// <param name="cfg">Optional configuration for response caching.</param>
 280    /// <param name="cacheControl">Optional default Cache-Control to apply (only if the response didn't set one).</param
 281    /// <returns> The updated KestrunHost instance. </returns>
 282    public static KestrunHost AddResponseCaching(this KestrunHost host, Action<ResponseCachingOptions>? cfg = null,
 283        CacheControlHeaderValue? cacheControl = null)
 284    {
 0285        ValidateCachingInput(host, cfg, cacheControl);
 0286        RegisterCachingServices(host, cfg);
 0287        return host.Use(CreateCachingMiddleware(host, cacheControl));
 288    }
 289
 290
 291    /// <summary>
 292    /// Adds HTTPS redirection to the application using the specified <see cref="HttpsRedirectionOptions"/>.
 293    /// </summary>
 294    /// <param name="host">The KestrunHost instance to configure.</param>
 295    /// <param name="cfg">The HTTPS redirection options.</param>
 296    /// <returns>The updated KestrunHost instance.</returns>
 297    public static KestrunHost AddHttpsRedirection(this KestrunHost host, HttpsRedirectionOptions cfg)
 298    {
 2299        if (host.Logger.IsEnabled(LogEventLevel.Debug))
 300        {
 0301            host.Logger.Debug("Adding HTTPS redirection with configuration: {@Config}", cfg);
 302        }
 303
 2304        if (cfg == null)
 305        {
 1306            return host.AddHttpsRedirection();   // fallback to parameterless overload
 307        }
 308
 1309        _ = host.AddService(services =>
 1310        {
 0311            _ = services.AddHttpsRedirection(opts =>
 0312            {
 0313                opts.RedirectStatusCode = cfg.RedirectStatusCode;
 0314                opts.HttpsPort = cfg.HttpsPort;
 0315            });
 1316        });
 317
 1318        return host.Use(app => app.UseHttpsRedirection());
 319    }
 320
 321    /// <summary>
 322    /// Adds HTTPS redirection to the application using the specified configuration delegate.
 323    /// </summary>
 324    /// <param name="host">The KestrunHost instance to configure.</param>
 325    /// <param name="cfg">The configuration delegate for HTTPS redirection options.</param>
 326    /// <returns>The updated KestrunHost instance.</returns>
 327    public static KestrunHost AddHttpsRedirection(this KestrunHost host, Action<HttpsRedirectionOptions>? cfg = null)
 328    {
 3329        if (host.Logger.IsEnabled(LogEventLevel.Debug))
 330        {
 0331            host.Logger.Debug("Adding HTTPS redirection with configuration: {HasConfig}", cfg != null);
 332        }
 333
 334        // Register the HTTPS redirection service
 3335        _ = host.AddService(services =>
 3336            {
 0337                _ = services.AddHttpsRedirection(cfg ?? (_ => { })); // Always pass a delegate
 3338            });
 339
 340        // Apply the middleware
 3341        return host.Use(app => app.UseHttpsRedirection());
 342    }
 343
 344
 345    /// <summary>
 346    /// Adds HSTS to the application using the specified <see cref="HstsOptions"/>.
 347    /// </summary>
 348    /// <param name="host">The KestrunHost instance to configure.</param>
 349    /// <param name="opts">The delegate for configuring HSTS options.</param>
 350    /// <returns>The updated KestrunHost instance.</returns>
 351    public static KestrunHost AddHsts(this KestrunHost host, Action<HstsOptions>? opts = null)
 352    {
 4353        if (host.Logger.IsEnabled(LogEventLevel.Debug))
 354        {
 1355            host.Logger.Debug("Adding HSTS with configuration: {HasConfig}", opts != null);
 356        }
 357
 358        // Register the HSTS service
 4359        _ = host.AddService(services =>
 4360            {
 0361                _ = services.AddHsts(opts ?? (_ => { })); // Always pass a delegate
 4362            });
 363
 364        // Apply the middleware
 4365        return host.Use(app => app.UseHsts());
 366    }
 367
 368    /// <summary>
 369    /// Adds HSTS to the application using the specified <see cref="HstsOptions"/>.
 370    /// </summary>
 371    /// <param name="host">The KestrunHost instance to configure.</param>
 372    /// <param name="opts">The HSTS options.</param>
 373    /// <returns>The updated KestrunHost instance.</returns>
 374    public static KestrunHost AddHsts(this KestrunHost host, HstsOptions opts)
 375    {
 4376        if (host.Logger.IsEnabled(LogEventLevel.Debug))
 377        {
 0378            host.Logger.Debug("Adding HSTS with configuration: {@Config}", opts);
 379        }
 380
 4381        if (opts == null)
 382        {
 1383            return host.AddHsts();   // fallback to parameterless overload
 384        }
 385
 3386        _ = host.AddService(services =>
 3387        {
 0388            _ = services.AddHsts(o =>
 0389            {
 0390                o.Preload = opts.Preload;
 0391                o.IncludeSubDomains = opts.IncludeSubDomains;
 0392                o.MaxAge = opts.MaxAge;
 0393                o.ExcludedHosts.Clear();
 0394                foreach (var h in opts.ExcludedHosts)
 0395                {
 0396                    o.ExcludedHosts.Add(h);
 0397                }
 0398            });
 3399        });
 400
 3401        return host.Use(app => app.UseHsts());
 402    }
 403}

Methods/Properties

AddCommonAccessLog(Kestrun.Hosting.KestrunHost,Kestrun.Middleware.CommonAccessLogOptions)
AddCommonAccessLog(Kestrun.Hosting.KestrunHost,System.Action`1<Kestrun.Middleware.CommonAccessLogOptions>)
AddResponseCompression(Kestrun.Hosting.KestrunHost,Microsoft.AspNetCore.ResponseCompression.ResponseCompressionOptions)
AddResponseCompression(Kestrun.Hosting.KestrunHost,System.Action`1<Microsoft.AspNetCore.ResponseCompression.ResponseCompressionOptions>)
AddResponseCaching(Kestrun.Hosting.KestrunHost,Microsoft.AspNetCore.ResponseCaching.ResponseCachingOptions,Microsoft.Net.Http.Headers.CacheControlHeaderValue)
ValidateCachingInput(Kestrun.Hosting.KestrunHost,System.Action`1<Microsoft.AspNetCore.ResponseCaching.ResponseCachingOptions>,Microsoft.Net.Http.Headers.CacheControlHeaderValue)
RegisterCachingServices(Kestrun.Hosting.KestrunHost,System.Action`1<Microsoft.AspNetCore.ResponseCaching.ResponseCachingOptions>)
ApplyCacheHeaders(Microsoft.AspNetCore.Http.HttpContext,Microsoft.Net.Http.Headers.CacheControlHeaderValue,Serilog.ILogger)
CreateCachingMiddleware(Kestrun.Hosting.KestrunHost,Microsoft.Net.Http.Headers.CacheControlHeaderValue)
AddResponseCaching(Kestrun.Hosting.KestrunHost,System.Action`1<Microsoft.AspNetCore.ResponseCaching.ResponseCachingOptions>,Microsoft.Net.Http.Headers.CacheControlHeaderValue)
AddHttpsRedirection(Kestrun.Hosting.KestrunHost,Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionOptions)
AddHttpsRedirection(Kestrun.Hosting.KestrunHost,System.Action`1<Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionOptions>)
AddHsts(Kestrun.Hosting.KestrunHost,System.Action`1<Microsoft.AspNetCore.HttpsPolicy.HstsOptions>)
AddHsts(Kestrun.Hosting.KestrunHost,Microsoft.AspNetCore.HttpsPolicy.HstsOptions)