给HttpClient添加动态代理
builder.Services.AddTransient<DynamicProxyHandler>();
builder.Services.AddHttpClient("searchIp", config =>
{
}).ConfigurePrimaryHttpMessageHandler(provider =>
{
var proxyHandler = provider.GetRequiredService<DynamicProxyHandler>();
return proxyHandler;
});
public class DynamicProxyHandler : HttpClientHandler
{
private readonly IFreeProxyPoolAppService _freeProxyPoolAppService;
public DynamicProxyHandler(IFreeProxyPoolAppService freeProxyPoolAppService)
{
_freeProxyPoolAppService = freeProxyPoolAppService;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
var maxRetryAttempts = 10;
var retry = 0;
HttpResponseMessage response = null;
// 获取动态代理IP
var proxyUrl = await _freeProxyPoolAppService.GetProxy();
if (!string.IsNullOrEmpty(proxyUrl))
{
while (retry < maxRetryAttempts)
{
if (retry > 0)
{
proxyUrl = await _freeProxyPoolAppService.GetProxy();
}
try
{
using var httpClientHandler = new HttpClientHandler();
httpClientHandler.Proxy = new WebProxy(proxyUrl, BypassOnLocal: false);
using var httpClient = new HttpClient(httpClientHandler);
httpClient.Timeout = TimeSpan.FromSeconds(15);
var clonedRequest = await CloneHttpRequestMessage(request);
response = await httpClient.SendAsync(clonedRequest, cancellationToken);
break;
}
catch (TaskCanceledException ex) when (!cancellationToken.IsCancellationRequested)
{
// request 请求超时
retry++;
}
catch (HttpRequestException ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine($"重试次数:{retry++}");
retry++;
}
}
}
else
{
using var httpClient = new HttpClient();
var clonedRequest = await CloneHttpRequestMessage(request);
response = await httpClient.SendAsync(clonedRequest, cancellationToken);
}
return response;
}
private async Task<HttpRequestMessage> CloneHttpRequestMessage(HttpRequestMessage request)
{
var clonedRequest = new HttpRequestMessage(request.Method, request.RequestUri);
// Copy the request content (if any)
if (request.Content != null)
{
clonedRequest.Content = new StreamContent(await request.Content.ReadAsStreamAsync());
if (request.Content.Headers != null)
{
foreach (var header in request.Content.Headers)
{
clonedRequest.Content.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
}
}
// Copy the request headers
foreach (var header in request.Headers)
{
clonedRequest.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
return clonedRequest;
}
}
HttpRequestMessage
和HttpResponseMessage
不接受与Content
相关的标头,按照惯例,应该将与Content
相关的标头添加到HttpContent
中。
如果使用 response.Headers.Add("Content-Type", ...);
将会报错 "System.InvalidOperationException: The request message was already sent. Cannot send the same request message multiple times."
不想遵循HttpClient的约定,并且在代理某些请求时可能需要此约定。 response.Headers.TryAddWithoutValidation("Content-Type", ...);