1 - YARP 入门
https://microsoft.github.io/reverse-proxy/articles/getting-started.html
YARP 是一个提供核心代理功能的类库,您可以通过添加或替换模块对其进行定制。YARP 目前以 NuGet 软件包和代码片段的形式提供。我们计划在未来提供一个项目模板和预构建的 exe。
YARP 是在 .NET Core 基础架构之上实现的,可在 Windows、Linux 或 MacOS 上使用。开发工作可通过 SDK 和您最喜欢的编辑器(Microsoft Visual Studio 或 Visual Studio Code)完成。
YARP 2.0.0 支持 ASP.NET Core 6.0 及更新版本。您可以从 https://dotnet.microsoft.com/download/dotnet/ 下载 .NET SDK。
备注:这里我下载了 .NET 6.0 for macOS Arm64 版本并安装
创建新项目
使用以下步骤创建的项目的完整版本可在 Minimal YARP Sample 中找到。有关不使用顶层语句的版本,请参阅基本 YARP 示例。
首先,使用命令行创建一个 “empty” ASP.NET Core 应用程序:
dotnet new web -n MyProxy -f net6.0
完成之后的目录结构如下:
ls -ls
8 -rw-r--r-- 1 sky staff 219 Jan 18 21:25 MyProxy.csproj
8 -rw-r--r-- 1 sky staff 135 Jan 18 21:25 Program.cs
0 drwxr-xr-x 3 sky staff 96 Jan 18 21:25 Properties
8 -rw-r--r-- 1 sky staff 127 Jan 18 21:25 appsettings.Development.json
8 -rw-r--r-- 1 sky staff 151 Jan 18 21:25 appsettings.json
0 drwxr-xr-x 7 sky staff 224 Jan 18 21:25 obj
用 vs code 打开这个项目。
修改 MyProxy.csproj
文件,增加内容:
<ItemGroup>
<PackageReference Include="Yarp.ReverseProxy" Version="2.0.0" />
</ItemGroup>
添加 YARP 中间件
更新 Program.cs 以使用 YARP 中间件:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
app.MapReverseProxy();
app.Run();
配置
YARP 的配置在 appsettings.json 文件中定义。详情请参阅配置文件。
也可以通过编程提供配置。请参阅配置提供程序了解详情。
您可以通过查看 RouteConfig 和 ClusterConfig 进一步了解可用的配置选项。
修改 appsettings.json
文件,内容如下:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ReverseProxy": {
"Routes": {
"route1" : {
"ClusterId": "cluster1",
"Match": {
"Path": "{**catch-all}"
}
}
},
"Clusters": {
"cluster1": {
"Destinations": {
"destination1": {
"Address": "https://example.com/"
}
}
}
}
}
}
运行项目
使用在样本目录中调用的 dotnet run
dotnet run
Building...
info: Yarp.ReverseProxy.Configuration.ConfigProvider.ConfigurationConfigProvider[1]
Loading proxy data from config.
warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
No XML encryptor configured. Key {2e19e480-715d-4990-a187-831a2dbc72fc} may be persisted to storage in unencrypted form.
warn: Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServer[5]
The application is trying to access the ASP.NET Core developer certificate key. A prompt might appear to ask for permission to access the key. When that happens, select 'Always Allow' to grant 'dotnet' access to the certificate key in the future.
info: Microsoft.Hosting.Lifetime[14]
Now listening on: https://localhost:7243
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5260
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: /Users/sky/work/code/yarp/MyProxy/
这时通过浏览器访问 http://localhost:5260
可以得到和直接访问 https://example.com/ 一样的页面。反向代理成功!
2 - 支持的运行时
https://microsoft.github.io/reverse-proxy/articles/runtimes.html
YARP 2.0+ 支持 ASP.NET Core 6.0 及更新版本。您可以从 https://dotnet.microsoft.com/download/dotnet/ 下载 .NET SDK。有关具体的版本支持,请参阅 版本。
YARP 正在利用新的 .NET 功能和优化功能。这意味着如果您运行的是以前版本的 ASP.NET,某些功能可能不可用。
相关的 6.0 运行时改进
- HTTP/3 - 支持入站和出站连接(预览版)。
- 分布式跟踪 .NET 6.0 内置了可配置的支持,YARP 可利用这些支持启用更多开箱即用的方案。
- Http.sys Delegation–内核级 ASP.NET Core 6 功能允许将请求转移到不同的进程。
- UseHttpLogging–包含一个额外的中间件组件,可用于提供有关请求和响应的更多详细信息。
- 动态 HTTP/2 窗口缩放 - 提高高延迟连接上的 HTTP/2 下载速度。
- 非验证标头 - 通过使用非验证 HttpClient 标头提高性能。
相关的 7.0 运行时改进
- HTTP/3 - 支持入站和出站连接(稳定)。
- HttpClient 响应流的零字节读取 - 减少了内存使用量。
- 减少头分配 - 减少内存使用量。
- Kestrel Http/2 性能改进 - 改进一个连接上多个请求的争用和吞吐量。
备注:新发布的8.0没有提及。
3 - 配置文件
https://microsoft.github.io/reverse-proxy/articles/config-files.html
简介
反向代理可以使用 Microsoft.Extensions 中的 IConfiguration 抽象从文件加载路由和群集的配置。这里给出的示例使用的是 JSON,但任何 IConfiguration 源文件都可以使用。如果源文件发生变化,也无需重启代理即可更新配置。
加载配置
要从 IConfiguration 加载代理配置,请在 Program.cs 中添加以下代码:
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
// Add the reverse proxy capability to the server
builder.Services.AddReverseProxy()
// Initialize the reverse proxy from the "ReverseProxy" section of configuration
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
// Register the reverse proxy routes
app.MapReverseProxy();
app.Run();
注:有关中间件顺序的详细信息,请参阅此处。
可使用配置过滤器在加载过程中修改配置。
多个配置源
从 1.1 开始,YARP 支持从多个来源加载代理配置。LoadFromConfig 可以多次调用,引用不同的 IConfiguration 部分,也可以与不同的配置源(如 InMemory)相结合。路由可以引用其他来源的群集。注意,不支持为给定路由或群集合并不同来源的部分配置。
services.AddReverseProxy()
.LoadFromConfig(Configuration.GetSection("ReverseProxy1"))
.LoadFromConfig(Configuration.GetSection("ReverseProxy2"));
或者
services.AddReverseProxy()
.LoadFromMemory(routes, clusters)
.LoadFromConfig(Configuration.GetSection("ReverseProxy"));
配置合约
基于文件的配置由 IProxyConfigProvider 实现动态映射到 Yarp.ReverseProxy.Configuration 名称空间中的类型,在应用程序启动时和每次配置更改时进行转换。
配置结构
配置由上文通过 Configuration.GetSection(“ReverseProxy”) 指定的命名部分组成,并包含路由和群集的子部分。
示例:
{
"ReverseProxy": {
"Routes": {
"route1" : {
"ClusterId": "cluster1",
"Match": {
"Path": "{**catch-all}",
"Hosts" : [ "www.aaaaa.com", "www.bbbbb.com"],
},
}
},
"Clusters": {
"cluster1": {
"Destinations": {
"cluster1/destination1": {
"Address": "https://example.com/"
}
}
}
}
}
}
路由
路由部分是路由匹配及其相关配置的无序集合。路由至少需要以下字段:
- RouteId - 唯一名称
- ClusterId - 指群集部分中的条目名称。
- Match - 包含 Hosts 数组或 Path 模式字符串。Path 是 ASP.NET Core 路由模板,可按此处的说明进行定义。路由匹配基于具有最高优先级的最特定路由,如此处所述。可以使用 order 字段实现显式排序,值越小优先级越高。
可以在每个路由条目上配置 Headers, Authorization, CORS 和其他基于路由的策略。有关其他字段,请参阅 RouteConfig。
代理将应用给定的匹配标准和策略,然后将请求传递给指定的群集。
集群
集群部分是命名集群的无序集合。一个群组主要包含命名的目的地及其地址,其中任何一个目的地都被认为可以处理给定路由的请求。代理将根据路由和群组配置处理请求,以选择目的地。
有关其他字段,请参阅 ClusterConfig。
所有配置属性
{
// 服务器监听的基本 URL,必须独立于下面的路由进行配置
"Urls": "http://localhost:5000;https://localhost:5001",
"Logging": {
"LogLevel": {
"default": "Information",
// 取消注释可从运行时和代理中隐藏诊断信息
// "Microsoft": "Warning",
// "Yarp" : "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"ReverseProxy": {
// 路由告诉代理转发哪些请求
"Routes": {
"minimumroute" : {
// 匹配任何内容并将其路由到 www.example.com
"ClusterId": "minimumcluster",
"Match": {
"Path": "{**catch-all}"
}
},
"allrouteprops" : {
// 匹配 /something/* 和路由到 "allclusterprops"
"ClusterId": "allclusterprops", // 其中一个群集的名称
"Order" : 100, // 数字越小优先级越高
"MaxRequestBodySize" : 1000000, // 以字节为单位。可选择覆盖服务器的限制(默认为 30MB)。设置为 -1 则禁用。
"AuthorizationPolicy" : "Anonymous", // 策略名称或 "Default","Anonymous"
"CorsPolicy":"Default",// 适用于此路由的 CorsPolicy 名称或 "Default","Disable"
"Match": {
"Path": "/something/{**remainder}", // 使用 ASP.NET 语法匹配的路径。
"Hosts" : [ "www.aaaaa.com", "www.bbbbb.com"], // 要匹配的主机名,未指定的为任意主机名
"Methods" : [ "GET", "PUT" ], // 匹配的 HTTP 方法,未指定的为所有方法
"Headers": [// 要匹配的头信息,未指定的为任何头信息
{
"名称": "MyCustomHeader", // 标头名称
"Values": [ "value1", "value2", "another value" ], // 匹配这些值中的任何一个
"Mode": "ExactHeader", // 或 "HeaderPrefix", "Exists" , "Contains", "NotContains", "NotExists"
"IsCaseSensitive": true
}
],
"QueryParameters": [ // 要匹配的查询参数,未指定的为任意参数
{
"名称": "MyQueryParameter", // 查询参数名称
"Values": [ "value1", "value2", "another value" ], // 与这些值中的任意值进行匹配
"模式": "精确",//或 "前缀","存在","包含","不包含"
"IsCaseSensitive": true
}
]
},
"MetaData":{ // 可由自定义扩展使用的键值对列表
"MyName":"MyValue"
},
"Transforms" : [ // 转换列表。有关详细信息,请参阅 "变换 "文章
{
"RequestHeader": "MyHeader",
"Set": "MyValue",
}
]
}
},
// 集群告诉代理转发请求的位置和方式
"Clusters": {
"minimumcluster": {
"Destinations": {
"example.com": {
"Address": "http://www.example.com/"
}
}
},
"allclusterprops": {
"Destinations": {
"first_destination": {
"Address": "https://contoso.com"
},
"another_destination": {
"Address": "https://10.20.30.40",
"Health" : "https://10.20.30.40:12345/test" // 覆盖主动健康检查
}
},
"LoadBalancingPolicy" : "PowerOfTwoChoices", // 可选择 "FirstAlphabetical", "Random", "RoundRobin", "LeastRequests" 或 "FirstAlphabetical", "Random", "RoundRobin", "LeastRequests".
"SessionAffinity": {
Enabled": true, // 默认为 "false
"Policy": "Cookie",//默认,或者 "CustomHeader"(自定义头文件
"FailurePolicy": "Redistribute",//默认,或者 "Return503Error"。
"Settings" : {
"CustomHeaderName": MySessionHeaderName" // 默认为 "X-Yarp-Proxy-Affinity`"。
}
},
"HealthCheck": {
"Active": { // 调用 API 来验证健康状况。
"Enabled": "true",
"Interval": "00:00:10",
"Timeout": "00:00:10",
"Policy": "ConsecutiveFailures",
"Path": "/api/health" // 用于查询健康状态的 API 端点
},
"Passive": { // 根据 HTTP 响应代码禁用目的地
"Enabled": true, // 默认为 false
"Policy" : "TransportFailureRateHealthPolicy", // Required
"ReactivationPeriod" : "00:00:10" // 10s
}
},
"HttpClient" : {用于联系目的地的 HttpClient 实例的配置
"SSLrotocols" : "Tls13",
"DangerousAcceptAnyServerCertificate" : false,
"MaxConnectionsPerServer" : 1024,
"EnableMultipleHttp2Connections" : true,
"RequestHeaderEncoding" : "Latin1", // 如何解释请求头值中的非 ASCII 字符
"ResponseHeaderEncoding" : "Latin1" // 如何解释响应头值中的非 ASCII 字符
},
"HttpRequest" : { // 向目的地发送请求的选项
"ActivityTimeout" : "00:02:00",
"Version" : "2",
"VersionPolicy" : "RequestVersionOrLower",
"AllowResponseBuffering" : "false" (允许响应缓冲)。
},
"MetaData" : { // 自定义键值对
"TransportFailureRateHealthPolicy.RateLimit": "0.5", // 被动健康策略使用
"MyKey" : "MyValue"
}
}
}
}
}
更多信息,请参阅日志配置和 HTTP 客户端配置。
4 - 配置提供者
https://microsoft.github.io/reverse-proxy/articles/config-providers.html
简介
基本 Yarp 示例 显示代理配置是从 appsettings.json 中加载的。不过,代理配置可以通过编程从你选择的源代码中加载。您可以通过提供几个实现 IProxyConfigProvider 和 IProxyConfig 的类来实现这一点。
请参阅 ReverseProxy.Code.Sample 以了解自定义配置提供程序的示例。
可以使用 Configuration Filters 在加载过程中修改配置。
结构体
IProxyConfigProvider 有一个方法 GetConfig()
,该方法应返回一个 IProxyConfig 实例。IProxyConfig 包含当前路由和群集的列表,以及一个 IChangeToken
用来在这些信息过时并需要重新加载时通知代理,这将导致再次调用 GetConfig()
。
路由
路由部分是命名路由的无序集合。路由包含匹配项及其相关配置。路由至少需要以下字段:
- RouteId - 唯一名称
- ClusterId - 指群集部分中的条目名称。
- Match - 包含 Hosts 数组或 Path 模式字符串。Path 是 ASP.NET Core 路由模板,可定义为此处解释。
每个路由条目都可以配置 Headers、Authorization、CORS 和其他基于路由的策略。有关其他字段,请参阅 RouteConfig。
代理将应用给定的匹配标准和策略,然后将请求传递给指定的群集。
群集
群集部分是命名群集的无序集合。一个群集主要包含一组命名的目的地及其地址,其中任何一个目的地都被认为有能力处理给定路由的请求。代理将根据路由和集群配置处理请求,以选择目的地。
有关其他字段,请参阅 ClusterConfig。
内存配置
InMemoryConfigProvider 实现了 IProxyConfigProvider,可通过调用 LoadFromMemory 直接在代码中指定路由和集群。
services.AddReverseProxy().LoadFromMemory(routes, clusters);
要稍后更新配置,请解析服务容器中的 InMemoryConfigProvider
并使用新的路由和集群列表调用 Update
。
httpContext.RequestServices.GetRequiredService<InMemoryConfigProvider>().Update(routes, clusters);
生命周期
启动
应将 IProxyConfigProvider
作为单例在 DI 容器中注册。启动时,代理将解析该实例并调用 GetConfig()
。在第一次调用时,提供程序可以选择
- 如果提供程序因故无法生成有效的代理配置,则抛出异常。这将阻止应用程序启动。
- 在加载配置时同步阻塞。这将阻止应用程序启动,直到有效路由数据可用。
- 或者,它可以选择在后台加载配置时返回一个空的
IProxyConfig
实例。当配置可用时,提供程序需要触发IChangeToken
。
代理会验证给定的配置,如果配置无效,就会产生异常,导致应用程序无法启动。提供程序可通过使用 IConfigValidator 对路由和群集进行预验证,并采取任何其认为适当的措施(如排除无效条目)来避免这种情况。
原子性
一旦通过 GetConfig()
交给代理,提供给代理的配置对象和集合应为只读,不得修改。
重新加载
如果 IChangeToken 支持 ActiveChangeCallbacks,代理处理完初始配置集后,就会使用此令牌注册一个回调。如果提供商不支持回调,则将每 5 分钟轮询一次 HasChanged。
当提供商想向代理提供新配置时,它应该这样做:
重新加载
如果 “IChangeToken” 支持 “ActiveChangeCallbacks”(活动更改回调),代理处理完初始配置集后,就会使用此令牌注册一个回调。如果提供商不支持回调,则将每 5 分钟轮询一次 “HasChanged”。
当提供程序要向代理提供新配置时,它应该:
- 在后台加载该配置。
- 路由和集群对象是不可变的,因此必须为任何新数据创建新实例。
- 不变路由和群集的对象可以重复使用,也可以创建新实例,但会通过差分来检测变化。
- 可选择使用 IConfigValidator 验证配置,然后才从先前的
IProxyConfig
实例向IChangeToken
发送新数据可用的信号。代理会再次调用GetConfig()
来获取新数据。
重新加载配置与首次加载配置有重要区别。
- 新配置将与当前配置进行比较,只有修改过的路由或群集才会被更新。更新将以原子方式应用,只会影响新请求,而不会影响当前正在处理的请求。
- 重载过程中出现的任何错误都将被记录并抑制。应用程序将继续使用上次已知的良好配置。
- 如果
GetConfig()
抛出,代理将无法监听未来的更改,因为IChangeToken
是一次性的。
一旦验证并应用了新配置,代理将使用新的 IChangeToken
注册一个回调。请注意,如果接连发出多个重新加载信号,代理可能会跳过其中一些,并在下一个可用配置准备就绪时立即加载。每个 IProxyConfig
都包含完整的配置状态,因此不会丢失任何内容。
示例
InMemoryConfigProvider](https://github.com/microsoft/reverse-proxy/blob/main/src/ReverseProxy/Configuration/InMemoryConfigProvider.cs) 提供了一个 IProxyConfigProvider
的示例,其中手动加载了路由和群集。