写点什么

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

  • 2024-01-25
    福建
  • 本文字数:4973 字

    阅读完需:约 16 分钟

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


Windows


  • Windows 11 版本 22000 或更高版本/Windows Server 2022。


  • TLS 1.3 或更高版本的连接。


Linux


  • 已安装 libmsquic 包。


实现讲解


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


git clone https://gitee.com/hejiale010426/Gateway.gitcd 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 baseUSER rootRUN  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 /appEXPOSE 8080EXPOSE 8081
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS buildARG BUILD_CONFIGURATION=ReleaseWORKDIR /srcCOPY ["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 publishARG BUILD_CONFIGURATION=ReleaseRUN dotnet publish "./Gateway.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS finalWORKDIR /appCOPY --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


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


文章转载自:tokengo

原文链接:https://www.cnblogs.com/hejiale010426/p/17985452

体验地址:http://www.jnpfsoft.com/?from=001

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
如何让你的.NET WebAPI程序支持HTTP3?_Web_不在线第一只蜗牛_InfoQ写作社区