Nguon: Microsoft Learn · .NET 8.0

Triển khai web server HTTP.sys trong ASP.NET Core

Nguồn: HTTP.sys web server implementation in ASP.NET Core

Tổng quan

HTTP.sys là một web server cho ASP.NET Core chỉ chạy trên Windows. Đây là giải pháp thay thế cho Kestrel và cung cấp một số tính năng mà Kestrel không có.

Quan trọng: HTTP.sys không tương thích với ASP.NET Core Module và không thể sử dụng với IIS hoặc IIS Express.

Các tính năng được hỗ trợ

HTTP.sys hỗ trợ:

Phiên bản Windows được hỗ trợ:

Khi nào nên dùng HTTP.sys

HTTP.sys hữu ích cho các triển khai khi:

HTTP.sys là công nghệ lâu đời, được tôi luyện để chống nhiều loại tấn công và cung cấp độ bền, bảo mật và khả năng mở rộng. Bản thân IIS chạy như một HTTP listener trên nền HTTP.sys.

Hỗ trợ HTTP/2

HTTP/2 được kích hoạt cho các ứng dụng ASP.NET Core khi đáp ứng các yêu cầu sau:

Nếu kết nối HTTP/2 được thiết lập, HttpRequest.Protocol sẽ báo cáo HTTP/2. HTTP/2 được bật mặc định và tự động fallback về HTTP/1.1 nếu không thể thiết lập.

Hỗ trợ HTTP/3

HTTP/3 được kích hoạt khi:

HTTP/3 được phát hiện thông qua nâng cấp từ HTTP/1.1 hoặc HTTP/2 qua header alt-svc. Http.Sys không tự động thêm header alt-svc; ứng dụng cần thêm thủ công:

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

Xác thực Kernel Mode với Kerberos

HTTP.sys ủy quyền xác thực kernel mode với giao thức Kerberos. Xác thực user mode không được hỗ trợ với Kerberos và HTTP.sys. Machine account phải được dùng để giải mã Kerberos token/ticket lấy từ Active Directory. Đăng ký Service Principal Name (SPN) cho host, không phải cho user của ứng dụng.

Bật Channel Binding Token (CBT) Hardening

Channel binding tokens (CBT) liên kết Windows Authentication với kênh TLS cơ bản, giúp giảm thiểu các cuộc tấn công NTLM relay và man-in-the-middle. Để bật CBT hardening, thiết lập AppContext switch Microsoft.AspNetCore.Server.HttpSys.EnableCBTHardening thành true.

Bật switch trong file runtimeconfig.template.json của dự án:

json
{
  "configProperties": {
    "Microsoft.AspNetCore.Server.HttpSys.EnableCBTHardening": true
  }
}

Hoặc thiết lập lập trình trước khi build host trong Program.cs:

csharp
AppContext.SetSwitch("Microsoft.AspNetCore.Server.HttpSys.EnableCBTHardening", true);

Cảnh báo: CBT hardening mặc định bị tắt. Bật tính năng này có thể khiến Windows Authentication thất bại với các client hoặc proxy không hỗ trợ channel binding. Hãy kiểm tra kỹ trong môi trường của bạn trước khi bật trên production.

Hỗ trợ Kernel-Mode Response Buffering

Khối lượng lớn các ghi nhỏ với độ trễ cao có thể ảnh hưởng đáng kể đến hiệu suất HTTP.sys do thiếu Pipe buffer. HTTP.sys hỗ trợ response buffering. Bật buffering bằng cách đặt HttpSysOptions.EnableKernelResponseBuffering thành true.

Response buffering nên được bật cho các ứng dụng thực hiện synchronous I/O, hoặc asynchronous I/O với không quá một lần ghi đang chờ xử lý tại một thời điểm.

Cảnh báo: Ứng dụng sử dụng asynchronous I/O với nhiều hơn một lần ghi đang chờ không nên dùng flag này, vì có thể dẫn đến CPU và memory usage cao hơn.

Cách dùng HTTP.sys

Cấu hình ứng dụng ASP.NET Core để dùng HTTP.sys

Gọi extension method UseHttpSys khi build host, chỉ định các HttpSysOptions cần thiết:

csharp
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();

Cấu hình HTTP.sys bổ sung được xử lý thông qua registry settings.

Tùy chỉnh Security Descriptors

Request queue trong HTTP.sys là cấu trúc kernel-level lưu tạm thời các HTTP request đến. Quản lý quyền truy cập vào request queue bằng thuộc tính RequestQueueSecurityDescriptor trên HttpSysOptions. Đặt nó thành một instance GenericSecurityDescriptor khi cấu hình HTTP.sys server.

Ví dụ cho phép tất cả authenticated users nhưng từ chối guests:

csharp
using System.Security.AccessControl;
using System.Security.Principal;
using Microsoft.AspNetCore.Server.HttpSys;

var securityDescriptor = new CommonSecurityDescriptor(isContainer: false, isDS: false, sddlForm: string.Empty);

var dacl = new DiscretionaryAcl(isContainer: false, isDS: false, capacity: 2);
dacl.AddAccess(
    AccessControlType.Allow,
    new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null),
    -1,
    InheritanceFlags.None,
    PropagationFlags.None
);
dacl.AddAccess(
    AccessControlType.Deny,
    new SecurityIdentifier(WellKnownSidType.BuiltinGuestsSid, null),
    -1,
    InheritanceFlags.None,
    PropagationFlags.None
);

