如今的应用部署逐渐向微服务化发展,导致一个完整的事务往往会跨越很多的应用或服务,出于分布式链路跟踪的需要,我们往往将从上游服务获得的跟踪请求报头无脑地向下游服务进行转发。本文介绍的这个名为HeaderForwarder的组件可以帮助我们完成针对指定HTTP请求报头的自动转发。本篇文章分为上下两篇,上篇通过三个例子介绍HeaderForwarder的应用场景,下篇则介绍该组件的设计与实现。

目录

一、自动转发指定的请求报头

二、添加任意需要转发的请求报头

三、在非ASP.NET Core应用中使用

一、自动转发指定的请求报头

假设整个分布式调用链路由如下图所示的三个应用构成。请求由控制台应用App1通过HttpClient向WebApp1(localhost:5000),该请求携带foo和bar两个需要被转发的跟踪报头。ASP.NET Core应用WebApp1在通过HttpClient调用WebApp2时,我们的组件会自动实现这对这两个请求报头的转发。

如下所示的是作为下游应用的WebApp2的定义。如代码片段所示,为了验证指定的跟踪报头是否在WebApp1中被我们的组件成功转发,我们将接收到的所有请求报头拼接成一个字符串作为响应内容。

public class Program
{
public static void Main()
{
Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(web => web.Configure(app=>app.Run(Process)))
.Build()
.Run(); static Task Process(HttpContext httpContext)
{
var headerString = string.Join(";", httpContext.Request.Headers.Select(it => $"{it.Key}={it.Value}"));
return httpContext.Response.WriteAsync(headerString);
}
}
}

WebApp1的所有代码定义如下。HeaderForwarder组件通过调用IHostBuilder的扩展方法UseHeaderForwarder进行注册,在调用该方法的时候我们指定了需要转发的请求报头名称(foo和bar)。在接收到请求之后,WebApp1会利用HttpClient调用WebApp2,并将得到结果作为相应的内容。

public class Program
{
public static void Main()
{
Host.CreateDefaultBuilder()
.UseHeaderForwarder(forwarder=>forwarder.AddHeaderNames("foo", "bar"))
.ConfigureWebHostDefaults(web => web
.ConfigureServices(svcs=>svcs.AddHttpClient())
.Configure(app => app.Run(Process)))
.Build()
.Run(); static async Task Process(HttpContext httpContext)
{
var httpClient = httpContext.RequestServices.GetRequiredService<IHttpClientFactory>().CreateClient();
var headerString = await httpClient.GetStringAsync("http://localhost:6000");
await httpContext.Response.WriteAsync(headerString);
}
}
}

作为上游应用的App具有如下所示的定义。它直接利用HttpClient向WebApp1发送了一个请求,该请求携带了foo和bar这两个需要WebApp1转发的报头。如果WebApp1完成了针对这两个请求报头的转发,那么得到的响应内容将包含这两个报头的值,我们将这一验证逻辑体现在两个调试断言中。

class Program
{
static async Task Main(string[] args)
{
var httpClient = new HttpClient();
var request = new HttpRequestMessage
{
RequestUri = new Uri("http://localhost:5000"),
Method = HttpMethod.Get
};
request.Headers.Add("foo", "123");
request.Headers.Add("bar", "456");
var response = await httpClient.SendAsync(request);
var headers = (await response.Content.ReadAsStringAsync()).Split(";");
Debug.Assert(headers.Contains("foo=123"));
Debug.Assert(headers.Contains("bar=456"));

}
}

二、添加任意需要转发的请求报头

上面我们演示了HeaderForwarder组件自动提取指定的报头并自动转发的功能,实际上该组件还可以帮助我们将任意的报头添加到由HttpClient发出的请求消息中。假设WebApp1除了自动转发的foo和bar报头之外,还需要额外添加一个baz报头,我们可以对程序作如下的修改。

public class Program
{
public static void Main()
{
Host.CreateDefaultBuilder()
.UseHeaderForwarder(forwarder => forwarder.AddHeaderNames("foo", "bar"))
.ConfigureWebHostDefaults(web => web
.ConfigureServices(svcs => svcs.AddHttpClient())
.Configure(app => app.Run(Process)))
.Build()
.Run(); static async Task Process(HttpContext httpContext)
{
using (new HttpInvocationContextScope())
{
HttpInvocationContext.Current.AddOutgoingHeader("baz", "789");
var httpClient = httpContext.RequestServices.GetRequiredService<IHttpClientFactory>().CreateClient();
var headerString = await httpClient.GetStringAsync("http://localhost:6000");
await httpContext.Response.WriteAsync(headerString);
}
}
}
}

