实战指南:用回调微调代理配置,而无需自定义配置提供程序)
YARP 配置过滤器IProxyConfigFilter实战指南用回调微调代理配置而无需自定义配置提供程序【免费下载链接】reverse-proxyA toolkit for developing high-performance HTTP reverse proxy applications.项目地址: https://gitcode.com/GitHub_Trending/re/reverse-proxy导读本文基于 YARPYet Another Reverse Proxy仓库中的ReverseProxy.ConfigFilter.Sample示例系统讲解IProxyConfigFilter 配置过滤器机制。当你的代理配置绝大部分由配置文件如appsettings.json提供只需在加载时对个别值例如目标地址、路由优先级做动态微调又不愿为此编写完整的自定义配置提供程序IProxyConfigProvider时配置过滤器就是最轻量的方案。读完本文你将掌握IProxyConfigFilter接口的两个回调方法、基于 Record 不可变对象的安全修改方式以及如何借助环境变量实现配置即模板的部署技巧。一、为什么需要配置过滤器在 YARP 中配置来源可以是配置文件、内存对象或完全自定义的配置提供程序。多数场景下基于appsettings.json的LoadFromConfig已经足够但现实部署常遇到两类诉求配置文件大部分内容可直接使用但个别值需要在加载时动态替换——例如容器/云环境中目标后端地址由环境变量注入不同的环境开发、预发、生产复用同一份配置模板希望在不重写整个配置管道的条件下对 Route、Cluster 做统一的后置修正——例如调整路由优先级、注入默认策略。如果为这两类小改动去实现一个完整的IProxyConfigProvider代价过高且不易维护。配置过滤器正是为此设计的轻量钩子它允许在配置加载过程中注册回调逐条查看并修改即将生效的 Route 与 Cluster。正如 samples/ReverseProxy.ConfigFilter.Sample/README.md 所描述配置文件提供了你所需的大部分内容但你希望调整某些值又不想编写自定义配置提供程序。二、IProxyConfigFilter 接口解析过滤器的契约定义在 src/ReverseProxy/Configuration/IProxyConfigFilter.cspublic interface IProxyConfigFilter { ValueTaskClusterConfig ConfigureClusterAsync(ClusterConfig cluster, CancellationToken cancel); ValueTaskRouteConfig ConfigureRouteAsync(RouteConfig route, ClusterConfig? cluster, CancellationToken cancel); }接口只有两个方法它们分别在Cluster 与 Route 从配置加载时被回调方法回调时机典型用途ConfigureClusterAsync每个 Cluster 加载时修改目标地址、负载均衡策略、会话粘滞等 Cluster 级配置ConfigureRouteAsync每个 Route 加载时可拿到其关联的 Cluster修改匹配规则、路由优先级Order、超时策略等 Route 级配置接口注释明确指出这是每次代理配置被加载时都会运行的配置过滤器。两个方法都是异步签名ValueTask返回被修改后的配置对象。三、示例核心实现CustomConfigFilter示例的主体代码位于 samples/ReverseProxy.ConfigFilter.Sample/CustomConfigFilter.cs它实现了IProxyConfigFilter接口演示了两个非常实用的场景。3.1 ConfigureClusterAsync环境变量替换目标地址// Matches {{env_var_name}} private readonly Regex _exp new(\\{\\{(\\w)\\}\\}); public ValueTaskClusterConfig ConfigureClusterAsync(ClusterConfig origCluster, CancellationToken cancel) { // Each cluster has a dictionary of destinations, which is read-only, // so well create a new one with our updates var newDests new Dictionarystring, DestinationConfig(StringComparer.OrdinalIgnoreCase); foreach (var d in origCluster.Destinations) { var origAddress d.Value.Address; if (_exp.IsMatch(origAddress)) { // Get the name of the env variable from the destination and lookup value var lookup _exp.Matches(origAddress)[0].Groups[1].Value; var newAddress System.Environment.GetEnvironmentVariable(lookup); if (string.IsNullOrWhiteSpace(newAddress)) { throw new System.ArgumentException($Configuration Filter Error: Substitution for {lookup} in cluster {d.Key} not found as an environment variable.); } // using c# 9 with to clone and initialize a new record var modifiedDest d.Value with { Address newAddress }; newDests.Add(d.Key, modifiedDest); } else { newDests.Add(d.Key, d.Value); } } return new ValueTaskClusterConfig(origCluster with { Destinations newDests }); }这段代码的核心逻辑可以拆解为四步模式匹配用正则\{\{(\w)\}\}检查每个 destination 的Address是否符合{{env_var_name}}形式。注意正则使用了非贪婪匹配{{contoso}}中的contoso会落入第 1 个捕获组环境变量查找通过System.Environment.GetEnvironmentVariable(lookup)读取同名环境变量的值这正是部署到 Azure 等平台时通过管理控制台直接替换后端地址的简单途径——代码注释中明确说明了这一设计意图缺失即报错如果环境变量不存在或为空IsNullOrWhiteSpace直接抛出ArgumentException明确提示是哪个 Cluster 的哪个 Destination 替换失败避免生产环境悄悄用了错误地址构造新配置返回由于配置对象是不可变 Record这里采用 C# 9 的with表达式克隆出修改后的DestinationConfig再汇总到新的只读字典中最后同样用origCluster with { Destinations newDests }返回新的 Cluster。3.2 ConfigureRouteAsync约束配置路由的优先级public ValueTaskRouteConfig ConfigureRouteAsync(RouteConfig route, ClusterConfig cluster, CancellationToken cancel) { // Example: do not let config based routes take priority over code based routes. // Lower numbers are higher priority. Code routes default to 0. if (route.Order.HasValue route.Order.Value 1) { return new ValueTaskRouteConfig(route with { Order 1 }); } return new ValueTaskRouteConfig(route); }这里演示的是路由优先级护栏YARP 中路由的Order数值越小优先级越高见 src/ReverseProxy/Configuration/RouteConfig.cs 中对Order的注释 Routes with lower numbers take precedence over higher numbers而通过代码注册的路由默认Order为 0。为了不让配置文件里声明的路由抢占代码路由的优先级过滤器将配置来源且Order 1的路由统一提升为 1。值得注意的是该方法在无修改时原样返回route这体现了配置过滤器的一个重要约定——要么原样返回要么返回替换品见下文不可变性说明。四、注册与底层调用时机4.1 一行代码完成注册示例的入口 samples/ReverseProxy.ConfigFilter.Sample/Program.cs 展示了完整的接线方式var builder WebApplication.CreateBuilder(args); builder.Services.AddControllers(); builder.Services.AddReverseProxy() .LoadFromConfig(builder.Configuration.GetSection(ReverseProxy)) .AddConfigFilterCustomConfigFilter(); var app builder.Build(); app.MapReverseProxy(); app.Run();AddConfigFilterT()扩展方法定义在 src/ReverseProxy/Management/ReverseProxyServiceCollectionExtensions.cs其内部将过滤器注册为单例服务public static IReverseProxyBuilder AddConfigFilter[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TService(this IReverseProxyBuilder builder) where TService : class, IProxyConfigFilter { ArgumentNullException.ThrowIfNull(builder); builder.Services.TryAddEnumerable(ServiceDescriptor.SingletonIProxyConfigFilter, TService()); return builder; }这里有两个对扩展性至关重要的细节支持注册多个过滤器TryAddEnumerableSingletonIProxyConfigFilter, TService意味着你可以多次调用AddConfigFilterT注册多个不同类型的过滤器按注册顺序执行API 文档明确写道 Multiple filters are allowed, and they will be run in registration order允许多个过滤器它们将按注册顺序依次运行这使得过滤器可以像管道一样串联实现先做替换、再做校验的分层处理。4.2 过滤器在配置管道中的位置从 src/ReverseProxy/Management/ProxyConfigManager.cs 可以看到过滤器的真实调用位置。ProxyConfigManager通过构造函数注入IEnumerableIProxyConfigFilter得到_filters数组_filters字段声明见第 45 行并在加载配置时对每个 Route、Cluster 逐一执行// Route 过滤第 530-533 行附近 foreach (var filter in _filters) { route await filter.ConfigureRouteAsync(route, cluster, cancellation); } // Cluster 过滤第 584-587 行附近 foreach (var filter in _filters) { cluster await filter.ConfigureClusterAsync(cluster, cancellation); } var clusterErrors await _configValidator.ValidateClusterAsync(cluster);这段源码揭示了三个关键事实过滤发生在校验之前Cluster 在过滤完成后才会进入_configValidator.ValidateClusterAsync校验流程说明过滤器拥有先修改、后校验的完整机会修改不当也会被后续校验拦截逐对象逐个回调每个 Route 和每个 Cluster 都会依次经过所有已注册过滤器顺序即注册顺序返回值驱动每次回调的返回值都会作为下一次调用的输入route await filter.ConfigureRouteAsync(...)形成可组合的修改链。五、配置文件与运行示例5.1 appsettings.json写入环境变量占位符appsettings.json 中cluster1的一个目标地址被刻意写成了占位符形式Clusters: { cluster1: { Destinations: { cluster1/destination1: { // Following value will be found by regex and looked up as an environment variable Address: {{contoso}} }, cluster1/destination2: { Address: https://bing.com/ } } }, cluster2: { Destinations: { cluster2/destination1: { Address: https://example.com/ } } } }配置中还定义了两条路由route1匹配localhost主机上GET/POST /api/{**catch-all}并转发到cluster1route2兜底匹配所有路径并转发到cluster2。运行时{{contoso}}会被ConfigureClusterAsync的正则命中并替换为环境变量contoso的值而https://bing.com/、https://example.com/不匹配模式会被原样保留。5.2 launchSettings.json为 F5 调试注入环境变量Properties/launchSettings.json 中定义了示例依赖的环境变量profiles: { ReverseProxy.ConfigFilter.Sample: { commandName: Project, environmentVariables: { Key: Value, contoso: https://contoso.com, ASPNETCORE_ENVIRONMENT: Development } } }这意味着在 Visual Studio 或其他支持launchSettings.json的工具中按F5调试时环境变量contosohttps://contoso.com会被自动注入{{contoso}}最终被替换为https://contoso.com。README 特别提示了这一点环境变量定义在 launchSettings 中由 Visual Studio 等工具在调试时注入。5.3 运行与验证命令行直接运行dotnet run若环境变量未注入可先export contosohttps://contoso.com访问http://localhost:5000/api/...请求将被转发至cluster1此时目标为环境变量解析出的地址若删除contoso环境变量再启动应用会因ConfigureClusterAsync抛出的ArgumentException而启动失败并打印类似Configuration Filter Error: Substitution for contoso in cluster cluster1/destination1 not found as an environment variable.的明确错误信息——这是过滤器快速失败策略的有意设计。六、不可变性为什么过滤器必须返回新对象README 特别强调Route 和 Cluster 都被定义为Record是不可变的immutable因此过滤器的两个方法应原样返回同一个对象或返回一个替换品。这一约定在源码中得到印证ClusterConfig的Destinations属性类型为IReadOnlyDictionarystring, DestinationConfig?见 src/ReverseProxy/Configuration/ClusterConfig.cs只读字典无法原地修改示例因此新建Dictionarystring, DestinationConfig并逐项重建最后用with表达式生成新 ClusterRouteConfig.Order也声明为{ get; init; }的 init-only 属性见 src/ReverseProxy/Configuration/RouteConfig.cs只能在对象初始化时赋值。这种设计让过滤器的行为可预测、线程安全过滤在配置加载期间并发执行也不会产生数据竞争同时with表达式只需浅拷贝需要变更的属性代价可控。作为过滤器开发者请务必遵守要么返回原对象要么用with克隆出新对象切勿尝试对传入对象做强制类型转换后的可变修改。七、进阶要点与适用边界多过滤器组合可按需注册多个过滤器形成处理链顺序即注册顺序例如环境变量替换 统一注入默认超时策略 路由优先级护栏各司其职失败策略示例采用找不到环境变量即抛异常的硬失败模式可防止配置模板在生产环境被静默解析成无效地址若你的场景允许降级也可改为保留原值并写日志适用范围过滤器适合配置文件为主、按需微调的场景若配置几乎全部来自外部系统如 Kubernetes、数据库、管理 API则仍应实现完整的IProxyConfigProvider——二者是互补关系而非替代关系与配置热更新兼容过滤器在每次配置加载时都会执行接口注释 run each time the proxy configuration is loaded因此当配置提供程序发出变更令牌触发重载时环境变量替换逻辑同样会重新运行保证新配置也经过同一套修正逻辑。八、相关文件索引示例说明文档samples/ReverseProxy.ConfigFilter.Sample/README.md过滤器实现samples/ReverseProxy.ConfigFilter.Sample/CustomConfigFilter.cs程序入口与注册samples/ReverseProxy.ConfigFilter.Sample/Program.cs代理配置文件samples/ReverseProxy.ConfigFilter.Sample/appsettings.json调试环境变量samples/ReverseProxy.ConfigFilter.Sample/Properties/launchSettings.json接口定义src/ReverseProxy/Configuration/IProxyConfigFilter.cs注册扩展方法src/ReverseProxy/Management/ReverseProxyServiceCollectionExtensions.cs过滤器调用与校验顺序src/ReverseProxy/Management/ProxyConfigManager.cs【免费下载链接】reverse-proxyA toolkit for developing high-performance HTTP reverse proxy applications.项目地址: https://gitcode.com/GitHub_Trending/re/reverse-proxy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考