HTTPClient自定义DelegatingHandler拦截器要点

        以前是写前端的,感觉前端的类似axios的拦截器很方便,可以在请求的发送前后做一些通用化的操作,现在以为工作原因刚刚接触.NET,在.NET中请求库中经过挑选最后选择了HTTPClient这个类库,就在想有没有类似axios拦截库一样的功能,在查了一下官网文档,是推荐在应用程序的入口处就using,这样在整个应用周期中都可以使用HTTPClient。

        先贴一下应用入口的代码:

        /**
         * 使用HttpClinet实例
         */
        public static readonly HttpClient httpClient = new HttpClient();

        以上代码是最简单的引入HTTPClient请求库,现在贴一下简单版本的DelegatingHandler拦截器代码:

public class CustomDelegatingHandler : DelegatingHandler
    {
        private readonly TimeSpan _timeout;

        public CustomDelegatingHandler(TimeSpan timeout)
        {
            _timeout = timeout;
        }

        // 请求前拦截
        protected async Task<HttpRequestMessage> InterceptRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            Console.WriteLine($"Sending request to {request.RequestUri}");
            return request;
        }

        // 响应后拦截
        protected async Task<HttpResponseMessage> InterceptResponseAsync(HttpResponseMessage response, CancellationToken cancellationToken)
        {
            Console.WriteLine($"Received response with status code {response.StatusCode}");
            if (response.StatusCode == System.Net.HttpStatusCode.OK)
            {
                return response;
            }
            else if (response.StatusCode == System.Net.HttpStatusCode.InternalServerError)
            {
                Console.WriteLine("响应返回了状态码 500 - Internal Server Error");
                MessageBox.Show("未接收到正确返回信息。", "提示信息", MessageBoxButton.OK, MessageBoxImage.Information);
            }
            else
            {
                Console.WriteLine($"响应返回了其他状态码: {(int)response.StatusCode}");
                MessageBox.Show($"接收到错误返回信息{(int)response.StatusCode},请联系信息科。", "提示信息", MessageBoxButton.OK, MessageBoxImage.Information);
            }
            return response;
        }

        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            // 请求前拦截
            var interceptedRequest = await InterceptRequestAsync(request, cancellationToken);
            // 创建一个链接到原始cancellationToken的新CancellationTokenSource
            using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
            {
                // 设置超时时间(例如30秒)
                linkedCts.CancelAfter(TimeSpan.FromSeconds(30));

                HttpResponseMessage response = null;
                try
                {
                    // 使用linkedCts.Token而不是原始的cancellationToken
                    response = await base.SendAsync(interceptedRequest, linkedCts.Token);
                    response.EnsureSuccessStatusCode();
                }
                catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
                {
                    // 如果超时发生
                    throw new TimeoutException("The request timed out.");
                }
                catch (Exception)
                {
                    throw; // 其他异常保持原样抛出
                }
                // 响应后拦截
                return await InterceptResponseAsync(response, linkedCts.Token);
            }
        }
    }

         然后再在将应用入口的引入代码更改为:

public static readonly HttpClient httpClient = new HttpClient(new CustomDelegatingHandler(TimeSpan.FromSeconds(10)));

         这里有一个非常重要的踩坑点需要注意,在实际运用程序的时候,会在response = await base.SendAsync(interceptedRequest, linkedCts.Token);卡住没有反应,需要将入口处的引入修改成如下所示就能解决。

        public static readonly HttpClient httpClient = new HttpClient(new CustomDelegatingHandler(TimeSpan.FromSeconds(10))
        {
            InnerHandler = new HttpClientHandler()
        });

        关于为什么会卡住,猜测是因为base.SendAsync 的调用是异步的,但是在拦截器中,无法正确的接收到这个异步方法的完成情况,所以就导致了循环请求的感觉,这个只是我目前的个人猜测,请各位大佬予以指教。