如上面的代码片段所示,我们将针对HttpClient的调用置于HttpInvocationContextScope对象构建的上下文范围中。在调用HttpClient发送请求之前,我们通过Current静态属性得到当前的HttpInvocationContext上下文,并通过调用其AddOutgoingHeader方法设置待转发的baz报头。为了验证WebApp1针对baz报头的转发,我们将App的程序进行如下的改写。

class Program
{
static async Task Main(string[] args)
{
var httpClient = new HttpClient();
var request = new HttpRequestMessage
{
RequestUri = new Uri("http://localhost:5000"),
Method = HttpMethod.Get
};
request.Headers.Add("foo", "123");
request.Headers.Add("bar", "456");
var response = await httpClient.SendAsync(request);
var headers = (await response.Content.ReadAsStringAsync()).Split(";");
Debug.Assert(headers.Contains("foo=123"));
Debug.Assert(headers.Contains("bar=456"));
Debug.Assert(headers.Contains("baz=789"));

}
}

如果涉及到多个HTTP调用都需要对相同的报头进行转发,上面介绍的这种基于HttpInvocationContextScope/HttpInvocationContext的编程模式会变得很方便。

using (new HttpInvocationContextScope())
{
HttpInvocationContext.Current
.AddOutgoingHeader("foo", "123")
.AddOutgoingHeader("bar", "456")
.AddOutgoingHeader("baz", "789");
var result1 = await httpClient.GetStringAsync("http://www.foo.com/");
var result2 = await httpClient.GetStringAsync("http://www.bar.com/");
var result3 = await httpClient.GetStringAsync("http://www.baz.com/");
}

三、在非ASP.NET Core应用中使用

在ASP.NET Core应用中,HeaderForwarder是通过调用IHostBuilder的扩展方法UseHeaderForwarder进行注册的,如果在控制台应用又该如何使用。其实很简单,HeaderForwarder针对请求(通过HttpClient发送)报头的添加是通过该注册提供的一个HttpClientObserver对象提供的,它实现了IObserver<DiagnosticListener>接口,我们只需要对该对象进行注册就可以了。

class Program
{
static async Task Main()
{
var httpClientObserver = new ServiceCollection()
.AddHeaderForwarder()
.BuildServiceProvider()
.GetRequiredService<HttpClientObserver>();
DiagnosticListener.AllListeners.Subscribe(httpClientObserver); using (new HttpInvocationContextScope())
{
HttpInvocationContext.Current
.AddOutgoingHeader("foo", "123")
.AddOutgoingHeader("bar", "456");
var headers = (await new HttpClient().GetStringAsync("http://locahost:5000")).Split(";");
Debug.Assert(headers.Contains("foo=123"));
Debug.Assert(headers.Contains("bar=456"));
Debug.Assert(headers.Contains("baz=789"));
}
}
}

如上面的代码片段所示,我们调用扩展方法AddHeaderForwarder将HeaderForwarder相关的服务注册到创建的ServiceCollection对象上,并利用构建的IServiceProvider对象得到我们需要的HttpClientObserver对象,并将其添加到DiagnosticListener.AllListeners属性的IObservable<DiagnosticListener>列表中。有了HttpClientObserver的加持,设置请求报头的方式就可以通过上述的编程模式了。

如何实现Http请求报头的自动转发[应用篇]
如何实现Http请求报头的自动转发[设计篇]

