Welcome to YARP - 2.1配置功能 - 配置文件
目录
Welcome to YARP - 1.认识YARP并搭建反向代理服务
- 2.1 - 配置文件(Configuration Files)
- 2.2 - 配置提供者(Configuration Providers)
- 2.3 - 配置过滤器(Configuration Filters)
哈哈哈,第一篇文章还说,只规划了8篇文章,写到配置功能的时候发现东西还是挺多的,还是拆分成小章节来说吧。目前成了10篇了—_—。写之前觉得配置功能应该没什么东西可讲,结果写着写着都想讲一嘴。然后就越写越多,大家看的时候可以选择性的跳过。
介绍
如果有同学不知道YARP
是什么,YARP
有哪些功能的同学请移步第一篇文章Welcome to YARP - 1.认识YARP并搭建反向代理服务。接下来这篇文章主要讲解YARP
的配置功能,包含:配置文件、配置提供程序、配置筛选器。
配置文件(Configuration Files)
YARP
可以使用 Microsoft.Extensions
中的 IConfiguration
从文件加载路由和群集的配置。这就说明了不只是JSON
文件,任何支持 IConfiguration
源都有效。你可以基于IConfiguration
扩展出XML
、YAML
等格式的配置。而且如果源文件发生更改,配置也将更新,无需重新启动代理服务。
1.加载配置
要从 IConfiguration
加载代理配置,请在启动中添加以下代码(LoadFromConfig)
:
builder.Services.AddReverseProxy()//添加ReverseProxy相关服务到DI
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));//从配置文件中加载ReverseProxy的设置
LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))
这段代码会把配置文件中ReverseProxy
节点下的路由(Routes)配置和集群(Clusters)配置都加载到程序中。
2.多个配置源
从 1.1 开始,YARP
支持从多个源加载代理配置。LoadFromConfig
可以多次调用,引用不同的 IConfiguration
节点,也可以与不同的配置源(如InMemory)结合使用。路由可以引用来自其他源的群集。
请注意,对于给定的路由或集群,不支持合并来自不同源的节点配置。
思考1:YARP
是怎么做到可以多配置源或加载不同的配置源呢?(只是使用,可略过此部分)
源码解读:
这时候我们就要扒一扒他的源码了
看源码也是为了看优秀的代码怎么写,学习其设计,其次就是看源码的技巧,怎么一层一层的去解剖、去找你想要的东西。
我们先从入口开始:
builder.Services.AddReverseProxy()//添加ReverseProxy相关服务到DI
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));//从配置文件中加载ReverseProxy的设置
接下来我们在看一下 AddReverseProxy
这个扩展方法
/// <summary>
/// Adds ReverseProxy's services to Dependency Injection.
/// </summary>
public static IReverseProxyBuilder AddReverseProxy(this IServiceCollection services)
{
var builder = new ReverseProxyBuilder(services);
builder
.AddConfigBuilder()
.AddRuntimeStateManagers()
.AddConfigManager()//关键就在这里
.AddSessionAffinityPolicies()
.AddActiveHealthChecks()
.AddPassiveHealthCheck()
.AddLoadBalancingPolicies()
.AddHttpSysDelegation()
.AddDestinationResolver()
.AddProxy();
services.TryAddSingleton<ProxyEndpointFactory>();
services.AddDataProtection();
services.AddAuthorization();
services.AddCors();
services.AddRouting();
return builder;
}
我们看到有一个 AddConfigManager
,这个就是注入 ProxyConfigManager
的,关键代码就在这个类里,我们直接看这个类,代码太多了,我们只看关键性的代码片段
internal sealed class ProxyConfigManager : EndpointDataSource, IProxyStateLookup, IDisposable
{
private static readonly IReadOnlyDictionary<string, ClusterConfig> _emptyClusterDictionary = new ReadOnlyDictionary<string, ClusterConfig>(new Dictionary<string, ClusterConfig>());
private readonly object _syncRoot = new();
private readonly ILogger<ProxyConfigManager> _logger;
private readonly IProxyConfigProvider[] _providers;//关键接口
}
IProxyConfigProvider
接口是获取配置的关键,他里面只有一个 返回类型为 IProxyConfig
的GetConfig
方法。IProxyConfig
里面定义了当前路由和集群的列表,以及一个用于信息过期,并需要通知YARP
重新加载配置的令牌IChangeToken
,而他们就是获取配置的核心,我们甚至可以实现IProxyConfigProvider
接口去自定义一个配置提供者,比如LoadFromRedis()
、LoadFromSqlServer()
完全可以的(等有时间了,我会写个demo示例,先把规划的文章写完了再去搞这个)。
/// <summary>
/// A data source for proxy route and cluster information.
/// </summary>
public interface IProxyConfigProvider
{
/// <summary>
/// Returns the current route and cluster data.
/// </summary>
/// <returns></returns>
IProxyConfig GetConfig();
}
而ProxyConfigManager
类就是定义了 IProxyConfigProvider[]
数组去获取多个 IProxyConfigProvider
实例,然后在调用 GetConfig()
方法去获取每个实例的配置。具体实现在这个类的 InitialLoadAsync
方法里,代码片段如下:
internal async Task<EndpointDataSource> InitialLoadAsync()
{
try
{
var routes = new List<RouteConfig>();
var clusters = new List<ClusterConfig>();
var resolvedConfigs = new List<(int Index, IProxyConfigProvider Provider, ValueTask<IProxyConfig> Config)>(_providers.Length);
//这里循环_providers去调用LoadConfigAsync方法去拿配置,LoadConfigAsync方法内部就是调用的provider.GetConfig()方法获取配置,只不过内部还做了一写其他操作(配置校验等)
for (var i = 0; i < _providers.Length; i++)
{
var provider = _providers[i];
var configLoadTask = LoadConfigAsync(provider, cancellationToken: default);
resolvedConfigs.Add((i, provider, configLoadTask));
}
foreach (var (i, provider, configLoadTask) in resolvedConfigs)
{
var config = await configLoadTask;
_configs[i] = new ConfigState(provider, config);
routes.AddRange(config.Routes ?? Array.Empty<RouteConfig>());
clusters.AddRange(config.Clusters ?? Array.Empty<ClusterConfig>());
}
return this;
}
然后我们在验证下,既然官方说了1.1支持的多配置那么1.0版本这里是不是就不是数组呢?答案是肯定的:
好了现在已经知道是通过多个IProxyConfigProvider
循环去调用每个实例的GetConfig
方法拿配置了。那么IProxyConfigProvider
是接口,他的实例从哪来?哈哈哈,其实这是一个很蠢的问题。那不就是通过 LoadFromXXX
来注入不同的 ConfigProvider
吗?本着既然写了就刨到底的态度,我们还是看一眼吧,哈哈哈
LoadFromConfig
方法是通过配置文件获取配置,注入的是ConfigurationConfigProvider
,而这个类则是继承了IProxyConfigProvider
接口:
/// <summary>
/// Loads routes and endpoints from config.
/// </summary>
public static IReverseProxyBuilder LoadFromConfig(this IReverseProxyBuilder builder, IConfiguration config)
{
if (config is null)
{
throw new ArgumentNullException(nameof(config));
}
builder.Services.AddSingleton<IProxyConfigProvider>(sp =>
{
// This is required because we're capturing the configuration via a closure
return new ConfigurationConfigProvider(sp.GetRequiredService<ILogger<ConfigurationConfigProvider>>(), config);
});
return builder;
}
internal sealed class ConfigurationConfigProvider : IProxyConfigProvider, IDisposable
{
public IProxyConfig GetConfig()
{
// First time load
if (_snapshot is null)
{
_subscription = ChangeToken.OnChange(_configuration.GetReloadToken, UpdateSnapshot);
UpdateSnapshot();
}
return _snapshot;
}
}
LoadFromMemory
也是同样的,注入的是InMemoryConfigProvider
:
/// <summary>
/// Adds an InMemoryConfigProvider
/// </summary>
public static IReverseProxyBuilder LoadFromMemory(this IReverseProxyBuilder builder, IReadOnlyList<RouteConfig> routes, IReadOnlyList<ClusterConfig> clusters)
{
builder.Services.AddSingleton(new InMemoryConfigProvider(routes, clusters));
builder.Services.AddSingleton<IProxyConfigProvider>(s => s.GetRequiredService<InMemoryConfigProvider>());
return builder;
}
public sealed class InMemoryConfigProvider : IProxyConfigProvider
{
// Marked as volatile so that updates are atomic
private volatile InMemoryConfig _config;
/// <summary>
/// Implementation of the IProxyConfigProvider.GetConfig method to supply the current snapshot of configuration
/// </summary>
/// <returns>An immutable snapshot of the current configuration state</returns>
public IProxyConfig GetConfig() => _config;
}
说的有点多了,其实是一个很简单的功能(对于使用者来说),快速入手可以忽略这部分。
3.配置约定(可略过)
基于文件的配置通过 IProxyConfigProvider
实现在应用程序启动时和每次配置更改时进行转换,动态映射到 Yarp.ReverseProxy.Configuration 命名空间中的类型。
说人话就是:Yarp.ReverseProxy.Configuration
这个命名空间下有很多关于YARP
[配置的类]( Namespace Yarp.ReverseProxy.Configuration (microsoft.github.io) ),如ActiveHealthCheckConfig.cs
、ClusterConfig.cs
等等,然后这些类都是通过IProxyConfigProvider
进行映射的,我们拿到配置后会对这些类的属性进行配置映射。
4.配置结构
LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))
这段代码会把配置文件中ReverseProxy
节点下的 Routes
配置节和 Clusters
配置节都加载到程序中。
例:
{
"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/"
}
}
}
}
}
}
5.Routes
路由配置最小集:
路由节点是路由匹配及其相关配置的无序集合。路径至少需要以下字段:
-
(RouteId)路由 ID - 唯一名称 (示例中的
route1
) -
(ClusterId)群集 ID - 指 集群
(Clusters)
节点中 某个集群的名称 (示例中的cluster1
) -
(Match)匹配 - 包含
Hosts
数组或Path
模式字符串。Path
是ASP.NET Core
路由模板,可以按ASP.NET Core
路由模板文档进行定义。路由匹配中越具体的路由匹配优先级越高。也可以使用
Order
字段实现显式排序,值越低优先级越高。
其他配置:
当然你还可以在路由节点下配置以下内容
- AuthorizationPolicy
- 要应用于此路由的授权策略的名称。如果未设置,则仅应用回退策略。设置为
Default
以启用应用程序默认策略的授权。设置为Anonymous
以禁用此路由的所有授权检查。
- 要应用于此路由的授权策略的名称。如果未设置,则仅应用回退策略。设置为
- Headers
- 要匹配的标头(未指定)为任意
- CorsPolicy
- 要应用于此路由的
CorsPolicy
的名称。如果未设置,则不会自动匹配cors
预检请求的路由。设置为Default
以启用具有默认策略的cors
。设置为Disable
以拒绝此路由的cors
请求。
- 要应用于此路由的
还有很多支持的配置这里就不一 一列举了,可以查看官方的 RouteConfig 文档。
思考2:令我好奇的是,我查看了官方的
RouteConfig
文档竟然没有发现关于针对路由限流的?难道限流不是在这里配置?方便的是每页的API文档上有对应的源码链接,点看查看,果然是由限流配置的。只不过要.NET 7.0
才支持这个功能。然后我又打开限流的文档查看,发现文档上也是有配置的。哈哈哈,这里算是来了一波剧透了吧。
6.Clusters
群集节点是命名群集的无序集合。集群主要包含命名目标及地址的集合,其中任何一个都被认为能够处理给定路由的请求。代理将根据路由和群集配置处理请求,以便选择目标。
说人话:YARP
会根据路由节点的集群标识(ClusterId)去集群节点中匹配相应的集群目标地址(Address)。
相关其他字段配置,请参阅 ClusterConfig。
7.所有属性配置(可以暂时略过,后续在具体功能模块会拿出来单独讲)
{
// 代理服务的URL,必须和下面路由定义的Url区分开
"Urls": "http://localhost:5000;https://localhost:5001",
"Logging": {
"LogLevel": {
"Default": "Information",
// 取消注释以对运行时和代理隐藏诊断消息
// "Microsoft": "Warning",
// "Yarp" : "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"ReverseProxy": {
// Routes 告诉代理要转发的请求
"Routes": {
"minimumroute" : {
// 匹配任何内容并将其路由到www.example.com
"ClusterId": "minimumcluster",
"Match": {
"Path": "{**catch-all}"
}
},
"allrouteprops" : {
// 匹配 /something/* 并路由到“allclusterrops”
"ClusterId": "allclusterprops", // 其中一个群集的名称
"Order" : 100, // 数字越低,优先级越高
"MaxRequestBodySize" : 1000000, // 以字节为单位。服务器请求体大小的限制(默认为30MB)。设置为-1可禁用。
"Authorization Policy" : "Anonymous", // 策略名称 "Default", "Anonymous"
"CorsPolicy" : "Default", // 要应用到此路由的CorsPolicy的名称 "Default", "Disable"
"Match": {
"Path": "/something/{**remainder}", // 这里的Path是 ASP.NET Core语法
"Hosts" : [ "www.aaaaa.com", "www.bbbbb.com"], // 要匹配的主机名(未指定)为任意
"Methods" : [ "GET", "PUT" ], // 匹配的HTTP方法, uspecified 是所有
"Headers": [ // 要匹配的标头(未指定)为任意
{
"Name": "MyCustomHeader", // 标头的名称
"Values": [ "value1", "value2", "another value" ],
"Mode": "ExactHeader", // "ExactHeader(完全匹配)","HeaderPrefix(前缀匹配)", "Exists" , "Contains", "NotContains"
"IsCaseSensitive": true
}
],
"QueryParameters": [ // 要匹配的查询参数(未指定)为任意
{
"Name": "MyQueryParameter", // 查询参数名称
"Values": [ "value1", "value2", "another value" ],
"Mode": "Exact", // "Exact" "Prefix", "Exists" , "Contains", "NotContains"
"IsCaseSensitive": true
}
]
},
"MetaData" : { // 可由自定义扩展使用的键值对列表
"MyName" : "MyValue"
},
"Transforms" : [ // 转换列表。有关更多详细信息,请参阅Transforms文章
{
"RequestHeader": "MyHeader",
"Set": "MyValue",
}
]
}
},
// Clusters 告诉代理在哪里以及如何转发请求
"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", // "PowerOfTwoChoices", "FirstAlphabetical", "Random", "RoundRobin", "LeastRequests"
"SessionAffinity": {
"Enabled": true, // 默认 'false'
"Policy": "Cookie", // 默认值"Cookie", 可选 "CustomHeader"
"FailurePolicy": "Redistribute", // 默认值"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",
"ReactivationPeriod" : "00:00:10" // 10s
}
},
"HttpClient" : { // 用于关联指定的HttpClient实例的配置
"SSLProtocols" : "Tls13",
"DangerousAcceptAnyServerCertificate" : false,
"MaxConnectionsPerServer" : 1024,
"EnableMultipleHttp2Connections" : true,
"RequestHeaderEncoding" : "Latin1" // 如何解释标头值中的非ASCII字符
},
"HttpRequest" : { // 将请求发送到目的地的选项
"ActivityTimeout" : "00:02:00",
"Version" : "2",
"VersionPolicy" : "RequestVersionOrLower",
"AllowResponseBuffering" : "false"
},
"MetaData" : {
"TransportFailureRateHealthPolicy.RateLimit": "0.5", // 被动健康策略使用
"MyKey" : "MyValue"
}
}
}
}
}
总结
至此配置文件部分结束,说的有点多了,对于使用者而言 我们知道怎么用的,支持哪些配置源,各个配置代表什么就可以了。但是本着学习的态度结果罗嗦了一堆,大家看的时候可以选择性忽略,能忽略的部分已经做了标记。
本章节没有代码示例
下个小章节我们快速介绍下:Configuration Providers(多种方式提供配置)