理解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方法实在重用了外部的高下文。