如何为每个请求设置AllowAutoRedirect?在多线程环境中共享HttpClient
本文关键字:多线程 环境 HttpClient 共享 AllowAutoRedirect 设置 请求 | 更新日期: 2023-09-27 18:28:15
我对HttpClient有点困惑。目前,我正在整个应用程序中使用此类的单个实例。
然而,它似乎不允许我为某些请求设置AllowAutoRedirect选项。
HttpClient是否被设计为针对每一个其他请求进行实例化?我说的是多线程环境。也许我应该用更灵活的类结构来包装它?
若要更改每个请求的任何属性,那么是的,您将需要创建HttpClient
的新实例。显然,如果您正在构建一个高性能应用程序,由于多次创建对象而产生了许多请求,那么性能和资源可能会起到一定的作用。
如果您真的走这条路,为HttpClient
创建一个包装类将是有益的,并且在任何情况下,如果您希望更改为单个实例,您都可以将HttpClient
作为参数传递。
在大多数情况下,HttpClient
应该用作一个实例,因为您可以调用任意多的请求,并且该对象具有处理异步和响应的所有工具。这对于请求类型WebRequest
和HttpWebRequest
是相同的。
如果您需要在启动另一个实例之前收到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手动处理它们。
- 在发送请求之前,我们使用扩展方法SetAutoRedirect将重定向规则存储在请求的选项字典中
- 收到响应后,我们会检查它是否是重定向。如果是,我们将使用扩展方法GetAutoRedirect检查请求的选项字典中的重定向规则
- 重复#2,直到达到MaxAutomaticRedirections或没有进一步的重定向