如何让你的.NET WebAPI程序支持HTTP3?

简介: 如何让你的.NET WebAPI程序支持HTTP3?

下面我将总结构建Http3的经验,以Token Gateway的项目为例,请注意使用Http3之前你需要知道它的限制,

Windows

  • Windows 11 版本 22000 或更高版本/Windows Server 2022。
  • TLS 1.3 或更高版本的连接。

Linux

  • 已安装 libmsquic 包。

实现讲解

首先我们需要拉取我们的代码

git clone https://gitee.com/hejiale010426/Gateway.git
cd Gateway

然后我们打开Program.cs

#region FreeSql类型转换

Utils.TypeHandlers.TryAdd(typeof(Dictionary<string, string>), new StringJsonHandler<Dictionary<string, string>>());
Utils.TypeHandlers.TryAdd(typeof(RouteMatchEntity), new StringJsonHandler<RouteMatchEntity>());
Utils.TypeHandlers.TryAdd(typeof(List<DestinationsEntity>), new StringJsonHandler<List<DestinationsEntity>>());
Utils.TypeHandlers.TryAdd(typeof(string[]), new StringJsonHandler<string[]>());

#endregion

var builder = WebApplication.CreateBuilder(args);

builder.Configuration.GetSection(nameof(JwtOptions))
    .Get<JwtOptions>();

builder.WebHost.UseKestrel(options =>
{
   
   
    // 配置多个域名证书
    options.ConfigureHttpsDefaults(adapterOptions =>
    {
   
   
        adapterOptions.ServerCertificateSelector = (_, name) =>
        {
   
   
            // 从Certificate服务中获取
            if (string.IsNullOrEmpty(name) ||
                !CertificateService.CertificateEntityDict.TryGetValue(name, out var certificate)) return new X509Certificate2();

            var path = Path.Combine("/data/", certificate.Path);

            if (File.Exists(path)) return new X509Certificate2(path, certificate.Password);

            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine($"证书文件不存在:{path}");
            Console.ResetColor();
            throw new Exception($"证书文件不存在:{path}");
        };
    });
});

builder.WebHost.ConfigureKestrel(kestrel =>
{
   
   
    kestrel.Limits.MaxRequestBodySize = null;

    kestrel.ListenAnyIP(8081, portOptions =>
    {
   
   
        portOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
        portOptions.UseHttps();
    });

    kestrel.ListenAnyIP(8080, portOptions =>
    {
   
   
        portOptions.Protocols = HttpProtocols.Http1AndHttp2;
    });
});

#region Jwt

builder.Services
    .AddAuthorization()
    .AddJwtBearerAuthentication();

#endregion

builder.Services.Configure<KestrelServerOptions>(options =>
{
   
   
    options.Limits.MaxRequestBodySize = int.MaxValue; 
});

builder.Services.Configure<FormOptions>(x =>
{
   
   
    x.ValueLengthLimit = int.MaxValue;
    x.MultipartBodyLengthLimit = int.MaxValue; // if don't set default value is: 128 MB
    x.MultipartHeadersLengthLimit = int.MaxValue;
});

builder.Services.ConfigureHttpJsonOptions(options =>
{
   
   
    options.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
    options.SerializerOptions.Converters.Add(new JsonDateTimeConverter());
});

builder.Services.AddHostedService<GatewayBackgroundService>();

builder.Services.AddCors(options =>
{
   
   
    options.AddPolicy("AllowAll",
        builder => builder
            .SetIsOriginAllowed(_ => true)
            .AllowAnyMethod()
            .AllowAnyHeader()
            .AllowCredentials());
});

builder.Configuration.GetSection(nameof(RequestOptions)).Get<RequestOptions>();
builder.Services.AddMemoryCache();

builder.Services.AddSingleton<RequestLogMiddleware>();
builder.Services.AddSingleton<StaticFileProxyMiddleware>();

