给HttpClient添加请求头(HttpClientFactory)
前言
在微服务的大环境下,会出现这个服务调用这个接口,那个接口的情况。假设出了问题,需要排查的时候,我们要怎么关联不同服务之间的调用情况呢?换句话就是说,这个请求的结果不对,看看是那里出了问题。
最简单的思路应该就是请求头加一个标识,从头贯穿到尾,这样我们就可以知道,对于这一个请求,在不同的服务都经历了什么样的过程。
在.NET Core时代,相信大部分都是在用HttpClientFactory来创建HttpClient,然后在发起请求。
这篇短文就简单介绍一下如何实现。
示例
我们先定义一个自己的DelegatingHandler,这里取名为HeadersPropagationDelegatingHandler
代码如下:
public class HeadersPropagationDelegatingHandler : DelegatingHandler { private readonly IHttpContextAccessor _accessor; public HeadersPropagationDelegatingHandler(IHttpContextAccessor accessor) { _accessor = accessor; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var traceId = string.Empty; if (_accessor.HttpContext.Request.Headers.TryGetValue("traceId", out var tId)) { traceId = tId.ToString(); Console.WriteLine($"{traceId} from request {DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}."); } else { traceId = System.Guid.NewGuid().ToString("N"); _accessor.HttpContext.Request.Headers.Add("traceId", new Microsoft.Extensions.Primitives.StringValues(traceId)); Console.WriteLine($"{traceId} from generated {DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}."); } if (!request.Headers.Contains("trace-id")) { request.Headers.TryAddWithoutValidation("traceId", traceId); } return await base.SendAsync(request, cancellationToken); } }
应该不用太多解释,就是在HttpClient发起请求之前,给它加多一个请求头,这个请求头的值要么是从上一个请求的请求头中取,要么就是重新生成一个。
下面就是主角IHttpMessageHandlerBuilderFilter出场了,它只是一个接口,我们需要自己去实现里面的Configure。
简单的示例如下:
public class HeadersPropagationMessageHandlerBuilderFilter : IHttpMessageHandlerBuilderFilter { private readonly IHttpContextAccessor httpContextAccessor; public HeadersPropagationMessageHandlerBuilderFilter(IHttpContextAccessor httpContextAccessor) { this.httpContextAccessor = httpContextAccessor; } public Action<HttpMessageHandlerBuilder> Configure(Action<HttpMessageHandlerBuilder> next) { if (next == null) { throw new ArgumentNullException(nameof(next)); } return (builder) => { next(builder); builder.AdditionalHandlers.Add(new HeadersPropagationDelegatingHandler(httpContextAccessor)); }; } }
万事具备,下面我们只需要在Startup中进行注入即可。
public void ConfigureServices(IServiceCollection services) { services.AddHttpContextAccessor(); services.AddTransient<Ext.HeadersPropagationDelegatingHandler>(); services.AddSingleton<IHttpMessageHandlerBuilderFilter, Ext.HeadersPropagationMessageHandlerBuilderFilter>(); services.AddHttpClient(); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); }
最后就是调用看看效果,这里为了简单,选择创建多个路由,用路由间发起HTTP请求来模拟。当然,最好的还是多个项目模拟。
示例如下:
[Route("api/[controller]")] [ApiController] public class ValuesController : ControllerBase { private readonly IHttpClientFactory _clientFactory; public ValuesController(IHttpClientFactory clientFactory) { this._clientFactory = clientFactory; } // GET api/values [HttpGet] public async Task<string> GetAsync() { var traceId = string.Empty; if (Request.Headers.TryGetValue("traceId", out var tId)) { traceId = tId.ToString(); Console.WriteLine($"{traceId} from request {DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}."); } else { traceId = System.Guid.NewGuid().ToString("N"); Request.Headers.Add("traceId", new Microsoft.Extensions.Primitives.StringValues(traceId)); Console.WriteLine($"{traceId} from generated {DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}."); } using (HttpClient client = new HttpClient()) { client.DefaultRequestHeaders.Clear(); client.DefaultRequestHeaders.TryAddWithoutValidation("traceId", traceId); var res = await client.GetAsync("http://localhost:9898/api/values/demo1"); var str = await res.Content.ReadAsStringAsync(); Console.WriteLine($"{traceId} demo1 return {str} at {DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}"); return str; } } // GET api/values/demo1 [HttpGet("demo1")] public async Task<string> GetDemo1() { var client = _clientFactory.CreateClient("demo2"); var res = await client.GetAsync("http://localhost:9898/api/values/demo2"); var str = await res.Content.ReadAsStringAsync(); return str; } // GET api/values/demo2 [HttpGet("demo2")] public async Task<string> GetDemo2() { var client = _clientFactory.CreateClient("demo3"); var res = await client.GetAsync("http://localhost:9898/api/values/demo3"); var str = await res.Content.ReadAsStringAsync(); return str; } // GET api/values/demo3 [HttpGet("demo3")] public ActionResult<string> GetDemo3() { return "demo3"; } // GET api/values/demo4 [HttpGet("demo4")] public async Task<string> GetDemo4() { var client = _clientFactory.CreateClient("demo1"); var res = await client.GetAsync("http://localhost:9898/api/values/demo3"); var str = await res.Content.ReadAsStringAsync(); var traceId = string.Empty; if (Request.Headers.TryGetValue("traceId", out var tId)) traceId = tId.ToString(); Console.WriteLine($"{traceId} demo3 return {str} at {DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}"); return str; } }
先访问
api/values
再访问
api/values/demo4
可以看到下面的结果。
可以看到用传统的方法和用HttpClientFactory都达到了一样的效果。

如果您认为这篇文章还不错或者有所收获,可以点击右下角的【推荐】按钮,因为你的支持是我继续写作,分享的最大动力!
作者:Catcher Wong ( 黄文清 )
来源:http://catcher1994.cnblogs.com/
声明:
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果您发现博客中出现了错误,或者有更好的建议、想法,请及时与我联系!!如果想找我私下交流,可以私信或者加我微信。