springboot转发http请求_如何实现Http请求报头的自动转发
HeaderForwarder组件不仅能够从当前接收请求提取指定的HTTP报头,并自动将其添加到任何一个通过HttpClient发出的请求中,它同时也提供了一种基于Context/ContextScope的编程模式是我们可以很方便地将任何报头添加到指定范围内的所有由HttpClient发出的请求中。上篇介绍了HeaderForwarder组件的使用方式,现在我们来简单聊聊该组件的设计和实现原理。[源代码从这里下载]
目录
一、HeaderForwardObserver
二、HttpClientObserver
三、HeaderForwaderStartupFilter
四、HttpInvocationContext/HttpInvocationContextScope
五、OutgoingHeaderCollectionProvider
六、服务注册
一、HeaderForwardObserver
HeaderForwarder组件利用HeaderForwardObserver对HttpClient进行拦截,并将需要的报头添加到由它发出的请求消息中,我们曾经在《 四种为HttpClient添加默认请求报头的解决方案 》一文中介绍过这种方案,这也是大部分APM自动添加跟踪报头的解决方案。具体的原理其实很简单:当HttpClient发送请求过程中会利用DiagnosticListener触发一些列事件,并在事件中提供相应的对象,比如发送的HttpRequestMessage和接收的HttpResponseMessage。如果我们需要这个过程进行干预,只需要订阅相应的事件并将干预操作实现在提供的回调中。《ASP.NET Core 3框架揭秘》第8“诊断日志”具有对DiagnosticListener的详细介绍。
HeaderForwarder用来添加请求报头的是一个类型为HeaderForwardObserver的对象。在介绍该类型之前,我们得先来介绍如下这个IOutgoingHeaderCollectionProvider接口,顾名思义,它用来提供需要被添加的所有HTTP请求报头。
public interface IOutgoingHeaderCollectionProvider{ IDictionary GetHeaders();}
如下所示的是HeaderForwardObserver的定义。如代码片段所示,HeaderForwardObserver实现了IObserver> 接。在实现的OnNext中,通过对事件名称( System.Net.Http.HttpRequestOut.Start )的比较订阅了HttpClient在发送请求前触发的事件,并从提供的参数提取出表示待发送请求的HttpRequestMessage对象(对应Request属性)。有了这个待发送的请求,我们只需要从构造函数中注入的IOutgoingHeaderCollectionProvider 对象提取出所有报头列表,并将其添加这个HttpRequestMessage对象中即可。
public sealed class HeaderForwardObserver : IObserver>{ private static Func _requestAccessor; private readonly IOutgoingHeaderCollectionProvider _provider; public HeaderForwardObserver(IOutgoingHeaderCollectionProvider provider) { _provider = provider ?? throw new ArgumentNullException(nameof(provider)); } public void OnCompleted() { } public void OnError(Exception error) { } public void OnNext(KeyValuePair value) { if (headers.Any() && value.Key == "System.Net.Http.HttpRequestOut.Start") { var headers = _provider.GetHeaders(); _requestAccessor ??= CreateRequestAccessor(value.Value.GetType()); var outgoingHeaders = _requestAccessor(value.Value).Headers; foreach (var kv in headers) { outgoingHeaders.Add(kv.Key, kv.Value.AsEnumerable()); } } } private static Func CreateRequestAccessor(Type type) { var requestProperty = type.GetProperty("Request"); var payload = Expression.Parameter(typeof(object)); var convertToPayload = Expression.Convert(payload, type); var getRequest = Expression.Call(convertToPayload, requestProperty.GetMethod); var convertToRequest = Expression.Convert(getRequest, typeof(HttpRequestMessage)); return Expression.Lambda>(convertToRequest, payload).Compile(); }}
二、HttpClientObserver
HeaderForwardObserver借助于如下这个HttpClientObserver进行注册。如代码片段所示,HttpClientObserver 实现了IObserver接口,在实现的OnNext方法中,它创建出HeaderForwardObserver对象并将其订阅到HttpClient使用的DiagnosticListener对象上(该对象的名称为 HttpHandlerDiagnosticListener )。
public sealed class HttpClientObserver : IObserver{ private readonly IOutgoingHeaderCollectionProvider _provider; public HttpClientObserver(IOutgoingHeaderCollectionProvider provider) { _provider = provider ?? throw new ArgumentNullException(nameof(provider)); } public void OnCompleted() { } public void OnError(Exception error) { } public void OnNext(DiagnosticListener value) { if (value.Name == "HttpHandlerDiagnosticListener") { value.Subscribe(new HeaderForwardObserver(_provider)); } }}
三、HeaderForwaderStartupFilter
我们将针对HttpClientObserver的注册实现在如下这个HeaderForwaderStartupFilter类型中。如代码片段所示,HeaderForwaderStartupFilter实现了IStartupFilter接口,针对HttpClientObserver的注册就实现在Configure方法中。
public sealed class HeaderForwaderStartupFilter : IStartupFilter{ public Action Configure(Action next) { return app => { DiagnosticListener.AllListeners.Subscribe(app.ApplicationServices.GetRequiredService()); next(app); }; }}
四、HttpInvocationContext/HttpInvocationContextScope
接下来我们讨论待转发HTTP报头的来源问题。通过上篇的介绍我们知道,带转发报头有两种来源,一种是从当前请求中提取出来的,另一种是手工添加到HttpInvocationContext上下文中。如下所示的是HttpInvocationContext的定义,我们添加的报头就存储在它的OutgoingHeaders 属性中,表示当前上下文的HttpInvocationContext对象存储在AsyncLocal对象上。
public sealed class HttpInvocationContext{ internal static readonly AsyncLocal _current = new AsyncLocal(); public static HttpInvocationContext Current => _current.Value; public IDictionary OutgoingHeaders { get; } = new Dictionary(); internal HttpInvocationContext() { }}
HttpInvocationContextScope用来控制HttpInvocationContext的范围(生命周期),从定义可以看出,只有在创建该Scope的using block范围为才能得到当前的HttpInvocationContext上下文。
public sealed class HttpInvocationContextScope : IDisposable{ public HttpInvocationContextScope() { HttpInvocationContext._current.Value = new HttpInvocationContext(); } public void Dispose() => HttpInvocationContext._current.Value = null;}
五、OutgoingHeaderCollectionProvider
HeaderForwardObserver添加到请求消息中的报头是通过注入的IOutgoingHeaderCollectionProvider对象提供的,现在我们来看看该接口的实现类型OutgoingHeaderCollectionProvider。我们说过,所有的报头具有两个来源,其中一个来源于当前接收的请求,但是并不是请求中携带的所有报头都需要转发,所以我们需要利用如下这个HeaderForwarderOptions类型来配置转发的报头名称。
public class HeaderForwarderOptions public ISet AutoForwardHeaderNames { get; } = new HashSet(); public void AddHeaderNames(params string[] headerNames) => Array.ForEach(headerNames, it => AutoForwardHeaderNames.Add(it));}
如下所示的是OutgoingHeaderCollectionProvider类型的定义。在实现的GetHeaders方法中,它利用注入的IHttpContextAccessor 对象得到当前HttpContext,并结合HeaderForwarderOptions上的配置得到需要自动转发的报头。然后通过当前HttpInvocationContext上下文你得到手工指定的报头,两者合并之后成为了最终需要添加到请求消息的报头列表。
public sealed class OutgoingHeaderCollectionProvider : IOutgoingHeaderCollectionProvider{ private readonly IHttpContextAccessor _httpContextAccessor; private readonly ISet _autoForwardedHeaderNames; public OutgoingHeaderCollectionProvider(IHttpContextAccessor httpContextAccessor, IOptions optionsAccessor) { _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor)); _autoForwardedHeaderNames = (optionsAccessor?? throw new ArgumentNullException(nameof(optionsAccessor))).Value.AutoForwardHeaderNames; } public IDictionary GetHeaders() { var headers = new Dictionary(); try { var incomingHeaders = _httpContextAccessor.HttpContext?.Request?.Headers; if (incomingHeaders != null) { foreach (var headerName in _autoForwardedHeaderNames) { if (incomingHeaders.TryGetValue(headerName, out var values)) { headers.Add(headerName, values); } } } } catch (ObjectDisposedException) {} var outgoingHeaders = HttpInvocationContext.Current?.OutgoingHeaders; if (outgoingHeaders != null) { foreach (var kv in outgoingHeaders) { if (headers.TryGetValue(kv.Key, out var values)) { headers[kv.Key] = new StringValues(values.Concat(kv.Value).ToArray()); } else { headers.Add(kv.Key, kv.Value); } } } return headers; }}
到目前为止,HeaderForwarder的核心成员均已介绍完毕,这些接口/类型之间的关系体现在如下所示的UML中。
六、服务注册
HeaderForwarder涉及的服务通过如下这个AddHeaderForwarder扩展方法进行注册
public static class ServiceCollectionExtensions{ public static IServiceCollection AddHeaderForwarder(this IServiceCollection services, Action setup = null) { services = services ?? throw new ArgumentNullException(nameof(services)); services.AddOptions(); services.AddHttpContextAccessor(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddEnumerable(ServiceDescriptor.Singleton()); if (null != setup) { services.Configure(setup); } return services; }}
我们进一步定义了针对IHostBuilder接口的扩展方法,我们在前面演示实例中正是使用的这个方法。
public static class HostBuilderExtensions{ public static IHostBuilder UseHeaderForwarder(this IHostBuilder hostBuilder, Action setup = null) { hostBuilder = hostBuilder ?? throw new ArgumentNullException(nameof(hostBuilder)); hostBuilder.ConfigureServices((_,services) => services.AddHeaderForwarder(setup)); return hostBuilder; }}
springboot转发http请求_如何实现Http请求报头的自动转发相关推荐
- httpcline转发_如何实现Http请求报头的自动转发[应用篇]
如今的应用部署逐渐向微服务化发展,导致一个完整的事务往往会跨越很多的应用或服务,出于分布式链路跟踪的需要,我们往往将从上游服务获得的跟踪请求报头无脑地向下游服务进行转发.本文介绍的这个名为Header ...
- ios开发循环网络请求_如何简化网络请求接口开发
日常开发中,需要大量和后端对接接口,很容易写出大量的模板代码,比如把对接口的请求放到组件内,或者虽然有数据访问层,但是有大量 get,post 方法的调用,传参的字符串拼接,异常处理等等,有些逻辑是可 ...
- axios不发起请求_重复的ajax请求让人很受伤
重复的ajax请求让人很受伤 重复的ajax请求一波又一波的袭来,服务器正躲在角落里瑟瑟发抖,它内心是崩溃的,这算是遭了罪了, 前端小王子为啥没有做好限制和封锁,真是伤不起啊,哎,不如意事常八九,能与 ...
- springboot 中文文档_比Swagger还好用的自动生成接口文档工具
JApiDocs是一个无需额外注解.开箱即用的SpringBoot接口文档生成工具. 编写和维护API文档这个事情,对于后端程序员来说,是一件恼人但又不得不做的事情,我们都不喜欢写文档,但除非项目前后 ...
- ajax请求_重复的ajax请求让人很受伤
重复的ajax请求让人很受伤 重复的ajax请求一波又一波的袭来,服务器正躲在角落里瑟瑟发抖,它内心是崩溃的,这算是遭了罪了, 前端小王子为啥没有做好限制和封锁,真是伤不起啊,哎,不如意事常八九,能与 ...
- java 每秒处理1000个请求_每秒处理请求数和并发是一个概念吗?
不是同一个概念,但它们之间有联系: 设平均响应时间为t(单位为毫秒), 并发量为c,每秒处理请求数为q,则: q = (1000/t) * c 就是这个关系: 想要升高q,就只有两条路:1) 降低t ...
- axios创建实例对象发送ajax请求_解决一个网页请求多个服务器场景---axios工作笔记009
然后我们再去看看,我们利用 axios去创建实例对象来发送ajax请求 可以看到上面我们创建了一个duanzi的axios对象. 然后我们在这个duanzi的axios对象中,指定默认的baseURL ...
- python3网络请求_使用Python3和请求发送XMLHttpRequest并获取响应
我在浏览器中打开DevTools,可以看到需要的请求. Request info 我的代码:import requests url = 'https://www.docavenue.com/elast ...
- python token发送请求_使用Python发送请求
本节内容# requests安装 requests使用 JSON类型解析 requests库详解 带安全认证的请求 序言# 上节课我们学习了接口测试的理论,抓包工具及使用Postman手工测试各种接口 ...
最新文章
- GitHub接连封杀开源项目惹众怒,CEO亲自道歉
- linux7开放3306端口,CentOS 7 开放3306端口访问
- Postfix配置文档
- 关系计划笔谈(9-1):泛BOM与虚拟产品
- SpringCloud(第二部分)
- 聚焦 AI + 大数据全球视野引领行业创新升级
- intellij idea 2018 license 可用无废话
- (网页)parseFloat在工作中遇到的错误
- [系统底层] x86和x64下ssdt的差异
- 【转】MFC与.NET的区别
- MySQL/sqlserver查询in操作 查询结果按in集合顺序显示
- python中tkinter的使用-下
- LINUX下载编译OpenSSL
- ndimage.median_filter中值滤去除干扰信号
- 熊猫烧香病毒源码及分析
- 辨别 优盘 真假 [金士顿]
- 超定方程的求解、最小二乘解
- Excel合并单元格中间插入斜杠和数字保留一位小数
- “踢群第一案”上热搜的背后
- 内网渗透结束,痕迹清理必备手段
热门文章
- opencv-python 学习笔记1:简单的图片处理
- Windows SharePoint Services 3.0 Tools: Visual Studio 2005 Extensions发布1.1 CTP版本扩展
- 大数据相加_推动媒体融合与大数据相加发展
- html52D转换3D,CSS3 Transform 2D和3D转换
- go 查看全局安装了哪些包_如何用 GVM 管理 Go 项目
- 信息学奥赛一本通(2038:【例5.5】最大数位置)
- 信息学奥赛一本通(1070:人口增长)
- 训练日志 2019.1.14
- 34 SD配置-销售凭证设置-定义销售凭证类型
- 2 SD配置-企业结构-定义-定义分销渠道