builder.Services.AddSingleton<RequestLogService>();
builder.Services.AddSingleton<GatewayService>();
builder.Services.AddSingleton<CertificateService>();
builder.Services.AddSingleton<FileStorageService>();
builder.Services.AddSingleton<StaticFileProxyService>();
builder.Services.AddSingleton<TestService>();
builder.Services.AddSingleton<SettingService>();
builder.Services.AddSingleton<AuthorityService>();

builder.Services.AddSingleton<IContentTypeProvider, FileExtensionContentTypeProvider>();

builder.Services.AddSingleton<IFreeSql>(_ =>
{
   
   
    var directory = new DirectoryInfo("/data");
    if (!directory.Exists)
    {
   
   
        directory.Create();
    }

    return new FreeSqlBuilder()
        .UseConnectionString(DataType.Sqlite, builder.Configuration.GetConnectionString("DefaultConnection"))
        .UseMonitorCommand(cmd => Console.WriteLine($"Sql:{cmd.CommandText}")) //监听SQL语句
        .UseAutoSyncStructure(true) //自动同步实体结构到数据库,FreeSql不会扫描程序集,只有CRUD时才会生成表。
        .Build();
});

// 使用内存加载配置 
builder.Services.AddReverseProxy()
    .LoadFromMemory(GatewayService.Routes, GatewayService.Clusters);

var app = builder.Build();

app.UseCors("AllowAll");

app.UseMiddleware<RequestLogMiddleware>();
app.UseMiddleware<StaticFileProxyMiddleware>();

// 配置MiniApis服务
app.MapRequestLog();
app.MapStaticFileProxy();
app.MapFileStorage();
app.MapGateway();
app.MapAuthority();
app.MapCertificate();
app.MapSetting();

app.UseAuthentication();
app.UseAuthorization();

app.MapReverseProxy();

await app.RunAsync();

上面是完整的代码,我们不过多讲解,只讲解HTTP3需要哪些配置

首先,我们的Gateway支持动态加载证书,而HTTP3是强制使用证书的,我们在这里提供了动态配置HTTP3的实现。

builder.WebHost.UseKestrel(options =>
{
   
   
    // 配置多个域名证书
    options.ConfigureHttpsDefaults(adapterOptions =>
    {
   
   
        adapterOptions.ServerCertificateSelector = (_, name) =>
        {
   
   
            // 从Certificate服务中获取
            if (string.IsNullOrEmpty(name) ||
                !CertificateService.CertificateEntityDict.TryGetValue(name, out var certificate)) return new X509Certificate2();

            var path = Path.Combine("/data/", certificate.Path);

            if (File.Exists(path)) return new X509Certificate2(path, certificate.Password);

            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine($"证书文件不存在:{path}");
            Console.ResetColor();
            throw new Exception($"证书文件不存在:{path}");
        };
    });
});

上面配置好了证书,下面我们配置启用HTTP3,下面我们对于容器会监听俩个端口8080,8081,8080是Http端口,所以不需要开启HTTP3,我们在监听8081的时候修改了协议为HttpProtocols.Http1AndHttp2AndHttp3,然后portOptions.UseHttps()强制使用HTTPS,Http1AndHttp2AndHttp3是自动支持多个协议,如果HTTP3不支持则会降级支持HTTP2如果HTTP2不支持则降级支持HTTP1,由于浏览器不确定你是否支持HTTP3所以会先请求一个HTTP2或HTTP1协议的请求,如果支持的话框架会自动给响应头返回一个Alt-Svc的值。

builder.WebHost.ConfigureKestrel(kestrel =>
{
   
   
    kestrel.Limits.MaxRequestBodySize = null;

    kestrel.ListenAnyIP(8081, portOptions =>
    {
   
   
        portOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
        portOptions.UseHttps();
    });

    kestrel.ListenAnyIP(8080, portOptions =>
    {
   
   
        portOptions.Protocols = HttpProtocols.Http1AndHttp2;
    });
});

