Servers - HTTP.sys

In this article

Important HTTP.sys isn't compatible with the ASP.NET Core Module and can't be used with IIS or IIS Express.

When to use HTTP.sys

HTTP.sys communicates directly with the Internet!

HTTP.sys communicates directly with the internal network!

HTTP/2 support

HTTP/3 support

app.Use((context, next) =>
{
    context.Response.Headers.AltSvc = "h3=\":443\"";
    return next(context);
});

Kernel mode authentication with Kerberos

Support for kernel-mode response buffering

How to use HTTP.sys

Configure the ASP.NET Core app to use HTTP.sys

using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.HttpSys;

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.UseHttpSys(options =>
{
    options.AllowSynchronousIO = false;
    options.Authentication.Schemes = AuthenticationSchemes.None;
    options.Authentication.AllowAnonymous = true;
    options.MaxConnections = null;
    options.MaxRequestBodySize = 30_000_000;
    options.UrlPrefixes.Add("http://localhost:5005");
});

builder.Services.AddRazorPages();

var app = builder.Build();
[RequestSizeLimit(100000000)]
public IActionResult MyActionMethod()
app.Use((context, next) =>
{
    context.Features.GetRequiredFeature<IHttpMaxRequestBodySizeFeature>()
                                             .MaxRequestBodySize = 10 * 1024;

    var server = context.RequestServices
        .GetRequiredService<IServer>();
    var serverAddressesFeature = server.Features
                                 .GetRequiredFeature<IServerAddressesFeature>();

    var addresses = string.Join(", ", serverAddressesFeature.Addresses);

    var loggerFactory = context.RequestServices
        .GetRequiredService<ILoggerFactory>();
    var logger = loggerFactory.CreateLogger("Sample");

    logger.LogInformation("Addresses: {addresses}", addresses);

    return next(context);
});

Select console app profile!

Configure Windows Server

If the app is a self-contained deployment, the app includes the runtime in its deployment. No framework installation is required on the server.

The following code example shows how to use UrlPrefixes with the server's local IP address 10.0.0.4 on port 443:

An advantage of UrlPrefixes is that an error message is generated immediately for improperly formatted prefixes. The settings in UrlPrefixes override UseUrls/urls/ASPNETCORE_URLS settings. Therefore, an advantage of UseUrls, urls, and the ASPNETCORE_URLS environment variable is that it's easier to switch between Kestrel and HTTP.sys. HTTP.sys recognizes two types of wild cards in URL prefixes:

For more information, see UrlPrefix Strings.

