HttpWebrequest 中 HTTP 标头的严格排序

use*_*441 7 c# httpclient httpwebrequest http-headers

尽管 RFC 声明唯一命名的标头的顺序无关紧要,但我发送此请求的网站确实对标头的顺序进行了检查。

这有效:

GET https://www.thewebsite.com HTTP/1.1
Host: www.thewebsite.com
Connection: keep-alive
Accept: */*
User-Agent: Mozilla/5.0 etc
Run Code Online (Sandbox Code Playgroud)

这不起作用:

GET https://www.thewebsite.com HTTP/1.1
Accept: */*
User-Agent: Mozilla/5.0 etc
Host: www.thewebsite.com
Connection: keep-alive
Run Code Online (Sandbox Code Playgroud)

默认情况下HttpWebRequest似乎将HostConnection标题放在最后,在空行之前,而不是在 url 之后。

有什么方法(HttpWebRequest甚至在 Nuget 中使用 fork或其他库)来指定HttpWebRequest.

如果可能的话,我宁愿不开始沿着实现代理的路线对它们进行排序或不得不使用TcpClient.

我很感激这方面的任何提示。

更新:随着 Fiddler 的运行,HttpWebrequest 中的标头顺序可以在 CustomRules.cs 中重新排列。尽管如此,仍然没有更接近没有代理的解决方案。

ton*_*lla 6

一些服务器实现标头排序作为对任何攻击或垃圾邮件的预防措施,一篇文章解释了为什么排序 HTTP 标头很重要

但标准是,接收具有不同字段名称的标头字段的顺序并不重要。

HttpWebRequest,没有简单的方法来对标头进行排序,并且 和ConnectionHost在内部添加的。

如果排序确实很重要,请使用 the代替,它可以根据@Jason 的示例HttpClient轻松排列。Headers

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

处理程序

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

执行

HttpClient clientRequest = new HttpClient(new CustomHttpClientHandler());
await clientRequest.GetAsync(url);
Run Code Online (Sandbox Code Playgroud)


Jas*_*son 4

.Net核心

如果您自己设置标题,则可以指定顺序。添加公共标头时,它将查找现有标头而不是附加它们:

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

这是一个示例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(""));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

带有有序标头的 GET 带有有序标题的 POST

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

.Net框架

在 .Net Framework 上,标头是保留的,因此不能像这样设置它们,请参阅使用 System.Net.WebRequest 时无法设置某些 HTTP 标头

一种解决方法是使用反射来修改框架类的行为,但请注意,如果更新库,这可能会中断,因此不建议这样做!

本质上,HttpWebRequest调用ToString序列WebHeaderCollection化。请参阅https://referencesource.microsoft.com/#System/net/System/Net/HttpWebRequest.cs,5079

因此可以创建一个自定义类来覆盖ToString. 不幸的是,需要反射来将标头设置为将WebRequest分配时的集合复制到Headers,而不是采用新的引用。

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

如果您使用此功能,请编写一些单元测试来验证更新到 .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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述