HTTP请求中HTTP头部的严格顺序要求(HttpWebRequest)

7
尽管RFC声明具有唯一名称的标头的顺序不应该重要,但我发送此请求到的网站确实对标头的顺序进行了检查。
这个可以工作:
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标头放在URL之后的空行之前,而不是紧跟着URL。

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

如果可能的话,我宁愿不走代理排序或使用TcpClient编写整个过程的路线。

非常感谢任何关于此的提示。

更新:通过运行Fiddler,在CustomRules.cs中可以重新排列HttpWebrequest中的标头顺序。但仍无法在没有代理的情况下找到解决方案。


请详细说明“这不起作用”是什么意思。服务器返回哪个HTTP状态码? - Peter Csala
你尝试在Postman中执行这些请求了吗? - Peter Csala
1
我尝试了Postman并使用Wireshark检查了发送的内容。你不能使用Postman来排序头文件,所以每次都会失败并返回403错误。Fiddler是更好的工具。 - user2146441
你能否更新一下问题,说明这个Web请求是如何在C#中创建的? - weichch
2
关于“我宁愿不开始实现代理来对它们进行排序”的问题...我认为你应该采用反向代理的方式。我认为与RFC标准更严格的网站值得使用这种抽象层。考虑一下在分期环境中订单被改变或者根据请求的资源而变化的情况。 - Brett Caswell
显示剩余7条评论
2个回答

6

有些服务器对于攻击或垃圾邮件可能采用了头部排序的措施,这篇文章解释了 为什么HTTP头部顺序很重要。

但标准是,接收到具有不同字段名称的标头字段的顺序并不重要,详见此处

HttpWebRequest 中,没有简单的方法来排列标头,而且 ConnectionHost 会在内部添加。

如果排序真的很重要,请改用 HttpClient,它可以根据@Jason的示例轻松地排列Headers

如果您将使用 HttpClient,请创建一个自定义 HttpClientHandler 并从那里安排您的标头。 可以像这样:

HANDLER

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);

我认为这是一个不错的答案,尽管不完整。您可以通过提供有关如何重用此解决方案的说明来改进此答案。也就是说,由于 OP 寻求的是非代理或项目编码解决方案,因此合理的做法是将此行为作为包和框架集成,并且可能需要/支持一些配置工作。 - Brett Caswell
@tontonsevilla,事实证明我的解决方案在 .net Framework 上不起作用。我开始认为重新排序标头的这种方法可能会在将来的 .net Core 更新中出现问题。文档中没有任何地方说明我们被允许这样做,它只是利用了内部集合使用的副作用。 - Jason

4

.Net Core

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

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(""));
        }
    }
}

GET with ordered headers POST with ordered headers

编辑 上面的代码只适用于 .Net Core,而不适用于 .Net Framework

.Net Framework

在 .Net Framework 上,这些标头被保留,因此无法像这样设置它们,请参见 Cannot set some HTTP headers when using System.Net.WebRequest

一个解决方法是使用反射来修改框架类的行为,但请注意,如果库更新了,这可能会出现问题,因此不建议使用!

本质上,HttpWebRequest 在序列化时调用 WebHeaderCollectionToString 方法。 请查看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;
        }
    }
}

enter image description here


我还研究了使用自定义GetEnumerator和排序函数,在自定义的WebHeaderCollection上实现,但这需要反射来设置字段,因为HttpWebRequest中的setter会将内容复制到新的WebHeaderCollection中。 - Jason
上面的 WebRequest 代码在 .NET Framework 中无法运行。这是为了 .NET Core 吗? - user2146441
@user2146441 啊啊,没错,我在 .net core 上测试过了。我刚试了一下,并有一个使用反射的解决方案。但它并不美观。 - Jason
@user2146441 我已经添加了丑陋的代码。它可以工作,如果将字典传递到自定义标头中或在ToString中制作排序函数,则可以清理它,但是如果框架更改其实现,则肯定容易出错。 - Jason

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接