給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都達到了一樣的效果。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM