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
似乎将 Host
和 Connection
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,但请注意,如果更新库,这可能会中断,因此不推荐这样做!.
本质上,HttpWebRequest
在 WebHeaderCollection
上调用 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,Connection
和 Host
是内部添加的。
如果排序真的很重要,使用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);
尽管 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
似乎将 Host
和 Connection
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,但请注意,如果更新库,这可能会中断,因此不推荐这样做!.
本质上,HttpWebRequest
在 WebHeaderCollection
上调用 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,Connection
和 Host
是内部添加的。
如果排序真的很重要,使用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);