如今的应用部署逐渐向微服务化发展,导致一个完整的事务往往会跨越很多的应用或服务,出于分布式链路跟踪的需要,我们往往将从上游服务获得的跟踪请求报头无脑地向下游服务进行转发。本文介绍的这个名为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. Java第一课!

    public class Text { public static void main(String[] args) { int a=100; //赋值a=100 System.out.println ...

  2. echo 输出颜色

    shell脚本里使用echo输出颜色       echo命令颜色显示: echo:      -n:  不换行.      -e:让转移符生效. \t(tab) \n (换行) 实例: $ echo ...

  3. 论文学习笔记 - Classifification of Hyperspectral and LiDAR Data Using Coupled CNNs

    Classifification of Hyperspectral and LiDAR Data Using Coupled CNNs 来源:IEEE TGRS 2020 下载:https://arx ...

  4. eclipse快捷键(Mac版)整理

    eclipse快捷键(Mac版)整理 前言: 最近在学习JDBC,老师用的是eclipse,对于习惯了IDEA的我来说,没有了快捷键,效率明显下降. 我认为,开发工具的熟练使用,也是程序员必备的一项技 ...

  5. 联赛模拟测试24 B. 答题 折半枚举

    题目描述 分析 暴力的思想是把 \(2^n\) 种得分枚举出来,每一种得分的概率都是相同的,然后从小到大累加,直到大于等于所给的概率 把问题转化一下,就变成了在 \(2^n\) 种元素中求 \(k\) ...

  6. 谈谈Android项目框架的前世今生

    嗨,大家好,今天出了大太阳,真是美好的开始. 这篇文章和大家说说Android届流行的三大框架,了解下架构的前世今生,以及我对于这些框架的一些认识和看法. 三大框架区别 MVC 架构介绍 Model: ...

  7. 卸载联软UniAccess,删除UniAccess Agent记录

    UniAccess 卸载 事情起因: 公司假以安全上网为由,让公司员工安装所谓的"XX上网助手",实则是内嵌了联软的UniAccess监控系统. 有关这个软件的用途就不用多介绍了, ...

  8. 4g工业路由器的覆盖范围分析

    4G工业路由器通常覆盖范围在60-80米之间,而影响4G工业路由器的覆盖范围有以下几个因素. 影响4g工业路由器覆盖范围的因素一:环境 空旷的环境下4g工业路由器的信号覆盖范围必然更加广阔,如果传输环 ...

  9. 4G DTU的使用方法和应用领域

    4G DTU是一种数据传输单元,通俗理解就是,用来传输数据的一种硬件.既然是用来传输数据的,那就能将它视为一个管道,也就是说,指令同过它传给设备,而管道是不对这些指令做出响应的. 4G DTU如何使用 ...

  10. Linux 环境编程:dirfd参数 有关解析

    背景 在Unix环境编程中,系统提供了很多以at结尾的函数,如openat.fstatat等,而这类函数通常有一个特点,就是形参列表中多了int dirfd 例如: int open(const ch ...