• 给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都达到了一样的效果。

  • 相关阅读:
    array with objects sort
    Vue组件之区域滚动
    ajax跨域请求问题总结
    常见contentType
    Sublime Text 3实用快捷键大全
    具有层级关系的扁平化数组转化成树形结构数组
    阿里云地图选择器
    前端解析二进制文件流并下载
    tool.js日常工具方法
    git操作
  • 原文地址:https://www.cnblogs.com/catcher1994/p/11397906.html
Copyright © 2020-2023  润新知