首页 > 其他分享 >如何自动转发接收的请求报头?

如何自动转发接收的请求报头?

时间:2023-06-08 15:46:16浏览次数:64  
标签:调用 请求 using 转发 报头 接收 httpClient HttpClient

了解OpenTelemetry的朋友应该知道,为了将率属于同一个请求的多个操作(Span)串起来,上游应用会生成一个唯一的TraceId。在进行跨应用的Web调用时,这个TraceId和代表跟踪操作标识的SpanID一并发给目标应用,W3C还专门指定了一份名为Trace Context的标准,该标准确定了一个名为trace-parent的请求报头来传递TraceId、(Parent)SpanID以及其他两个跟踪属性。其实我们的应用也可能会使用到分布式跟踪这种类似的功能,我们需要在某个应用中添加一些“埋点”,当它调用另一个应用时,这些埋点会自动添加到请求的报头集合中,从而实现在整个调用链中自动传递。为了实现这个功能,我创建了一个名为HeaderForwarder(Github)的框架。本文不会介绍HeaderForwarder的设计,仅仅介绍它的使用方式,有兴趣的朋友可以查看源代码。

一、 请求报头的自动转发
二、 屏蔽自动转发功能
三、 为请求添加请求报头
四、 同名报头的处理
五、 屏蔽“外部”添加的请求报头

一、 请求报头的自动转发

我们创建App1、App2和App3三个应用,ASP.NET Core应用App2和App3以路由的形式提供一个简单的API,App1则是一个简单的控制台应用。App1利用HttpClient调用App2承载的API,后者进一步调用App3。我们让处于中间的App2安装HeaderForwarder。如下所示的是控制台应用App1的定义。我们利用创建的HttpClient调用App2承载的API,发送的请求中人为添加了名为 “foo” 、“bar” 和 “baz” 的三个报头。

复制代码
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost:5000/test");
request.Headers.Add("foo", "123");
request.Headers.Add("bar", "456");
request.Headers.Add("baz", "789");
using (var httpClient = new HttpClient())
{
    await httpClient.SendAsync(request);
}
复制代码

App2定义如下。HeaderForwarder设计的服务通过调用IServiceCollection接口的AddHeaderForwarder进行注册,该方法中同时指定了需要自动转发的报头名称 “foo” 和 “bar” (不区分大小写)。后面调用AddHttpClient扩展方法是为了使用注入的IHttpClientFactory对象所需的HttpClient对象。

复制代码
var builder = WebApplication.CreateBuilder(args);
builder.Services.
AddHeaderForwarder("foo", "bar")
.AddHttpClient();
var app = builder.Build();
app.MapGet("/test", async (HttpRequest request, IHttpClientFactory httpClientFactory) =>
{
    foreach (var kv in request.Headers)
    {
        Console.WriteLine($"{kv.Key}:{kv.Value}");
    }
    await httpClientFactory.CreateClient().GetAsync("http://localhost:5001/test");
});
app.Run("http://localhost:5000");
复制代码

App1调用的API体现为针对路径 “/test” 注册的路由。路由处理程序会再控制台上输出接收到的所有请求报头,并在此之后利用IHttpClientFactory对象创建的HttpClient完成针对App3的调用。App3提供的API仅仅按照如下的方式将接收到的请求报头输出到控制台上。

复制代码
var app = WebApplication.CreateBuilder(args).Build();
app.MapGet("/test",  (HttpRequest request) =>
{
    foreach (var kv in request.Headers)
    {
        Console.WriteLine($"{kv.Key}:{kv.Value}");
    }
});
app.Run("http://localhost:5001");
复制代码

三个应用先后启动后,App1调用App2添加的三个请求报头(“foo” 、 “bar” 和 “baz”)会出现在App2的控制台上。HeaderForwarder只会自动转发指定的请求报头“foo” 和“bar” ,所有只有这两个报头会出现在App3的控制台上。从图中还可以看到,默认由HttpClientFactory创建的HttpClient的调用添加和转发用于分布式跟踪的traceparent报头。

clip_image002

二、 屏蔽自动转发功能

HeaderForwarder能够获得当前的HttpContext上下文,并提取并转发所需的请求报头。如果App2在调用App3的时候并不希望将报头转发出去,可以按照如下的方式注入IOutgoingHeaderProcessor对象,并调用其SuppressHeaderForwarder方法将报头自动转发功能屏蔽掉。

复制代码
using HeaderForwarder;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHeaderForwarder("foo", "bar").AddHttpClient();
var app = builder.Build();
app.MapGet("/test", async (IHttpClientFactory httpClientFactory,IOutgoingHeaderProcessor processor ) =>
{
    
using (processor.SuppressHeaderForwarder()){
        await httpClientFactory.CreateClient().GetAsync("http://localhost:5001/test");
    
}
});
app.Run("http://localhost:5000");
复制代码

