HttpWebrequest 中 HTTP headers 的严格排序

Strict ordering of HTTP headers in HttpWebrequest

尽管 RFC 声明 uniquely-named header 的顺序无关紧要,但我发送此请求的网站确实对 [= 的顺序进行了检查40=]s.

这个有效:

GET https://www.thewebsite.com HTTP/1.1
Host: www.thewebsite.com
Connection: keep-alive
Accept: */*
User-Agent: Mozilla/5.0 etc

这行不通:

GET https://www.thewebsite.com HTTP/1.1
Accept: */*
User-Agent: Mozilla/5.0 etc
Host: www.thewebsite.com
Connection: keep-alive

默认的 HttpWebRequest 似乎将 HostConnection header 放在最后,空行之前,而不是 [=41 之后=].

有没有办法(甚至使用 HttpWebRequest 的分支或 Nuget 中的其他库)指定 HttpWebRequest 中 header 的顺序?

如果可能的话,我宁愿不开始使用代理来对它们进行排序或者不得不使用 TcpClient.

对整个事情进行编码

如果有任何提示,我将不胜感激。

更新:使用 Fiddler 运行,HttpWebrequest 中的 header 顺序可以是 CustomRules.cs 中的 re-shuffled。不过,仍然离没有代理的解决方案更近一步。

.网络核心

如果自己设置headers,可以指定顺序。添加公共 headers 时,它将找到现有的 headers 而不是附加它们:

using System.Net;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            var request = WebRequest.Create("http://www.google.com");
            request.Headers.Add("Host", "www.google.com");
            // this will be set within GetResponse.
            request.Headers.Add("Connection", "");
            request.Headers.Add("Accept", "*/*");
            request.Headers.Add("User-Agent", "Mozilla/5.0 etc");
            request.GetResponse();
        }
    }
}

这里有一个 HttpClient 的例子:

using System.Net.Http;
using System.Threading.Tasks;

namespace ConsoleApp3
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var client = new HttpClient();
            client.DefaultRequestHeaders.Add("Host", "www.google.com");
            client.DefaultRequestHeaders.Add("Connection", "keep-alive");
            client.DefaultRequestHeaders.Add("Accept", "*/*");
            client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 etc");
            await client.GetAsync("http://www.google.com");
            await client.PostAsync("http://www.google.com", new StringContent(""));
        }
    }
}

编辑 以上代码不适用于 .Net Framework only .Net Core

.Net 框架

在 .Net Framework 上,headers 是保留的,因此不能这样设置,请参阅 Cannot set some HTTP headers when using System.Net.WebRequest

一种变通方法是使用反射来修改框架的行为class,但请注意,如果更新库,这可能会中断,因此不推荐这样做!.

本质上,HttpWebRequestWebHeaderCollection 上调用 ToString 进行序列化。 参见 https://referencesource.microsoft.com/#System/net/System/Net/HttpWebRequest.cs,5079

因此可以自定义 class 来覆盖 ToString。不幸的是,需要反射来设置 headers,因为 WebRequest 将分配给 Headers 的 collection 复制,而不是采用新的引用。

警告,如果框架更改,以下代码可能会中断

如果您使用它,请编写一些单元测试来验证行为在更新到 .NET Framework 后仍然保持一致

using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Reflection;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            // WARNING, CODE CAN BREAK IF FRAMEWORK CHANGES
            // If you use this, write some unit tests that verify the behavior still stays consistent after updates to .NET Framework
            var request = (HttpWebRequest)WebRequest.Create("http://www.google.com");
            var field = typeof(HttpWebRequest).GetField("_HttpRequestHeaders", BindingFlags.Instance | BindingFlags.NonPublic);
            var headers = new CustomWebHeaderCollection(new Dictionary<string, string>
            {
                ["Host"] = "www.google.com",
                ["Connection"] = "keep-alive",
                ["Accept"] = "*/*",
                ["User-Agent"] = "Mozilla/5.0 etc"
            });
            field.SetValue(request, headers);
            request.GetResponse();
        }
    }

    internal class CustomWebHeaderCollection : WebHeaderCollection
    {
        private readonly Dictionary<string, string> _customHeaders;

        public CustomWebHeaderCollection(Dictionary<string, string> customHeaders)
        {
            _customHeaders = customHeaders;
        }

        public override string ToString()
        {
            // Could call base.ToString() split on Newline and sort as needed

            var lines = _customHeaders
                .Select(kvp => $"{kvp.Key}: {kvp.Value}")
                // These two new lines are needed after the HTTP header
                .Concat(new [] { string.Empty, string.Empty });

            var headers = string.Join("\r\n", lines);

            return headers;
        }
    }
}

一些服务器实施 header 排序作为任何攻击或垃圾邮件的预防措施,一篇解释 Why ordering HTTP headers is important 的文章。

但标准是,the order in which header fields with differing field names are received is not significant.

HttpWebRequest,没有简单的方法来订购 headers,ConnectionHost 是内部添加的。

如果排序真的很重要,使用HttpClient代替,它可以根据@Jason的例子轻松排列Headers

如果您将使用 HttpClient,您可以创建一个自定义 HttpClientHandler,然后您可以从那里安排您的 header。可以是这样的。

处理程序

public class CustomHttpClientHandler : HttpClientHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.Headers.Clear();

        request.Headers.Add("Host", $"{request.RequestUri.Authority}");
        request.Headers.Add("Connection", "keep-alive");
        request.Headers.Add("Accept", "*/*");
        request.Headers.Add("User-Agent", "Mozilla/5.0 etc");

        return await base.SendAsync(request, cancellationToken);
    }
}

实施

HttpClient clientRequest = new HttpClient(new CustomHttpClientHandler());
await clientRequest.GetAsync(url);