百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

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

bigegpt 2024-09-11 01:04 3 浏览

了解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报头。

二、 屏蔽自动转发功能

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方法其实重用了外部的上下文。

相关推荐

最全的MySQL总结,助你向阿里“开炮”(面试题+笔记+思维图)

前言作为一名编程人员,对MySQL一定不会陌生,尤其是互联网行业,对MySQL的使用是比较多的。对于求职者来说,MySQL又是面试中一定会问到的重点,很多人拥有大厂梦,却因为MySQL败下阵来。实际上...

Redis数据库从入门到精通(redis数据库设计)

目录一、常见的非关系型数据库NOSQL分类二、了解Redis三、Redis的单节点安装教程四、Redis的常用命令1、Help帮助命令2、SET命令3、过期命令4、查找键命令5、操作键命令6、GET命...

netcore 急速接入第三方登录,不看后悔

新年新气象,趁着新年的喜庆,肝了十来天,终于发了第一版,希望大家喜欢。如果有不喜欢看文字的童鞋,可以直接看下面的地址体验一下:https://oauthlogin.net/前言此次带来得这个小项目是...

精选 30 个 C++ 面试题(含解析)(c++面试题和答案汇总)

大家好,我是柠檬哥,专注编程知识分享。欢迎关注@程序员柠檬橙,编程路上不迷路,私信发送以下关键字获取编程资源:发送1024打包下载10个G编程资源学习资料发送001获取阿里大神LeetCode...

Oracle 12c系列(一)|多租户容器数据库

作者杨禹航出品沃趣技术Oracle12.1发布至今已有多年,但国内Oracle12C的用户并不多,随着12.2在去年的发布,选择安装Oracle12c的客户量明显增加,在接下来的几年中,Or...

flutter系列之:UI layout简介(flutter-ui-nice)

简介对于一个前端框架来说,除了各个组件之外,最重要的就是将这些组件进行连接的布局了。布局的英文名叫做layout,就是用来描述如何将组件进行摆放的一个约束。在flutter中,基本上所有的对象都是wi...

Flutter 分页功能表格控件(flutter 列表)

老孟导读:前2天有读者问到是否有带分页功能的表格控件,今天分页功能的表格控件详细解析来来。PaginatedDataTablePaginatedDataTable是一个带分页功能的DataTable,...

Flutter | 使用BottomNavigationBar快速构建底部导航

平时我们在使用app时经常会看到底部导航栏,而在flutter中它的实现也较为简单.需要用到的组件:BottomNavigationBar导航栏的主体BottomNavigationBarI...

Android中的数据库和本地存储在Flutter中是怎样实现的

如何使用SharedPreferences?在Android中,你可以使用SharedPreferencesAPI来存储少量的键值对。在Flutter中,使用Shared_Pref...

Flet,一个Flutter应用的实用Python库!

▼Flet:用Python轻松构建跨平台应用!在纷繁复杂的Python框架中,Flet宛如一缕清风,为开发者带来极致的跨平台应用开发体验。它用最简单的Python代码,帮你实现移动端、桌面端...

flutter系列之:做一个图像滤镜(flutter photo)

简介很多时候,我们需要一些特效功能,比如给图片做个滤镜什么的,如果是h5页面,那么我们可以很容易的通过css滤镜来实现这个功能。那么如果在flutter中,如果要实现这样的滤镜功能应该怎么处理呢?一起...

flutter软件开发笔记20-flutter web开发

flutterweb开发优势比较多,采用统一的语言,就能开发不同类型的软件,在web开发中,特别是后台式软件中,相比传统的html5开发,更高效,有点像c++编程的方式,把web设计出来了。一...

Flutter实战-请求封装(五)之设置抓包Proxy

用了两年的flutter,有了一些心得,不虚头巴脑,只求实战有用,以供学习或使用flutter的小伙伴参考,学习尚浅,如有不正确的地方还望各路大神指正,以免误人子弟,在此拜谢~(原创不易,转发请标注来...

为什么不在 Flutter 中使用全局变量来管理状态

我相信没有人用全局变量来管理Flutter应用程序的状态。毫无疑问,我们的Flutter应用程序需要状态管理包或Flutter的基本小部件(例如InheritedWidget或St...

Flutter 攻略(Dart基本数据类型,变量 整理 2)

代码运行从main方法开始voidmain(){print("hellodart");}变量与常量var声明变量未初始化变量为nullvarc;//未初始化print(c)...