如何为每个请求设置AllowAutoRedirect?在多线程环境中共享HttpClient

本文关键字:多线程 环境 HttpClient 共享 AllowAutoRedirect 设置 请求 | 更新日期: 2023-09-27 18:28:15

我对HttpClient有点困惑。目前,我正在整个应用程序中使用此类的单个实例。

然而,它似乎不允许我为某些请求设置AllowAutoRedirect选项。

HttpClient是否被设计为针对每一个其他请求进行实例化?我说的是多线程环境。也许我应该用更灵活的类结构来包装它?

如何为每个请求设置AllowAutoRedirect?在多线程环境中共享HttpClient

若要更改每个请求的任何属性,那么是的,您将需要创建HttpClient的新实例。显然,如果您正在构建一个高性能应用程序,由于多次创建对象而产生了许多请求,那么性能和资源可能会起到一定的作用。

如果您真的走这条路,为HttpClient创建一个包装类将是有益的,并且在任何情况下,如果您希望更改为单个实例,您都可以将HttpClient作为参数传递。

在大多数情况下,HttpClient应该用作一个实例,因为您可以调用任意多的请求,并且该对象具有处理异步和响应的所有工具。这对于请求类型WebRequestHttpWebRequest是相同的。

如果您需要在启动另一个实例之前收到200 OK状态,那么与创建自己的队列功能相比,使用一个实例管理这些情况非常容易。

我已经在另一个线程中回答了这个问题。

//Usage:
var handler = new RedirectHandler(new HttpClientHandler());
var client = new HttpClient(handler);
//redirects to HTTPS
var url = "http://stackoverflow.com/";
//AutoRedirect is true
var response = await HttpClientHelper.SendAsync(client, url, autoRedirect: true).ConfigureAwait(false);
//AutoRedirect is false
response = await HttpClientHelper.SendAsync(client, url, autoRedirect: false).ConfigureAwait(false);
public static class HttpClientHelper
{
    private const string AutoRedirectPropertyKey = "RequestAutoRedirect";
    private static readonly HttpRequestOptionsKey<bool?> AutoRedirectOptionsKey = new(AutoRedirectPropertyKey);
    public static Task<HttpResponseMessage> SendAsync(HttpClient client, string url, bool autoRedirect = true)
    {
        var uri = new Uri(url);
        var request = new HttpRequestMessage
        {
            RequestUri = uri,
            Method = HttpMethod.Get
        };
        
        request.SetAutoRedirect(autoRedirect);
        return client.SendAsync(request);
    }
    public static void SetAutoRedirect(this HttpRequestMessage request, bool autoRedirect)
    {
        request.Options.Set(AutoRedirectOptionsKey, autoRedirect);
    }
    public static bool? GetAutoRedirect(this HttpRequestMessage request)
    {
        request.Options.TryGetValue(AutoRedirectOptionsKey, out var value);
        return value;
    }
    public static HttpMessageHandler? GetMostInnerHandler(this HttpMessageHandler? self)
    {
        while (self is DelegatingHandler handler)
        {
            self = handler.InnerHandler;
        }
        return self;
    }
}
public class RedirectHandler : DelegatingHandler
{
    private int MaxAutomaticRedirections { get; set; }
    private bool InitialAutoRedirect { get; set; }
    public RedirectHandler(HttpMessageHandler innerHandler) : base(innerHandler)
    {
        var mostInnerHandler = innerHandler.GetMostInnerHandler();
        SetupCustomAutoRedirect(mostInnerHandler);
    }
    private void SetupCustomAutoRedirect(HttpMessageHandler? mostInnerHandler)
    {
        //Store the initial auto-redirect & max-auto-redirect values.
        //Disabling auto-redirect and handle redirects manually.
        try
        {
            switch (mostInnerHandler)
            {
                case HttpClientHandler hch:
                    InitialAutoRedirect = hch.AllowAutoRedirect;
                    MaxAutomaticRedirections = hch.MaxAutomaticRedirections;
                    hch.AllowAutoRedirect = false;
                    break;
                case SocketsHttpHandler shh:
                    InitialAutoRedirect = shh.AllowAutoRedirect;
                    MaxAutomaticRedirections = shh.MaxAutomaticRedirections;
                    shh.AllowAutoRedirect = false;
                    break;
                default:
                    Debug.WriteLine("[SetupCustomAutoRedirect] Unknown handler type: {0}", mostInnerHandler?.GetType().FullName);
                    InitialAutoRedirect = true;
                    MaxAutomaticRedirections = 17;
                    break;
            }
        }
        catch (Exception e)
        {
            Debug.WriteLine(e.Message);
            InitialAutoRedirect = true;
            MaxAutomaticRedirections = 17;
        }
    }
    private bool IsRedirectAllowed(HttpRequestMessage request)
    {
        var value = request.GetAutoRedirect();
        if (value == null)
            return InitialAutoRedirect;
        
        return value == true;
    }
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var redirectCount = 0;
        var response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
        //Manual Redirect
        //https://github.com/dotnet/runtime/blob/ccfe21882e4a2206ce49cd5b32d3eb3cab3e530f/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RedirectHandler.cs
        Uri? redirectUri;
        while (IsRedirect(response) && IsRedirectAllowed(request) && (redirectUri = GetUriForRedirect(request.RequestUri!, response)) != null)
        {
            redirectCount++;
            if (redirectCount > MaxAutomaticRedirections)
                break;
            response.Dispose();
            // Clear the authorization header.
            request.Headers.Authorization = null;
            // Set up for the redirect
            request.RequestUri = redirectUri;
            if (RequestRequiresForceGet(response.StatusCode, request.Method))
            {
                request.Method = HttpMethod.Get;
                request.Content = null;
                if (request.Headers.TransferEncodingChunked == true)
                    request.Headers.TransferEncodingChunked = false;
            }
            // Issue the redirected request.
            response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
        }
        return response;
    }
    private bool IsRedirect(HttpResponseMessage response)
    {
        switch (response.StatusCode)
        {
            case HttpStatusCode.MultipleChoices:
            case HttpStatusCode.Moved:
            case HttpStatusCode.Found:
            case HttpStatusCode.SeeOther:
            case HttpStatusCode.TemporaryRedirect:
            case HttpStatusCode.PermanentRedirect:
                return true;
            default:
                return false;
        }
    }
    private static Uri? GetUriForRedirect(Uri requestUri, HttpResponseMessage response)
    {
        var location = response.Headers.Location;
        if (location == null)
        {
            return null;
        }
        // Ensure the redirect location is an absolute URI.
        if (!location.IsAbsoluteUri)
        {
            location = new Uri(requestUri, location);
        }
        // Per https://tools.ietf.org/html/rfc7231#section-7.1.2, a redirect location without a
        // fragment should inherit the fragment from the original URI.
        var requestFragment = requestUri.Fragment;
        if (!string.IsNullOrEmpty(requestFragment))
        {
            var redirectFragment = location.Fragment;
            if (string.IsNullOrEmpty(redirectFragment))
            {
                location = new UriBuilder(location) { Fragment = requestFragment }.Uri;
            }
        }
        return location;
    }
    private static bool RequestRequiresForceGet(HttpStatusCode statusCode, HttpMethod requestMethod)
    {
        switch (statusCode)
        {
            case HttpStatusCode.Moved:
            case HttpStatusCode.Found:
            case HttpStatusCode.MultipleChoices:
                return requestMethod == HttpMethod.Post;
            case HttpStatusCode.SeeOther:
                return requestMethod != HttpMethod.Get && requestMethod != HttpMethod.Head;
            default:
                return false;
        }
    }
}

其主要思想是禁用自动重定向,并使用自定义的RedirectHandler手动处理它们。

  1. 在发送请求之前,我们使用扩展方法SetAutoRedirect将重定向规则存储在请求的选项字典中
  2. 收到响应后,我们会检查它是否是重定向。如果是,我们将使用扩展方法GetAutoRedirect检查请求的选项字典中的重定向规则
  3. 重复#2,直到达到MaxAutomaticRedirections或没有进一步的重定向