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

    如何解决Win7,win8无法使用DOS的Debug: 安装dosbox 将含有程序link,masm,edit,debug的文件夹masm放到d盘根目录 打开dosbox,输入mount c d:\ ...

  2. WebFlux快速上手

    一.新建项目 示例使用IDEA快速创建基于SpringBoot的工程. springboot 2.3.1 java 8 WebFlux 必须选用Reactive的库 POM 依赖 <depend ...

  3. spring boot:spring security给用户登录增加自动登录及图形验证码功能(spring boot 2.3.1)

    一,图形验证码的用途? 1,什么是图形验证码? 验证码(CAPTCHA)是"Completely Automated Public Turing test to tell Computers ...

  4. matplotlib条形图

    三个班级平均分 import matplotlib.pyplot as plt import matplotlib as mpl classes = ['class1','class2','class ...

  5. Java9系列第6篇-Stream流API的增强

    我计划在后续的一段时间内,写一系列关于java 9的文章,虽然java 9 不像Java 8或者Java 11那样的核心java版本,但是还是有很多的特性值得关注.期待您能关注我,我将把java 9 ...

  6. <!DOCTYPE>,<address>,<applet>的用法

    希望以下内容能让大家有所收获 HTML <!DOCTYPE> 标签 实例 <!DOCTYPE html> <html> <head> <title ...

  7. jmeter环境变量配置

    参考博客:超全 https://blog.csdn.net/qq_39720249/article/details/80721777

  8. java 保留小数点后指定位数四种方法

    1 package com.itheima_01; 2 3 import java.math.BigDecimal; 4 import java.text.DecimalFormat; 5 impor ...

  9. Spring 事件监听

    Spring 的核心是 ApplicationContext,它负责管理 Bean的完整生命周期:当加载 Bean 时,ApplicationContext 发布某些类型的事件:例如,当上下文启动时, ...

  10. swd composer.json

    { "require": { "php-amqplib/php-amqplib": "2.6.*", "tmtbe/swooled ...