如何实现Http请求报头的自动转发[应用篇]的更多相关文章

  1. 被「李笑来老师」拉黑之「JavaScript微博自动转发的脚本」

    故事的背景如下图,李笑来 老师于10月19日在 知乎Live 开设 一小时建立终生受用的阅读操作系统 的讲座,他老人家看到大家伙报名踊跃,便在微博上发起了一个 猜数量赢取iPhone7 的活动. 因为 ...

  2. Response ServletContext 中文乱码 Request 编码 请求行 共享数据 转发重定向

    Day35  Response 1.1.1 ServletContext概念 u 项目的管理者(上下文对象),服务器启动时,会为每一个项目创建一个对应的ServletContext对象. 1.1.2  ...

  3. 技术揭秘“QQ空间”自动转发不良信息

    大家经常会看到QQ空间自动转发一些附带链接的不良信息,即便我们的QQ密码并没有被盗取.最近通过对一个QQ空间自动转发链接进行分析,发现该自动转发机制通过利用腾讯网站存在漏洞的页面,精心构造出利用代码获 ...

  4. http请求报头

    客户请求的处理:Http请求报头 创建高效servlet的关键之一,就是要了解如何操纵超文本传输协议(HypeText TransferProtocol, HTTP). HTTP请求报头不同于前一章的 ...

  5. HTTP报头:通用报头,请求报头,响应报头和实体报头

    缓存控制优先级从高到低分别是Pragma -> Cache-Control -> Expires 报头 每一个报头都是由 [名称 + ":" + 空格 + 值 + ] ...

  6. 四种为HttpClient添加默认请求报头的解决方案

    HttpClient在Web调用中具有广泛的应用,而为它添加默认请求头是我们经常遇到的需求,本文介绍4种为HttpClient添加默认请求头的方式. 第一种方式 直接在创建的HttpClient对象的 ...

  7. C#实现通过HttpWebRequest发送POST请求实现网站自动登陆

    C#实现通过HttpWebRequest发送POST请求实现网站自动登陆   怎样通过HttpWebRequest 发送 POST 请求到一个网页服务器?例如编写个程序实现自动用户登录,自动提交表单数 ...

  8. 用Tasker实现收到Android手机短信自动转发到邮箱

    发送短信到邮箱的原理与 <用Tasker实现收到Android手机短信自动转发到邮箱>有些类似.  发送短信到邮箱是利用Ifttt这个服务将短信转发到邮箱中.Ifttt服务的可扩展性很强, ...

  9. Exchange 2019中启用自动转发到外部域

    今天遇到一个用户反映自动转发的邮件规则没有生效.检查了一下,邮件规则配置没有问题.用户邮箱也能正常收到邮件,但是就是没有转发出去.仔细检查邮件规则,转发的收件人是外部邮箱.Exchange出于安全考虑 ...

随机推荐

  1. 多测师讲解selenium _ 获取input输入文本值_高级讲师肖sir

    1.get方法来获取到对应元素它的值 案例代码比如在输入框中输入666 driver.find_element_by_css_selector('#kw').send_keys('666')l =dr ...

  2. 串口wifi

    串口wifi 串口WiFi ZLAN7146是一款wifi转串口的wifi串口服务器.该串口服务器可以方便地使得串口设备连接到WIFI无线网络,实现串口设备的无线化网络升级.RS232接口支持全双工. ...

  3. 本地环境Django配置问题

    Django本地环境出现的问题 当你的前端出现这个问题的时候 你只需要吧setting.py 中的DEBUG 改为 True,即可 我原来是是DEBUG = False 本人亲测有用!!!

  4. python面试题-django相关

    1.中间件 中间件一般做认证或批量请求处理,django中的中间件,其实是一个类,在请求和结束后,django会根据自己的规则在合适的时机执行中间件中相应的方法, 如请求过来 执行process_re ...

  5. SE第一次作业

    作业一.对软件工程的初步认识 下面是我对于软件工程的认识,结合自己的理解和课上听讲的内容 软件工程=软件+工程?软件工程是否就是简单的软件+工程呢?那么我们先来看下各自的概念. 那么什么叫软件呢,既然 ...

  6. 千万不要往 Shell 里粘贴命令!

    对于用惯了 IDE 的程序员来说,在终端里敲命令可能没那么顺手,也记不住那么多复杂的命令.比较偷懒的做法就是网上搜相关的命令,复制到剪贴板往命令行窗口里一贴,完事! 但是这么做有很大的风险,为什么呢? ...

  7. git删除缓存区中文件

    删除缓冲区中的文件 git rm --cached "文件路径",不删除物理文件,仅将该文件从缓存中删除: git rm --f "文件路径",不仅将该文件从缓 ...

  8. 关于天线长度及LC值的计算

    一.天线长度与波长 1.天线最佳长度计算 理论和实践证明,当天线的长度为无线电信号波长的1/4时,天线的发射和接收转换效率最高.因此,天线的长度将根据所发射和接收信号的频率即波长来决定.只要知道对应发 ...

  9. SpringBook+Lombok 使用教程

    什么是Lombok? Lombok项目是一个Java库,它会自动插入编辑器和构建工具中,Lombok提供了一组有用的注释,用来消除Java类中的大量样板代码. Lombok也存在一定风险,在一些开发工 ...

  10. ERROR: No matching distribution found for cv2

    ImportError: No module named cv2和No matching distribution found for cv2的问题 原因 这个是由于没有导入opencv库导致的 解决 ...