SuppressHeaderForwarder利用返回的IDisposable对象代表“屏蔽上下文”,意味着该创建的“屏障”会在其Dispose方法后失效,所以App2在此上下文中完成针对App3的调用,它接收的请求报头“foo” 和“bar”并不会被转发出去。

clip_image004

三、 为请求添加请求报头

当我们利用HttpClient进行Web调用时,如果需要认为地添加报头,典型的做法就是按照App1异常创建一个HttpRequestMessage对象,并将需要的报头以键值对的形式添加到它的Headers属性中。HeaderForwarder提供了一种更加快捷易用的编程模式。

var processor = OutgoingHeaderProcessor.Create();
using(var httpClient = new HttpClient())
using (processor.AddHeaders(("foo", "123"), ("bar", "456"), ("baz", "789")))
await httpClient.GetAsync("http://localhost:5000/test");

如上面的代码片段所示,我们调用OutgoingHeaderProcessor类型的静态方法Create创建了一个IOutgoingHeaderProcessor对象,并调用其AddHeaders完成了三个请求报头的添加。这个方法同样返回一个通过IDisposable对象表示的执行上下文,在此上下文中针对HttpClient的调用生成的请求均会自动附加这三个报头。

四、 同名报头的处理

由于IOutgoingHeaderProcessor接口的AddHeaders方法返回的时一个IDisposable对象表示的上下文,意味着上下文之间可能出现嵌套的关系。在默认情况下,如果HttpClient在这样一个嵌套的上下文中被使用,这些上下文携带的请求报头都将被转发。一般来说,这种情况正是我们希望的,但是如果我们在一个具有嵌套关系的多个上下文中添加了多个同名的报头,就有可能出现我们不愿看到的结果。

复制代码
using HeaderForwarder;

var processor = OutgoingHeaderProcessor.Create();
using(var httpClient = new HttpClient())
await FooAsync(httpClient);

async Task FooAsync(HttpClient httpClient)
{
    using (processor.AddHeaders(("foobarbaz", "abc")))
    await BarAsync(httpClient);
}
async Task BarAsync(HttpClient httpClient)
{
    using (processor.AddHeaders(("foobarbaz", "abc")))
    await BazAsync(httpClient);
}
async Task BazAsync(HttpClient httpClient)
{
    using (processor.AddHeaders(("foobarbaz", "abc")))
    await httpClient.GetAsync("http://localhost:5000/test");
}
复制代码

如上面的代码所示,三个嵌套调用的方法FooAsync、BarAsync和BazAsync采用相同的方式调用IOutgoingHeaderProcessor对象的AddHeaders方法添加相同的请求报头“foobarbaz”。意味着在BazAsync方法针对HttpClient的调用会在三个嵌套的上下文中进行,这意味着App2会接收到三个同名的请求报头。

clip_image006

如果不希望出现这种情况下,可以将针对AddHeaders方法的调用按照如下的方式替换成ReplaceHeaders。

复制代码
async Task FooAsync(HttpClient httpClient)
{
    using (processor.
ReplaceHeaders
(("foobarbaz", "abc")))
    await BarAsync(httpClient);
}
async Task BarAsync(HttpClient httpClient)
{
    using (processor.
ReplaceHeaders
(("foobarbaz", "abc")))
    await BazAsync(httpClient);
}
async Task BazAsync(HttpClient httpClient)
{
    using (processor.
ReplaceHeaders
(("foobarbaz", "abc")))
    await httpClient.GetAsync("http://localhost:5000/test");
}
复制代码

五、 屏蔽“外部”添加的请求报头

如果不愿意收到嵌套的“外部”上下文的干扰,我们可以调用IOutgoingHeaderProcessor接口的AddHeadersAfterClear方法。顾名思义,这个方法在添加指定请求报头之前,会先将现有的报头清除。

复制代码
var processor = OutgoingHeaderProcessor.Create();
using(var httpClient = new HttpClient())
await FooAsync(httpClient);

async Task FooAsync(HttpClient httpClient)
{
    using (processor.
AddHeadersAfterClear
(("foo", "123")))
    await BarAsync(httpClient);
}
async Task BarAsync(HttpClient httpClient)
{
    using (processor.
AddHeadersAfterClear
(("barbaz", "456")))
    await BazAsync(httpClient);
}
async Task BazAsync(HttpClient httpClient)
{
    using (processor.
AddHeadersAfterClear
(("barbaz", "789")))
    await httpClient.GetAsync("http://localhost:5000/test");
}
复制代码

