如何实现Http请求报头的自动转发之设计

这篇具有很好参考价值的文章主要介绍了如何实现Http请求报头的自动转发之设计。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

HeaderForwarder组件不仅能够从当前接收请求提取指定的HTTP报头,并自动将其添加到任何一个通过HttpClient发出的请求中,它同时也提供了一种基于Context/ContextScope的编程模式是我们可以很方便地将任何报头添加到指定范围内的所有由HttpClient发出的请求中。现在我们来简单聊聊该组件的设计和实现原理。

一、HeaderForwardObserver

HeaderForwarder组件利用HeaderForwardObserver对HttpClient进行拦截,并将需要的报头添加到由它发出的请求消息中,我们曾经介绍过这种方案,这也是大部分APM自动添加跟踪报头的解决方案。具体的原理其实很简单:当HttpClient发送请求过程中会利用DiagnosticListener触发一些列事件,并在事件中提供相应的对象,比如发送的HttpRequestMessage和接收的HttpResponseMessage。如果我们需要这个过程进行干预,只需要订阅相应的事件并将干预操作实现在提供的回调中。

HeaderForwarder用来添加请求报头的是一个类型为HeaderForwardObserver的对象。在介绍该类型之前,我们得先来介绍如下这个IOutgoingHeaderCollectionProvider接口,顾名思义,它用来提供需要被添加的所有HTTP请求报头。

public interface IOutgoingHeaderCollectionProvider
{
    IDictionary<string, StringValues> GetHeaders();
}

如下所示的是HeaderForwardObserver的定义。如代码片段所示,HeaderForwardObserver实现了IObserver<KeyValuePair<stringobject>> 接。在实现的OnNext中,通过对事件名称(System.Net.Http.HttpRequestOut.Start)的比较订阅了HttpClient在发送请求前触发的事件,并从提供的参数提取出表示待发送请求的HttpRequestMessage对象(对应Request属性)。有了这个待发送的请求,我们只需要从构造函数中注入的IOutgoingHeaderCollectionProvider 对象提取出所有报头列表,并将其添加这个HttpRequestMessage对象中即可。

public sealed class HeaderForwardObserver : IObserver<KeyValuePair<string, object>>
{
    private static Func<object, HttpRequestMessage> _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<string, object> 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<object, HttpRequestMessage> 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<Func<object, HttpRequestMessage>>(convertToRequest, payload).Compile();
    }
}

二、HttpClientObserver

HeaderForwardObserver借助于如下这个HttpClientObserver进行注册。如代码片段所示,HttpClientObserver 实现了IObserver<DiagnosticListener>接口,在实现的OnNext方法中,它创建出HeaderForwardObserver对象并将其订阅到HttpClient使用的DiagnosticListener对象上(该对象的名称为HttpHandlerDiagnosticListener)。

public sealed class HttpClientObserver : IObserver<DiagnosticListener>
{
    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<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
    {
        return app => {
            DiagnosticListener.AllListeners.Subscribe(app.ApplicationServices.GetRequiredService<HttpClientObserver>());
            next(app);
        };
    }
}

四、HttpInvocationContext/HttpInvocationContextScope

接下来我们讨论待转发HTTP报头的来源问题。带转发报头有两种来源,一种是从当前请求中提取出来的,另一种是手工添加到HttpInvocationContext上下文中。如下所示的是HttpInvocationContext的定义,我们添加的报头就存储在它的OutgoingHeaders 属性中,表示当前上下文的HttpInvocationContext对象存储在AsyncLocal<HttpInvocationContext>对象上。

public sealed class HttpInvocationContext
{
    internal static readonly AsyncLocal<HttpInvocationContext> _current = new AsyncLocal<HttpInvocationContext>();
    public static HttpInvocationContext Current => _current.Value;
    public IDictionary<string, StringValues> OutgoingHeaders { get; } = new Dictionary<string, StringValues>();
    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<string> AutoForwardHeaderNames { get; } = new HashSet<string>();
    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<string> _autoForwardedHeaderNames;

    public OutgoingHeaderCollectionProvider(IHttpContextAccessor httpContextAccessor, IOptions<HeaderForwarderOptions> optionsAccessor)
    {
        _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
        _autoForwardedHeaderNames = (optionsAccessor?? throw new ArgumentNullException(nameof(optionsAccessor))).Value.AutoForwardHeaderNames;
    }

    public IDictionary<string, StringValues> GetHeaders()
    {
        var headers = new Dictionary<string, StringValues>();
        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中。

如何实现Http请求报头的自动转发之设计

六、服务注册

HeaderForwarder涉及的服务通过如下这个AddHeaderForwarder扩展方法进行注册

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddHeaderForwarder(this IServiceCollection services, Action<HeaderForwarderOptions> setup = null)
    {
        services = services ?? throw new ArgumentNullException(nameof(services));
        services.AddOptions();
        services.AddHttpContextAccessor();
        services.TryAddSingleton<IOutgoingHeaderCollectionProvider, OutgoingHeaderCollectionProvider>();
        services.TryAddSingleton<HttpClientObserver>();
        services.TryAddEnumerable(ServiceDescriptor.Singleton<IStartupFilter, HeaderForwaderStartupFilter>());
        if (null != setup)
        {
            services.Configure(setup);
        }
        return services;
    }
}

我们进一步定义了针对IHostBuilder接口的扩展方法,我们在前面演示实例中正是使用的这个方法。

public static class HostBuilderExtensions
{
    public static IHostBuilder UseHeaderForwarder(this IHostBuilder hostBuilder, Action<HeaderForwarderOptions> setup = null)
    {
        hostBuilder = hostBuilder ?? throw new ArgumentNullException(nameof(hostBuilder));
        hostBuilder.ConfigureServices((_,services) => services.AddHeaderForwarder(setup));
        return hostBuilder;
    }
}