Warning Top-level wildcard bindings (http://*:80/ and http://+:80) should not be used. Top-level wildcard bindings create app security vulnerabilities. This applies to both strong and weak wildcards. Use explicit host names or IP addresses rather than wildcards. Subdomain wildcard binding (for example, *.mysub.com) isn't a security risk if you control the entire parent domain (as opposed to *.com, which is vulnerable). For more information, see RFC 9110: Section 7.2: Host and :authority.

Apps and containers are often given only a port to listen on, like port 80, without additional constraints like host or path. HTTP_PORTS and HTTPS_PORTS are config keys that specify the listening ports for the Kestrel and HTTP.sys servers. These keys may be specified as environment variables defined with the DOTNET_ or ASPNETCORE_ prefixes, or specified directly through any other config input, such as appsettings.json. Each is a semicolon-delimited list of port values, as shown in the following example: ASPNETCORE_HTTP_PORTS=80;8080 ASPNETCORE_HTTPS_PORTS=443;8081

The preceding example is shorthand for the following configuration, which specifies the scheme (HTTP or HTTPS) and any host or IP. ASPNETCORE_URLS=http://:80/;http://:8080/;https://:443/;https://:8081/

The HTTP_PORTS and HTTPS_PORTS configuration keys are lower priority and are overridden by URLS or values provided directly in code. Certificates still need to be configured separately via server-specific mechanics for HTTPS. These configuration keys are equivalent to top-level wildcard bindings. They're convenient for development and container scenarios, but avoid wildcards when running on a machine that may also host other services.

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.UseHttpSys(options =>
{
    options.UrlPrefixes.Add("https://10.0.0.4:443");
});

builder.Services.AddRazorPages();

var app = builder.Build();

Warning Top-level wildcard bindings (http://*:80/ and http://+:80) should not be used. Top-level wildcard bindings create app security vulnerabilities. This applies to both strong and weak wildcards. Use explicit host names or IP addresses rather than wildcards. Subdomain wildcard binding (for example, *.mysub.com) isn't a security risk if you control the entire parent domain (as opposed to *.com, which is vulnerable). For more information, see RFC 9110: Section 7.2: Host and :authority.

netsh http add urlacl url=<URL> user=<USER>

In the following example, the local IP address of the server is 10.0.0.4:

netsh http add urlacl url=https://10.0.0.4:443/ user=Users

When a URL is registered, the tool responds with URL reservation successfully added. To delete a registered URL, use the delete urlacl command:

netsh http delete urlacl url=<URL>
netsh http add sslcert 
    ipport=10.0.0.4:443 
    certhash=b66ee04419d4ee37464ab8785ff02449980eae10 
    appid="{9412ee86-c21b-4eb8-bd89-f650fbf44931}"

For reference purposes, store the GUID in the app as a package tag:

In the following example:

When a certificate is registered, the tool responds with SSL Certificate successfully added. To delete a certificate registration, use the delete sslcert command:

netsh http delete sslcert ipport=<IP>:<PORT>

Reference documentation for netsh.exe:

netsh http add sslcert ipport=<IP>:<PORT> certhash=<THUMBPRINT> appid="{<GUID>}"
``````9412ee86-c21b-4eb8-bd89-f650fbf44931
<PropertyGroup>
  <PackageTags>9412ee86-c21b-4eb8-bd89-f650fbf44931</PackageTags>
</PropertyGroup>

Browser window showing the app's Index page loaded!

Proxy server and load balancer scenarios

Get detailed timing information with IHttpSysRequestTimingFeature

using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.HttpSys;

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.UseHttpSys();

var app = builder.Build();

app.Use((context, next) =>
{
    var feature = context.Features.GetRequiredFeature<IHttpSysRequestTimingFeature>();
    
    var loggerFactory = context.RequestServices.GetRequiredService<ILoggerFactory>();
    var logger = loggerFactory.CreateLogger("Sample");

    var timestamps = feature.Timestamps;

    for (var i = 0; i < timestamps.Length; i++)
    {
        var timestamp = timestamps[i];
        var timingType = (HttpSysRequestTimingType)i;

        logger.LogInformation("Timestamp {timingType}: {timestamp}",
                                          timingType, timestamp);
    }

    return next(context);
});

app.MapGet("/", () => Results.Ok());

app.Run();
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.HttpSys;
var builder = WebApplication.CreateBuilder(args);

builder.WebHost.UseHttpSys();

var app = builder.Build();

app.Use((context, next) =>
{
    var feature = context.Features.GetRequiredFeature<IHttpSysRequestTimingFeature>();

    var loggerFactory = context.RequestServices.GetRequiredService<ILoggerFactory>();
    var logger = loggerFactory.CreateLogger("Sample");

    var timingType = HttpSysRequestTimingType.RequestRoutingEnd;

    if (feature.TryGetTimestamp(timingType, out var timestamp))
    {
        logger.LogInformation("Timestamp {timingType}: {timestamp}",
                                          timingType, timestamp);
    }
    else
    {
        logger.LogInformation("Timestamp {timingType}: not available for the "
                                           + "current request",    timingType);
    }

    return next(context);
});

app.MapGet("/", () => Results.Ok());

app.Run();
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.HttpSys;

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.UseHttpSys();

var app = builder.Build();

app.Use((context, next) =>
{
    var feature = context.Features.GetRequiredFeature<IHttpSysRequestTimingFeature>();

    var loggerFactory = context.RequestServices.GetRequiredService<ILoggerFactory>();
    var logger = loggerFactory.CreateLogger("Sample");

    var startingTimingType = HttpSysRequestTimingType.RequestRoutingStart;
    var endingTimingType = HttpSysRequestTimingType.RequestRoutingEnd;

    if (feature.TryGetElapsedTime(startingTimingType, endingTimingType, out var elapsed))
    {
        logger.LogInformation(
            "Elapsed time {startingTimingType} to {endingTimingType}: {elapsed}",
            startingTimingType,
            endingTimingType,
            elapsed);
    }
    else
    {
        logger.LogInformation(
            "Elapsed time {startingTimingType} to {endingTimingType}:"
            + " not available for the current request.",
            startingTimingType,
            endingTimingType);
    }

    return next(context);
});

app.MapGet("/", () => Results.Ok());

app.Run();

Advanced HTTP/2 features to support gRPC

Trailers

if (httpContext.Response.SupportsTrailers())
{
    httpContext.Response.DeclareTrailer("trailername");	

    // Write body
    httpContext.Response.WriteAsync("Hello world");

    httpContext.Response.AppendTrailer("trailername", "TrailerValue");
}

Reset

var resetFeature = httpContext.Features.Get<IHttpResetFeature>();
resetFeature.Reset(errorCode: 2);

Tracing

Additional resources

Ref: HTTP.sys web server implementation in ASP.NET Core