首页 > 解决方案 > 在现有的 HttpClient 中为一个请求设置 AllowAutoRedirect false

问题描述

This answer to the question on how to make HttpClientnot follow redirects 提供了在创建实际客户端时设置的解决方案:

var handler = new HttpClientHandler { AllowAutoRedirect = false };    
var client = new HttpClient(handler);

答案下方的评论是我的实际问题:

是否可以在不需要两个单独的 HttpClient 实例(即一个允许重定向,一个不允许重定向)的情况下基于每个请求执行此操作?

我现在想要单独的客户端也有一个特定的原因:我希望客户端从早期请求中保留其 cookie。我正在尝试首先执行一些请求,其中包括有效的重定向,但只有链中的最后一个我不想成为重定向。

我已经搜索并查看了 的重载.GetAsync(url, ...),并查看了 的属性和方法HttpClient,但还没有找到解决方案。

这可能吗?

标签: c#asp.net-coreasp.net-core-3.1

解决方案


该问题询问是否可以根据具体情况进行以下重定向。虽然对于许多常见情况肯定有用,但我发现现有的答案在这方面缺乏。

以下实现允许通过谓词根据具体情况来决定是否遵循重定向。解决方法是重写 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://stackoverflow.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 == "stackoverflow.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://stackoverflow.com的三个示例请求(这是一个重定向到https://stackoverflow.com的 URI ):

  1. 第一个 GET 请求将遵循重定向,因此我们看到对重定向请求的响应的状态代码OK ,因为处理程序配置为遵循所有重定向。
  2. 第二个 GET 请求不会跟随重定向,因此我们会看到状态代码Moved,因为处理程序配置为仅跟随重定向到主机 example.com。
  3. 第三个 GET 请求将跟随重定向,因此我们看到对重定向请求的响应的状态代码OK ,因为处理程序配置为仅跟随重定向到主机 stackoverflow.com。

当然,您可以用任何自定义逻辑替换谓词。


推荐阅读