 文章来源地址https://www.toymoban.com/news/detail-484511.html

到了这里,关于如何实现Http请求报头的自动转发之设计的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • HTTP的请求方法,空行,body,介绍请求报头的内部以及粘包问题

    目录 一、GET与POST简介 二、空行和body 三、初识请求报头以及粘包问题 四、认识请求报头剩余部分 GET https://www.sogou.com/HTTP/1.1 请求报文中的方法,是最常规的方法(获取资源) POST:传输实体主体的方法 一般来说方法的比重 GET占据八成 POST占据一成 其他的各种杂七杂八的方法

    2024年02月08日
    浏览(34)
  • Caddy反向代理转发修改http请求路径

    Caddy是个非常不错的开源服务器产品,简单易用,自带ssl。只是没啥详细的中文文档,遇到问题只能看官方文档。 记录一下使用Caddy转发http请求的方法。 问题:将http://192.168.1.10:7077/product/*的请求转发到http://192.168.1.12:7078/*。这里其实是两个需求,一个是转发端口,还有个是去

    2024年02月12日
    浏览(43)
  • Nginx接收Http协议请求转发使用Https协议

    公司使用阿里的apigateway,规定不太友好,同是SIT环境,A系统的SIT1环境居然不能调用B系统的SIT2环境的接口。因为各个系统之间部署的SIT环境数量不同A系统可能只有1套,B系统可能有8套,这样的话,可能会随时切换调用B系统的环境,管理员不允许,于是想着用Nginx做下转发。

    2024年02月08日
    浏览(65)
  • tengine/nginx https请求 转发 http upstream

    当前的互联网应用基本都要支持https协议,而当浏览器头通过https协议将请求发到到负责负载的nginx后,会由当前nginx再以http协议向后端upstream进行请求,之所以这么做是因为https协议的安全性也带来的额外的性能消耗。而源端基本都是在一个内网里面的,对于通讯协议的安全性

    2024年01月23日
    浏览(46)
  • Python使用HTTP代理实现网络请求的自动化

    随着网络技术的发展,网络请求成为了许多应用的重要组成部分。然而,手动发送网络请求不仅效率低下,而且容易出错。为了解决这个问题,我们可以使用Python来实现网络请求的自动化。而HTTP代理可以帮助我们更好地控制和管理这些请求。 在Python中,有许多库可以用来发

    2024年01月19日
    浏览(46)
  • Nginx转发http到https和开机自动启动

    场景: 以下都是基于windows系统(ip为虚构) 1.ip:172.16.54.55需要访问172.16.54.57的接口服务,来查看机械臂的运行状况 2.存在网络隔离,172.16.54.55无法直接访问172.16.54.57 3.172.16.54.56与172.16.54.57是机械臂厂商搞得内部网络,彼此可以互通 4.172.16.54.55与172.16.54.56是外部网络,彼此可以

    2023年04月25日
    浏览(46)
  • Java如何发起http的get请求的实现

    加哥最近做第三方接口开发,对方提供的是get方式的http请求,下面加哥给大家进行了总结如何用java代码去发送http请求并获取结果。 下面是发送get请求的工具类 下面是使用封装的好的请求工具类发送请求 注意请求发送后返回来的是JSON字符串,大家对其进行解析获取自己需要

    2024年02月11日
    浏览(48)
  • 飞桨Paddle动转静@to_static技术设计

    在深度学习模型构建上,飞桨框架支持动态图编程和静态图编程两种方式,其代码编写和执行方式均存在差异: 动态图编程: 采用 Python 的编程风格,解析式地执行每一行网络代码,并同时返回计算结果。 静态图编程: 采用先编译后执行的方式。需先在代码中预定义完整的

    2024年02月04日
    浏览(27)
  • lua使用resty.http做nginx反向代理(https请求,docker容器化部署集群),一个域名多项目转发

    下载使用 链接:https://pan.baidu.com/s/1uQ7yCzQsPWsF6xavFTpbZg 提取码:htay –来自百度网盘超级会员V5的分享 ad_load.lua文件

    2024年01月18日
    浏览(66)
  • Powershell脚本自动化登录网站的简单实例,命令行方式实现Http(s)的GET、POST请求

    自动化登录网站的流程比较简单,如果不懂 Python、JavaScript、C++ 等编程语言,又没有安装这些编程语言环境软件,我们还要新的点子:用Windows系统自带的 Powershell 运行自编的脚本来实现。 PowerShell 是一种功能强大的自动化工具,除了可以使用 DOS 批处理命令之外,还可以进行

    2024年02月10日
    浏览(56)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包