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

news2025/1/1 23:14:42

了解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”并不会被转发出去。

三、 为请求添加请求报头

当我们利用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会接收到三个同名的请求报头。

如果不希望出现这种情况下,可以将针对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设置的报头。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1049888.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

经过认证的工具链对安全关键型应用意味着什么?

作者:IAR 安全关键型应用,在很多人看来是个专业的词汇,但其实它离我们的日常生活很近,比如汽车驾驶系统、飞机控制系统、电梯运行系统、医疗设备等与我们息息相关的事物都可以纳入安全关键型应用的范畴。 对于这类应用&#xff…

SAP入门到放弃系列之QM检验计划-Part1

文章目录 一、概述1.1、检验计划抬头1.2、检验计划工序 二、系统操作2.1、测试数据准备:2.2、创建检验计划 一、概述 检验计划是用来描述如何对一种或多种物料进行质量检验操作的主数据。在检验计划中,可以定义检验的工序顺序以及可用于检验特征的数据规…

排序:归并(Merge)排序算法分析

1.归并操作 归并:把两个或多个已经有序的序列合并成一个。 2路归并:二合一k路归并:k合一结论:m路归并,每选出一个元素需要对比关键字m-1次。 2.算法思想 核心操作:把数组内的两个有序序列归并为一个。 例如: 3.代码实现 将…

数据集笔记: Porto

数据来源:Taxi Trajectory Data_数据集-阿里云天池 (aliyun.com) 1 数据介绍 葡萄牙波尔图市运行的所有442辆出租车的全年轨迹(从2013年7月1日至2014年6月30日) 2 读取数据 import pandas as pdtrapd.read_csv(C:/Users/16000/Download…

C语言中动态内存管理

前言:为什么存在动态内存分配,为什么要用动态内存分配,动态内存分配的意义。鸡汤:有了坚持不一定成功,但没有坚持,就注定失败,各位也要努力坚持提升自己! 动态内存分配 动态内存函数&#xff1a…

【Linux】 du 命令使用

问题 No space left on device 请求接口返回 java.io.IOException: No space left on device 设备上没有剩余空间 怎么解决问题: 查看这篇文章:一次 linux 服务器磁盘使用情况排查 我们提到命令:du -sh * 到底这个命令是干什么的咱们…

EXPLAIN 语句输出的各个列解释

title: “EXPLAIN 语句输出的各个列解释” createTime: 2022-03-06T15:52:4108:00 updateTime: 2022-03-06T15:52:4108:00 draft: false author: “ggball” tags: [“mysql”] categories: [“db”] description: “” EXPLAIN 语句输出的各个列解释 列名描述id在一个大的查…

多线程(基础)

文章目录 1. 线程的声明周期1.1 JDK 中用 Thread.State 枚举表示了线程的几种状态1.2 线程状态转换图 2. 线程的同步2.1 Synchronized 线程同步机制 3. 互斥锁3.1 注意事项和细节3.2 守护线程 setDaemon()方法 4. 线程的死锁5. 释放锁6. 课后练习 1. 线程的声明周期 1.1 JDK 中…

四通道信息融合下的齿轮箱故障诊断(Python代码,SVM模型和CNN模型进行对比实验,解压缩即可运行,有详细中文注释)

1.效果运行视频:四通道信息融合下的齿轮箱故障诊断(Python代码,SVM模型和CNN模型进行对比实验)_哔哩哔哩_bilibili 用到的库: 2.数据集介绍:数据免费下载链接(不要积分)&#xff1a…

什么是跨站请求伪造(CSRF)攻击?如何防止它?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 什么是跨站请求伪造(CSRF)攻击?⭐ 如何防止CSRF攻击?⭐ 写在最后 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦…

xxl-job 2.2之后版本高版本executor未授权访问漏洞

xxl-job 低版本executor未授权访问 低版本的executor未授权访问漏洞是 POST /run HTTP/1.1 Host: your-ip:9999 Accept-Encoding: gzip, deflate Accept: */* Accept-Language: en User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like G…

26663-2011 大型液压安全联轴器 课堂随笔

声明 本文是学习GB-T 26663-2011 大型液压安全联轴器. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 1 范围 本标准规定了大型液压安全联轴器的分类、技术要求、试验方法及检验规则等。 本标准适用于联接两同轴线的传动轴系,可起到限制…

基于Linux 系统聊天室登录与注册实现(03)

上一篇我们已经讲了如何搭建一个多线程的服务器模型,可以支持多个客户端同时连接服务器,本篇我们来实现多个客户端,如何实现向服务器注册信息,并实现登录的功能。 数据结构 接着上一篇的实例代码继续增加功能。要实现注册和登录…

(Java)关于easyExcel合并单元格

今天过客遇到一个需要合并单元格的业务,但是之前过客用的一直是easyExcel框架,所以这次也不想去使用其他的框架,今天就跟大家讲讲easyExcel怎么进行单元格的合并。 首先使用easyExcel进行导出的实体类一样是依据之前那样写,之后在…

宠物玩具在欧洲销售CE认证EN71测试标准

CE认证的EN71测试宠物玩具办理: 宠物玩具是用来给宠物玩耍,基于将宠物作为人类伙伴关系而诞生的一种玩具类型,这种玩具的存在就是让人类和自己的宠物真正的互动起来,在情感上面得到更大的交流和互动。 那么宠物玩具出口到欧盟市场…

支付宝电脑网站支付,异步通知

一:异步通知是支付宝回调商户的服务器,所以这个地址需要通过外网访问,在真实项目中都会有对应的服务器,但是在测试中只有使用内网穿透工具 推荐使用NATAPP-内网穿透 基于ngrok的国内高速内网映射工具 配置好内网穿透之后不要忘记…

目标检测如何演变:从区域提议和 Haar 级联到零样本技术

目录 一、说明 二、目标检测路线图 2.1 路线图(一般) 2.2 路线图(更传统的方法) 2.3 路线图(深度学习方法) 2.4 对象检测指标的改进 三、传统检测方法 3.1 维奥拉-琼斯探测器 (2001) 3.2 HOG探测器…

最大内切圆算法计算裂缝宽度

本文这里是对CSDN上另一位博主的代码进行了整理: 基于opencv的裂缝宽度检测算法(计算轮廓最大内切圆算法) 我觉得这位博主应该是上传了一个代码草稿,我对其进行了重新整理,并添加了详细的注释。 import cv2 import …

产业园区中工业厂房的能源综合配置

安科瑞 崔丽洁 园区工业地产中能源综合配置存在的问题 我国园区工业地产建设已历经近40年的发展, 园区在区域经济发展、产业集聚方面发挥了重要的载体和平台作用, 有力推动了我国社会经济的高质量发展。园区工业地产是国民经济的发展的重要载体, 但同时也是集中的环境污染源。…

大数据Doris(一):Doris概述篇

文章目录 Doris概述篇 一、前言 二、Doris简介