如上面的代码片段所示,FooAsync调用AddHeadersAfterClear方法添加了一个名为“foo”的报头,BarAsync和BazAsync则采用相同的方式添加了两个同名的请求报头“Barbaz”。App2只会接收到由BazAsync设置的报头。

clip_image008

AddHeadersAfterClear针对现有报头的清除只会体现在它创建的上下文中,当前上下文并不会收到影响。因为该方法根本没有做任何清除工作,而是创建一个全新的上下文。AddHeaders和ReplaceHeaders方法其实重用了外部的上下文。

 

 

出处:https://www.cnblogs.com/artech/p/HeaderForwarder.html

标签:调用,请求,using,转发,报头,接收,httpClient,HttpClient
From: https://www.cnblogs.com/mq0036/p/17466665.html

相关文章

  • SpringMVC参数接收与数据响应
    SpringMVC如何接受请求参数(普通类型参数/对象类型参数/数组/json数据等)1、普通类型参数(1)在可以在方法参数上使用@RequestParam注解来绑定请求参数,此注解允许指定请求参数的名称,以及是否是必须传的参数。@RequestMapping("/example")publicStringexampleMethod(@RequestPara......
  • C#语言LIS系统如何接收和解析仪器数据HL7协议
    以下是使用C#实现HL7接口协议的接收和解析的简单示例:1.使用TcpListener类创建一个TCP服务器,用于监听指定端口上的连接请求:```TcpListenerserver=newTcpListener(IPAddress.Any,8888);server.Start();```2.等待客户端连接,并使用TcpClient类创建一个TCP连接:```TcpClie......
  • c++关于 左右值 和 左右值引用 及 函数参数(万能引用,引用折叠,forward完美转发)
    左右值和左右值引用是有区别的。左右值是指对变量类别的区分,左值是有地址的值,可以长期存在;而右值是将亡值,是临时量,没有名字。而左右值引用是指变量的类型,如int&,int&&等,下面举一个例子:voidfunc(int&p){cout<<"&p"<<endl;return;}voidfunc(int&&p){......
  • 获取nginx转发前的真实ip
    有时,需要获取客户端真实ip,但由于nginx转发后,已经不是真实ip地址解决方式如下:在你的nginx配置下加上如下配置即可location/{proxy_passhttp://127.0.0.1:8080;//转发到你的业务服务器服务器proxy_set_headerx-forwarded-for$remote_addr;//把真实ip设置到......
  • 雷达原理与系统 第十五讲 接收机的动态范围和增益控制
    雷达原理与系统第十五讲接收机的动态范围和增益控制0.改善——1).现代接收机对于大的动态范围$D$的要求很高。2).动态范围是衡量接收机性能的重要指标,表示了接收机能正常工作所允许的输入信号的强度的范围。3).为了防止强信号引起的系统过载,有需要增益控制电路,实现更大......
  • 请求转发和重定向
    1. 请求转发和重定向  52当处理器对请求处理完毕后,向其它资源进行跳转时,有两种跳转方式:请求转发与重定向。而根据所要跳转的资源类型,又可分为两类:跳转到页面与跳转到其它处理器。注意,对于请求转发的页面,可以是WEB-INF中页面;而重定向的页面,是不能为WEB-INF中页的。因为重定向相当......
  • 记一次线上问题,Netty接收到的报文一次有数据一次没有数据
    最近线上遇到一个问题,客户端发送的tcp报文第一次连接成功后没有数据,第二次连接后正常带数据,第三次又没有数据...问题排查1:是否有负载均衡,其中有一台机器出现了异常,会出现一次成功一次失败的情况经过排查,本服务是没有负载均衡的,排除问题排查2:抓包分析 根据抓包数据,异常情况时......
  • springmvc后端接收前端数据的四种方式
    前端登录页面代码<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><title>Title</title></head><body><h1>登录页面</h1><formaction="login.do"......
  • 解决串口接收数据不完整
    C#解决串口接收数据不完整1. 使用缓存机制完成:首先通过定义一个成员变量List<byte>buffer=newList<byte>(4096);用来存放所有的数据,在接收函数里,通过buffer.AddRange()方法不断将接收到的数据加入到buffer中,并同时对buffer的数据进行校验,如果达到一定的长度并且校验结果正......
  • Effective Modern C++(四)再探移动语义与完美转发
    移动语义移动语义是c++11最为重要的特性之一,但这不代表着我们可以在任何时候都无脑地使用它。在以下几个情况下,移动语义并没有什么用处。没有移动操作:要移动的对象没有提供移动操作,所以移动的写法也会变成复制操作。比如对于STL库中的array容器而言,他的元素都直接存储在了......