仅针对一个请求在现有 HttpClient 中将 AllowAutoRedirect 设置为 false
Set AllowAutoRedirect false in existing HttpClient for just one request
This answer 关于如何使 HttpClient
不遵循重定向的问题给出了在创建实际客户端时设置的解决方案:
var handler = new HttpClientHandler { AllowAutoRedirect = false };
var client = new HttpClient(handler);
答案下方的评论是我的实际问题:
Is it possible to do this on a per-request basis without needing two separate HttpClient instances (i.e. one that allows redirects and one that does not)?
我现在想要单独的客户端也有一个特定的原因:我希望客户端保留来自早期请求的 cookie。我试图首先执行一些包含有效重定向的请求,但我不想成为链中的 最后一个 重定向。
我搜索过,查看了 .GetAsync(url, ...)
的重载,查看了 HttpClient
的属性和方法,但还没有找到解决方案。
这可能吗?
是的,您可以为每个请求设置 HttpClientHandler
的属性,如下所示:
using (var handler = new HttpClientHandler())
using (var client = new HttpClient(handler))
{
handler.AllowAutoRedirect = false;
// do your job
handler.AllowAutoRedirect = true;
}
只需确保一次只有一个线程使用 HttpClient
,如果 客户端处理程序设置不同。
示例(注意:仅适用于测试环境)
虚拟远程服务器 Node.js 在本地主机上运行:
const express = require('express')
const app = express()
const cookieParser = require('cookie-parser')
const session = require('express-session')
const port = 3000
app.use(cookieParser());
app.use(session({secret: "super secret"}))
app.get('/set-cookie/:cookieName', (req, res) => {
const cookie = Math.random().toString()
req.session[req.params.cookieName] = cookie
res.send(cookie)
});
app.get('/ok', (req, res) => res.send('OK!'))
app.get('/redirect-301', (req, res) => {
res.writeHead(301, {'Location': '/ok'})
res.end();
})
app.get('/get-cookie/:cookieName', (req, res) => res.send(req.session[req.params.cookieName]))
app.listen(port, () => console.log(`App listening on port ${port}!`))
测试
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using NUnit.Framework;
public class Tests
{
private HttpClientHandler handler;
private HttpClient client;
private CookieContainer cookieJar = new CookieContainer();
private string cookieName = "myCookie";
private string cookieValue;
[SetUp]
public void Setup()
{
handler = new HttpClientHandler()
{
AllowAutoRedirect = true,
CookieContainer = cookieJar
};
client = new HttpClient(handler);
}
[Test]
public async Task Test0()
{
using (var response = await client.GetAsync($"http://localhost:3000/set-cookie/{cookieName}"))
{
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
cookieValue = await response.Content.ReadAsStringAsync();
}
}
[Test]
public async Task Test1()
{
handler.AllowAutoRedirect = true;
using (var response = await client.GetAsync("http://localhost:3000/redirect-301"))
{
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
Assert.AreEqual(await response.Content.ReadAsStringAsync(), "OK!");
}
}
[Test]
public async Task Test2()
{
handler.AllowAutoRedirect = false;
using (var response = await client.GetAsync("http://localhost:3000/redirect-301"))
{
Assert.AreEqual(HttpStatusCode.MovedPermanently, response.StatusCode);
}
}
[Test]
public async Task Test3()
{
using (var response = await client.GetAsync($"http://localhost:3000/get-cookie/{cookieName}"))
{
Assert.AreEqual(await response.Content.ReadAsStringAsync(), cookieValue);
}
}
}
通过dotnet test
输出:
Test Run Successful.
Total tests: 4
Passed: 4
Total time: 0.9352 Seconds
您可能已经发现,在发出请求后,您将无法更改 HttpClientHandler
配置。
因为您想要这样做的动机是在请求之间维护 cookie,所以我建议更像这样的东西(不包括 exception/null 参考处理):
static CookieContainer cookieJar = new CookieContainer();
static async Task<HttpResponseMessage> GetAsync(string url, bool autoRedirect)
{
HttpResponseMessage result = null;
using (var handler = new HttpClientHandler())
using (var client = new HttpClient(handler))
{
handler.AllowAutoRedirect = autoRedirect;
handler.CookieContainer = cookieJar;
result = await client.GetAsync(url);
cookieJar = handler.CookieContainer;
}
return result;
}
测试:
static async Task Main(string[] args)
{
string url = @"http://whosebug.com";
using (var response = await GetAsync(url, autoRedirect: false))
{
Console.WriteLine($"HTTP {(int)response.StatusCode} {response.StatusCode}");
Console.WriteLine($"{response.Headers}");
Console.WriteLine("Cookies:");
Console.WriteLine($"{cookieJar.GetCookieHeader(new Uri(url))}\r\n");
}
Console.WriteLine(new string('-', 30));
using (var response = await GetAsync(url, autoRedirect: true))
{
Console.WriteLine($"HTTP {(int)response.StatusCode} {response.StatusCode}");
Console.WriteLine($"{response.Headers}");
Console.WriteLine("Cookies:");
Console.WriteLine($"{cookieJar.GetCookieHeader(new Uri(url))}\r\n");
}
Console.ReadLine();
}
问题询问是否可以在 个案 的基础上完成以下重定向。虽然对于许多常见情况肯定有用,但我发现现有的答案在这方面有所欠缺。
以下实现允许通过谓词根据具体情况决定是否遵循重定向。
解决方法是重写HttpClientHandler的SendAsync()方法。
using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace HttpClientCustomRedirectBehavior
{
static class Program
{
private const string REDIRECTING_URL = "http://whosebug.com/";
static async Task Main(string[] args)
{
HttpMessageHandler followRedirectAlwaysHandler = new RestrictedRedirectFollowingHttpClientHandler(
response => true);
HttpMessageHandler followRedirectOnlyToSpecificHostHandler = new RestrictedRedirectFollowingHttpClientHandler(
response => response.Headers.Location.Host == "example.com");
HttpResponseMessage response;
using (HttpClient followRedirectAlwaysHttpClient = new HttpClient(followRedirectAlwaysHandler))
{
response = await followRedirectAlwaysHttpClient.GetAsync(REDIRECTING_URL);
Console.WriteLine(response.StatusCode); // OK
}
using (HttpClient followRedirectOnlyToSpecificHostHttpClient = new HttpClient(followRedirectOnlyToSpecificHostHandler))
{
response = await followRedirectOnlyToSpecificHostHttpClient.GetAsync(REDIRECTING_URL);
Console.WriteLine(response.StatusCode); // Moved
}
followRedirectOnlyToSpecificHostHandler = new RestrictedRedirectFollowingHttpClientHandler(
response => response.Headers.Location.Host == "whosebug.com");
using (HttpClient followRedirectOnlyToSpecificHostHttpClient = new HttpClient(followRedirectOnlyToSpecificHostHandler))
{
response = await followRedirectOnlyToSpecificHostHttpClient.GetAsync(REDIRECTING_URL);
Console.WriteLine(response.StatusCode); // OK
}
}
}
public class RestrictedRedirectFollowingHttpClientHandler : HttpClientHandler
{
private static readonly HttpStatusCode[] redirectStatusCodes = new[] {
HttpStatusCode.Moved,
HttpStatusCode.Redirect,
HttpStatusCode.RedirectMethod,
HttpStatusCode.TemporaryRedirect,
HttpStatusCode.PermanentRedirect
};
private readonly Predicate<HttpResponseMessage> isRedirectAllowed;
public override bool SupportsRedirectConfiguration { get; }
public RestrictedRedirectFollowingHttpClientHandler(Predicate<HttpResponseMessage> isRedirectAllowed)
{
AllowAutoRedirect = false;
SupportsRedirectConfiguration = false;
this.isRedirectAllowed = response => {
return Array.BinarySearch(redirectStatusCodes, response.StatusCode) >= 0
&& isRedirectAllowed.Invoke(response);
};
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
int redirectCount = 0;
HttpResponseMessage response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
while (isRedirectAllowed.Invoke(response)
&& (response.Headers.Location != request.RequestUri || response.StatusCode == HttpStatusCode.RedirectMethod && request.Method != HttpMethod.Get)
&& redirectCount < this.MaxAutomaticRedirections)
{
if (response.StatusCode == HttpStatusCode.RedirectMethod)
{
request.Method = HttpMethod.Get;
}
request.RequestUri = response.Headers.Location;
response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
++redirectCount;
}
return response;
}
}
}
Main 方法显示了对 http://whosebug.com (which is a URI that redirects to https://whosebug.com):
的三个示例请求
- 第一个 GET 请求将遵循重定向,因此我们看到重定向请求响应的状态代码 OK,因为处理程序配置为遵循所有重定向。
- 第二个 GET 请求不会遵循重定向,因此我们看到状态代码 Moved,因为处理程序配置为完全遵循重定向到主机 example.com .
- 第三个 GET 请求将跟随重定向,因此我们看到重定向请求响应的状态代码 OK,因为处理程序配置为跟随重定向到主机whosebug.com独家。
当然,您可以用任何自定义逻辑替换谓词。
This answer 关于如何使 HttpClient
不遵循重定向的问题给出了在创建实际客户端时设置的解决方案:
var handler = new HttpClientHandler { AllowAutoRedirect = false };
var client = new HttpClient(handler);
答案下方的评论是我的实际问题:
Is it possible to do this on a per-request basis without needing two separate HttpClient instances (i.e. one that allows redirects and one that does not)?
我现在想要单独的客户端也有一个特定的原因:我希望客户端保留来自早期请求的 cookie。我试图首先执行一些包含有效重定向的请求,但我不想成为链中的 最后一个 重定向。
我搜索过,查看了 .GetAsync(url, ...)
的重载,查看了 HttpClient
的属性和方法,但还没有找到解决方案。
这可能吗?
是的,您可以为每个请求设置 HttpClientHandler
的属性,如下所示:
using (var handler = new HttpClientHandler())
using (var client = new HttpClient(handler))
{
handler.AllowAutoRedirect = false;
// do your job
handler.AllowAutoRedirect = true;
}
只需确保一次只有一个线程使用 HttpClient
,如果 客户端处理程序设置不同。
示例(注意:仅适用于测试环境)
虚拟远程服务器 Node.js 在本地主机上运行:
const express = require('express')
const app = express()
const cookieParser = require('cookie-parser')
const session = require('express-session')
const port = 3000
app.use(cookieParser());
app.use(session({secret: "super secret"}))
app.get('/set-cookie/:cookieName', (req, res) => {
const cookie = Math.random().toString()
req.session[req.params.cookieName] = cookie
res.send(cookie)
});
app.get('/ok', (req, res) => res.send('OK!'))
app.get('/redirect-301', (req, res) => {
res.writeHead(301, {'Location': '/ok'})
res.end();
})
app.get('/get-cookie/:cookieName', (req, res) => res.send(req.session[req.params.cookieName]))
app.listen(port, () => console.log(`App listening on port ${port}!`))
测试
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using NUnit.Framework;
public class Tests
{
private HttpClientHandler handler;
private HttpClient client;
private CookieContainer cookieJar = new CookieContainer();
private string cookieName = "myCookie";
private string cookieValue;
[SetUp]
public void Setup()
{
handler = new HttpClientHandler()
{
AllowAutoRedirect = true,
CookieContainer = cookieJar
};
client = new HttpClient(handler);
}
[Test]
public async Task Test0()
{
using (var response = await client.GetAsync($"http://localhost:3000/set-cookie/{cookieName}"))
{
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
cookieValue = await response.Content.ReadAsStringAsync();
}
}
[Test]
public async Task Test1()
{
handler.AllowAutoRedirect = true;
using (var response = await client.GetAsync("http://localhost:3000/redirect-301"))
{
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
Assert.AreEqual(await response.Content.ReadAsStringAsync(), "OK!");
}
}
[Test]
public async Task Test2()
{
handler.AllowAutoRedirect = false;
using (var response = await client.GetAsync("http://localhost:3000/redirect-301"))
{
Assert.AreEqual(HttpStatusCode.MovedPermanently, response.StatusCode);
}
}
[Test]
public async Task Test3()
{
using (var response = await client.GetAsync($"http://localhost:3000/get-cookie/{cookieName}"))
{
Assert.AreEqual(await response.Content.ReadAsStringAsync(), cookieValue);
}
}
}
通过dotnet test
输出:
Test Run Successful.
Total tests: 4
Passed: 4
Total time: 0.9352 Seconds
您可能已经发现,在发出请求后,您将无法更改 HttpClientHandler
配置。
因为您想要这样做的动机是在请求之间维护 cookie,所以我建议更像这样的东西(不包括 exception/null 参考处理):
static CookieContainer cookieJar = new CookieContainer();
static async Task<HttpResponseMessage> GetAsync(string url, bool autoRedirect)
{
HttpResponseMessage result = null;
using (var handler = new HttpClientHandler())
using (var client = new HttpClient(handler))
{
handler.AllowAutoRedirect = autoRedirect;
handler.CookieContainer = cookieJar;
result = await client.GetAsync(url);
cookieJar = handler.CookieContainer;
}
return result;
}
测试:
static async Task Main(string[] args)
{
string url = @"http://whosebug.com";
using (var response = await GetAsync(url, autoRedirect: false))
{
Console.WriteLine($"HTTP {(int)response.StatusCode} {response.StatusCode}");
Console.WriteLine($"{response.Headers}");
Console.WriteLine("Cookies:");
Console.WriteLine($"{cookieJar.GetCookieHeader(new Uri(url))}\r\n");
}
Console.WriteLine(new string('-', 30));
using (var response = await GetAsync(url, autoRedirect: true))
{
Console.WriteLine($"HTTP {(int)response.StatusCode} {response.StatusCode}");
Console.WriteLine($"{response.Headers}");
Console.WriteLine("Cookies:");
Console.WriteLine($"{cookieJar.GetCookieHeader(new Uri(url))}\r\n");
}
Console.ReadLine();
}
问题询问是否可以在 个案 的基础上完成以下重定向。虽然对于许多常见情况肯定有用,但我发现现有的答案在这方面有所欠缺。
以下实现允许通过谓词根据具体情况决定是否遵循重定向。 解决方法是重写HttpClientHandler的SendAsync()方法。
using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace HttpClientCustomRedirectBehavior
{
static class Program
{
private const string REDIRECTING_URL = "http://whosebug.com/";
static async Task Main(string[] args)
{
HttpMessageHandler followRedirectAlwaysHandler = new RestrictedRedirectFollowingHttpClientHandler(
response => true);
HttpMessageHandler followRedirectOnlyToSpecificHostHandler = new RestrictedRedirectFollowingHttpClientHandler(
response => response.Headers.Location.Host == "example.com");
HttpResponseMessage response;
using (HttpClient followRedirectAlwaysHttpClient = new HttpClient(followRedirectAlwaysHandler))
{
response = await followRedirectAlwaysHttpClient.GetAsync(REDIRECTING_URL);
Console.WriteLine(response.StatusCode); // OK
}
using (HttpClient followRedirectOnlyToSpecificHostHttpClient = new HttpClient(followRedirectOnlyToSpecificHostHandler))
{
response = await followRedirectOnlyToSpecificHostHttpClient.GetAsync(REDIRECTING_URL);
Console.WriteLine(response.StatusCode); // Moved
}
followRedirectOnlyToSpecificHostHandler = new RestrictedRedirectFollowingHttpClientHandler(
response => response.Headers.Location.Host == "whosebug.com");
using (HttpClient followRedirectOnlyToSpecificHostHttpClient = new HttpClient(followRedirectOnlyToSpecificHostHandler))
{
response = await followRedirectOnlyToSpecificHostHttpClient.GetAsync(REDIRECTING_URL);
Console.WriteLine(response.StatusCode); // OK
}
}
}
public class RestrictedRedirectFollowingHttpClientHandler : HttpClientHandler
{
private static readonly HttpStatusCode[] redirectStatusCodes = new[] {
HttpStatusCode.Moved,
HttpStatusCode.Redirect,
HttpStatusCode.RedirectMethod,
HttpStatusCode.TemporaryRedirect,
HttpStatusCode.PermanentRedirect
};
private readonly Predicate<HttpResponseMessage> isRedirectAllowed;
public override bool SupportsRedirectConfiguration { get; }
public RestrictedRedirectFollowingHttpClientHandler(Predicate<HttpResponseMessage> isRedirectAllowed)
{
AllowAutoRedirect = false;
SupportsRedirectConfiguration = false;
this.isRedirectAllowed = response => {
return Array.BinarySearch(redirectStatusCodes, response.StatusCode) >= 0
&& isRedirectAllowed.Invoke(response);
};
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
int redirectCount = 0;
HttpResponseMessage response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
while (isRedirectAllowed.Invoke(response)
&& (response.Headers.Location != request.RequestUri || response.StatusCode == HttpStatusCode.RedirectMethod && request.Method != HttpMethod.Get)
&& redirectCount < this.MaxAutomaticRedirections)
{
if (response.StatusCode == HttpStatusCode.RedirectMethod)
{
request.Method = HttpMethod.Get;
}
request.RequestUri = response.Headers.Location;
response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
++redirectCount;
}
return response;
}
}
}
Main 方法显示了对 http://whosebug.com (which is a URI that redirects to https://whosebug.com):
的三个示例请求- 第一个 GET 请求将遵循重定向,因此我们看到重定向请求响应的状态代码 OK,因为处理程序配置为遵循所有重定向。
- 第二个 GET 请求不会遵循重定向,因此我们看到状态代码 Moved,因为处理程序配置为完全遵循重定向到主机 example.com .
- 第三个 GET 请求将跟随重定向,因此我们看到重定向请求响应的状态代码 OK,因为处理程序配置为跟随重定向到主机whosebug.com独家。
当然,您可以用任何自定义逻辑替换谓词。