上面俩个配置完成以后我们修改我们的Dockerfile,由于微软提供的默认的镜像是不提供libmsquic,所以我们需要自己写一个Dockerfile,打开我们Gateway项目中的Dockerfile,并且添加libmsquic的构建流程

FROM mcr.microsoft.com/dotnet/aspnet:8.0.1-bookworm-slim-amd64 AS base
USER root
RUN  apt update \
    && apt-get install -y --no-install-recommends curl \
    && curl -sSL -O https://packages.microsoft.com/config/debian/12/packages-microsoft-prod.deb \
    && dpkg -i packages-microsoft-prod.deb \
    && rm packages-microsoft-prod.deb \
    && apt-get update \
    && apt-get install -y libmsquic \
    && apt-get purge -y --auto-remove wget && apt-get clean && rm -rf /var/lib/apt/lists/*
WORKDIR /app
EXPOSE 8080
EXPOSE 8081

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["src/Gateway/Gateway.csproj", "src/Gateway/"]
RUN dotnet restore "./src/Gateway/Gateway.csproj"
COPY . .
WORKDIR "/src/src/Gateway"
RUN dotnet build "./Gateway.csproj" -c $BUILD_CONFIGURATION -o /app/build

FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./Gateway.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Gateway.dll"]

在构建镜像的时候需要使用root权限,否则可能导致权限不足构建失败,上面完成了我们本地的镜像构建和.NET Core的HTTP3的启用,然后需要我们构建好了镜像就可以在服务器中跑一个容器了,在运行容器的时候还会有一个坑,下面我们来慢慢讲解,

部署服务

打开我们的服务器使用Linux服务器打开,下面是我们的Gateway的一个Compose版本,由于Docker端口监听默认使用的是tcp,所以我们需要监听俩个协议,因为HTTP3是基于UDP实现的,这也是坑之一,还有如果登录失败可能是映射目录权限不够创建Sqlite文件失败导致。

services:
  gateway-api:
    image: registry.token-ai.cn:8300/gateway
    restart: always
    environment:
      USER: root
      PASS: Aa010426.
    ports:
      - 8080:8080
      - 8081:8081/udp
      - 8081:8081/tcp
    volumes:
      - ./data:/data/

  gateway-web:
    image: registry.cn-shenzhen.aliyuncs.com/tokengo/gateway-web
    restart: always
    privileged: true
    environment:
      api_url: http://这里是你上面的Gateway-api能在浏览器本地请求的地址:8200/
    ports:
      - 1000:80

然后指向我们的sudo docker-compose up -d

指向完成以后我们打开我们的gateway-web的界面,并且登录进去,如果你没有设置环境变量的话默认密码是rootAa010426.

打开我们的代理设置,添加一个集群:

打开路由,点击添加路由,

打开证书管理,点击添加证书:

将我们的证书上传以后点击右上角的刷新缓存,则会生效,还需要注意将我们的域名解析到服务器当中。上面我们用的是gitea.token-ai.cn,注意的是自签证书似乎不能使用。上面操作完成以后点击我们右上角的刷新缓存,然后访问我们的https://gitea.token-ai.cn:8081,然后打开浏览器的F12,我们可以看到我们的,我们的协议除了第一个都是h3协议,这是因为第一个请求是不确定你是否支持h3所以发起一个h1或h2的协议然后,如果你的响应头响应了Alt-Svc则会下次请求使用h3,

还需要注意的是,Alt-Svc:h3=":8081"; ma=86400的8081是前端访问的端口,这个是需要和访问端口一致,如果不一致也不会使用h3。

注意事项

某些浏览器不一定支持所以需要先确认浏览器是否开启QUIC

还需要确认服务器防火墙是否开启UDP

然后根据上面的文档一步一步来即可,或者可以加群询问群主。

结尾

来着token的分享

开源地址:https://gitee.com/hejiale010426/Gateway
github: https://github.com/239573049/Gateway
技术交流群:737776595

目录
相关文章
|
6天前
|
Ubuntu Linux Shell
(已解决)Linux环境—bash: wget: command not found; Docker pull报错Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled
(已成功解决)Linux环境报错—bash: wget: command not found;常见Linux发行版本,Linux中yum、rpm、apt-get、wget的区别;Docker pull报错Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled
123 68
(已解决)Linux环境—bash: wget: command not found; Docker pull报错Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled
|
2天前
|
JSON 数据格式
.net HTTP请求类封装
`HttpRequestHelper` 是一个用于简化 HTTP 请求的辅助类,支持发送 GET 和 POST 请求。它使用 `HttpClient` 发起请求,并通过 `Newtonsoft.Json` 处理 JSON 数据。示例展示了如何使用该类发送请求并处理响应。注意事项包括:简单的错误处理、需安装 `Newtonsoft.Json` 依赖,以及建议重用 `HttpClient` 实例以优化性能。
42 2
|
27天前
|
开发框架 .NET 程序员
驾驭Autofac,ASP.NET WebApi实现依赖注入详细步骤总结
Autofac 是一个轻量级的依赖注入框架,专门为 .NET 应用程序量身定做,它就像是你代码中的 "魔法师",用它来管理对象的生命周期,让你的代码更加模块化、易于测试和维护
驾驭Autofac,ASP.NET WebApi实现依赖注入详细步骤总结
|
20天前
|
算法 Java 测试技术
Benchmark.NET:让 C# 测试程序性能变得既酷又简单
Benchmark.NET是一款专为 .NET 平台设计的性能基准测试框架,它可以帮助你测量代码的执行时间、内存使用情况等性能指标。它就像是你代码的 "健身教练",帮助你找到瓶颈,优化性能,让你的应用跑得更快、更稳!希望这个小教程能让你在追求高性能的路上越走越远,享受编程带来的无限乐趣!
70 13
|
3月前
|
开发框架 监控 前端开发
在 ASP.NET Core Web API 中使用操作筛选器统一处理通用操作
【9月更文挑战第27天】操作筛选器是ASP.NET Core MVC和Web API中的一种过滤器,可在操作方法执行前后运行代码,适用于日志记录、性能监控和验证等场景。通过实现`IActionFilter`接口的`OnActionExecuting`和`OnActionExecuted`方法,可以统一处理日志、验证及异常。创建并注册自定义筛选器类,能提升代码的可维护性和复用性。
|
2月前
|
XML 存储 安全
C#开发的程序如何良好的防止反编译被破解?ConfuserEx .NET混淆工具使用介绍
C#开发的程序如何良好的防止反编译被破解?ConfuserEx .NET混淆工具使用介绍
116 0
|
3月前
|
Ubuntu 持续交付 API
如何使用 dotnet pack 打包 .NET 跨平台程序集?
`dotnet pack` 是 .NET Core 的 NuGet 包打包工具,用于将代码打包成 NuGet 包。通过命令 `dotnet pack` 可生成 `.nupkg` 文件。使用 `--include-symbols` 和 `--include-source` 选项可分别创建包含调试符号和源文件的包。默认情况下,`dotnet pack` 会先构建项目,可通过 `--no-build` 跳过构建。此外,还可以使用 `--output` 指定输出目录、`-c` 设置配置等。示例展示了创建类库项目并打包的过程。更多详情及命令选项,请参考官方文档。
246 11
|
2月前
|
API
使用`System.Net.WebClient`类发送HTTP请求来调用阿里云短信API
使用`System.Net.WebClient`类发送HTTP请求来调用阿里云短信API
41 0
|
3月前
|
存储 运维
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
|
3月前
|
自然语言处理 C# 图形学
使用dnSpyEx对.NET Core程序集进行反编译、编辑和调试
使用dnSpyEx对.NET Core程序集进行反编译、编辑和调试