securityDescriptor.DiscretionaryAcl = dacl;

var builder = WebApplication.CreateBuilder();
builder.WebHost.UseHttpSys(options =>
{
    options.RequestQueueSecurityDescriptor = securityDescriptor;
});

Thuộc tính RequestQueueSecurityDescriptor chỉ áp dụng khi tạo request queue mới và không ảnh hưởng đến các request queue hiện có.

MaxRequestBodySize

Kích thước tối đa cho phép của bất kỳ request body nào tính bằng bytes. Khi đặt thành null, kích thước request body tối đa là không giới hạn. Giới hạn này không ảnh hưởng đến upgraded connections, vốn luôn không giới hạn.

Ghi đè giới hạn trong ứng dụng ASP.NET Core MVC cho một action cụ thể bằng RequestSizeLimitAttribute:

csharp
[RequestSizeLimit(100000000)]
public IActionResult MyActionMethod()

Ghi đè MaxRequestBodySize per-request sử dụng IHttpMaxRequestBodySizeFeature:

csharp
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);
});

Cấu hình Windows Server

  1. Mở cổng tường lửa: Dùng Windows Firewall hoặc cmdlet PowerShell New-NetFirewallRule.
  2. Triển khai Azure VM: Mở cổng trong Network Security Group.
  3. Lấy và cài đặt chứng chỉ X.509: Tạo self-signed certificates dùng cmdlet PowerShell New-SelfSignedCertificate. Cài đặt chứng chỉ trong store Local Machine > Personal.
  4. Cài đặt .NET runtime: Với framework-dependent deployments, cài đặt .NET hoặc .NET Framework cần thiết.
  5. Cấu hình URLs và ports:
csharp
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();

HTTP.sys nhận biết hai loại wildcard trong URL prefix:

Cảnh báo: Top-level wildcard bindings (http://*:80/http://+:80) KHÔNG NÊN dùng vì tạo ra lỗ hổng bảo mật. Hãy dùng tên host hoặc địa chỉ IP cụ thể.

  1. Đăng ký URL prefix trước: Dùng công cụ netsh.exe:
console
netsh http add urlacl url=https://10.0.0.4:443/ user=Users
  1. Đăng ký chứng chỉ X.509:
console
netsh http add sslcert ipport=10.0.0.4:443 certhash=b66ee04419d4ee37464ab8785ff02449980eae10 appid="{00001111-aaaa-2222-bbbb-3333cccc4444}"
  1. Chạy ứng dụng: Không cần quyền Administrator khi bind vào localhost dùng HTTP (không phải HTTPS) với port lớn hơn 1024.

Proxy Server và Load Balancer

Với các ứng dụng hosted bởi HTTP.sys tương tác với requests từ Internet hoặc mạng nội bộ, có thể cần cấu hình bổ sung khi hosting phía sau proxy servers và load balancers. Xem Configure ASP.NET Core to work with proxy servers and load balancers.

Lấy thông tin Timing chi tiết với IHttpSysRequestTimingFeature

IHttpSysRequestTimingFeature cung cấp thông tin timing chi tiết cho requests:

csharp
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();

TryGetTimestamp

Lấy timestamp cho loại timing được cung cấp:

csharp
var timingType = HttpSysRequestTimingType.RequestRoutingEnd;

if (feature.TryGetTimestamp(timingType, out var timestamp))
{
    logger.LogInformation("Timestamp {timingType}: {timestamp}",
                                      timingType, timestamp);
}
else
{
    logger.LogInformation("Timestamp {timingType}: không có sẵn cho request hiện tại", timingType);
}

TryGetElapsedTime

Trả về thời gian đã trôi qua giữa hai timing được chỉ định:

csharp
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);
}

Các tính năng HTTP/2 nâng cao hỗ trợ gRPC

HTTP.sys hỗ trợ các tính năng HTTP/2 bổ sung cho gRPC, bao gồm hỗ trợ response trailers và gửi reset frames.

Yêu cầu để chạy gRPC với HTTP.sys:

Trailers

HTTP Trailers tương tự HTTP Headers, nhưng được gửi sau response body. Với IIS và HTTP.sys, chỉ HTTP/2 response trailers được hỗ trợ.

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

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

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

Reset

Reset cho phép server reset một HTTP/2 request với mã lỗi cụ thể:

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

Ví dụ trên chỉ định mã lỗi INTERNAL_ERROR. Xem HTTP/2 specification error code section để biết thêm.

Quản lý Memory Pool

ASP.NET Core cung cấp IMemoryPoolFactory tích hợp. Memory pools tự động giải phóng khi ứng dụng idle hoặc tải thấp.

csharp
public class MyBackgroundService : BackgroundService
{
    private readonly MemoryPool<byte> _memoryPool;

    public MyBackgroundService(IMemoryPoolFactory<byte> factory)
    {
        _memoryPool = factory.Create();
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                await Task.Delay(20, stoppingToken);
                var rented = _memoryPool.Rent(100);
                rented.Dispose();
            }
            catch (OperationCanceledException)
            {
                return;
            }
        }
    }
}

Để dùng custom memory pool factory, tạo class implement IMemoryPoolFactory và đăng ký với dependency injection:

csharp
services.AddSingleton<IMemoryPoolFactory<byte>, CustomMemoryPoolFactory>();

public class CustomMemoryPoolFactory : IMemoryPoolFactory<byte>
{
    public MemoryPool<byte> Create()
    {
        return MemoryPool<byte>.Shared;
    }
}