HttpClient自定义拦截器以实现日志记录

集成DelegatingHandler类,实现SendAsync方法即可将自定义逻辑注入到HTTP请求和响应处理管道中。

第一步:实现自定义拦截逻辑

以下拦截器实现了当请求发生异常时,记录异常信息到日志文件的功能

/// <summary>
/// 自定义的 HttpMessageHandler,用于处理 HTTP 请求中的已知异常。
/// - 捕获并记录 HTTP 请求中的异常。
/// - 对非成功状态码的响应进行处理,并抛出自定义的 KnownException。
/// </summary>
/// <param name="logger">用于记录日志的 ILogger 实例。</param>
public class KnownExceptionDelegatingHandler(ILogger<KnownExceptionDelegatingHandler> logger) : DelegatingHandler
{
    /// <summary>
    /// 重写 SendAsync 方法,拦截 HTTP 请求和响应。
    /// </summary>
    /// <param name="request">HTTP 请求消息。</param>
    /// <param name="cancellationToken">取消令牌。</param>
    /// <returns>HTTP 响应消息。</returns>
    /// <exception cref="KnownException">
    /// 当发生异常或响应状态码非成功时,抛出自定义的 KnownException。
    /// </exception>
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        HttpResponseMessage message;
        try
        {
            // 调用管道中的下一个处理器,发送 HTTP 请求。
            message = await base.SendAsync(request, cancellationToken);
        }
        catch (Exception e)
        {
            // 捕获异常并记录日志。
            logger.LogError(e, "调用服务出错:{url}, HttpRequestException:{message}",
                request.RequestUri,
                e.Message);

            // 抛出自定义的 KnownException,表示发生了未知错误。
            throw new KnownException("未知错误");
        }

        // 检查响应状态码是否为成功状态码(2xx)。
        if (!message.IsSuccessStatusCode)
        {
            // 尝试从响应内容中反序列化为 ResponseData 对象。
            var data = await message.Content.ReadFromJsonAsync<ResponseData>(cancellationToken: cancellationToken);
            if (data != null)
            {
                // 如果反序列化成功,抛出包含详细错误信息的 KnownException。
                throw new KnownException(data.Message, data.Code, data.ErrorData.ToArray());
            }
            else
            {
                // 如果反序列化失败,抛出包含响应原因的 KnownException。
                throw new KnownException(message.ReasonPhrase ?? "未知错误");
            }
        }

        // 如果状态码为成功,返回响应消息。
        return message;
    }
}

第二步:注册

注册到容器:builder.Services.AddTransient<KnownExceptionDelegatingHandler>();
注册HttpClient:services.AddHttpClient()
添加拦截器到HttpClient管道中:builder.AddHttpMessageHandler<KnownExceptionDelegatingHandler>();

单元测试

[Fact]
public async Task AddKnownExceptionDelegatingHandlerTest()
{
	// Arrange
	var services = new ServiceCollection();
	services.AddTransient<KnownExceptionDelegatingHandler>();
	services.AddHttpClient("test")
	.AddTransient<KnownExceptionDelegatingHandler>();
	var provider = services.BuildServiceProvider();
	var httpClientFactory = provider.GetRequiredService<IHttpClientFactory>();
	var httpClient = httpClientFactory.CreateClient("test");
	
	// Act
	var ex = await Assert.ThrowsAsync<KnownException>(() => httpClient.GetAsync("http://localhost/500"));

	// Assert
	Assert.Equal("未知错误", ex.Message);
}
posted @ 2025-04-16 22:47  南山有榛  阅读(127)  评论(0)    收藏  举报