在使用 System.Net.WebRequest 时无法设置某些 HTTP 标头

当我尝试在 WebRequest对象上添加 HTTP 头键/值对时,会得到以下异常:

必须使用适当的属性修改此标头

我尝试过使用 Add ()方法向 Headers集合添加新值,但仍然得到相同的异常。

webRequest.Headers.Add(HttpRequestHeader.Referer, "http://stackoverflow.com");

我可以通过将 WebRequest 对象强制转换为 HttpWebRequest 并设置诸如 httpWebReq.Referer ="http://stackoverflow.com"之类的属性来解决这个问题,但是这只适用于通过属性公开的少数头文件。

我想知道是否有一种方法可以更好地控制通过请求远程资源来修改头文件的粒度。

167386 次浏览

WebRequest 是抽象的(因为任何继承类都必须重写 Header 属性)。.您使用的是哪个具体的 WebRequest?换句话说,如何让 WebRequest 对象以?

呃。.Mnour 的回答让我意识到你得到的错误信息实际上是正确的: 它告诉你,你试图添加的头已经存在,然后你应该使用适当的属性(例如,索引器)修改它的值,而不是试图再次添加它。这可能就是你想要的。

从 WebRequest 继承的其他类可能有更好的属性来包装某些头; 例如,请参见 这篇文章

基本上没有。这是一个 http 头文件,因此可以合理地转换为 HttpWebRequest并设置 .Referer(正如您在问题中指出的那样) :

HttpWebRequest req = ...
req.Referer = "your url";

无论何时更改 HttpWebRequest的标题,如果对象本身存在适当的属性,则需要对它们使用相应的属性。如果您有一个简单的 WebRequest,请确保首先将其强制转换为 HttpWebRequest。然后,在您的情况下,可以通过 ((HttpWebRequest)request).Referrer访问 Referrer,所以您不需要直接修改头部-只需将属性设置为正确的值。ContentLengthContentTypeUserAgent等等,都需要这样设置。

恕我直言,这是 MS 部分的一个缺点... 通过 Headers.Add()设置头应该自动调用适当的属性在幕后,如果这是他们想要做的。

我遇到了这个问题与自定义网络客户端。我认为人们可能会因为这样做的多种方式而感到困惑。使用 WebRequest.Create()时,可以强制转换为 HttpWebRequest,并使用该属性添加或修改标头。使用 WebHeaderCollection时,可以使用 .Add("referer","my_url")

练习一

WebClient client = new WebClient();
client.Headers.Add("referer", "http://stackoverflow.com");
client.Headers.Add("user-agent", "Mozilla/5.0");

练习二

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Referer = "http://stackoverflow.com";
request.UserAgent = "Mozilla/5.0";
response = (HttpWebResponse)request.GetResponse();

如果您需要简短的技术性答案,请直接进入答案的最后一部分。

如果你想了解更多,就全部读完,我希望你会喜欢..。


我今天也反驳了这个问题,我今天发现:

  1. 上述答案是正确的,如:

    1.1它告诉你,你试图添加的头已经存在,然后你应该使用适当的属性(例如,索引器)修改它的值,而不是试图再次添加它。

    1.2在任何时候更改 HttpWebRequest的头部时,如果对象本身存在适当的属性,则需要对它们使用适当的属性。

感谢和 Jvenema 的领导指导方针..。

  1. 但是,我发现,和 这就是拼图中缺失的一块是:

    2.1通常通过 WebRequest.Header 或 WebResponse.Header 访问 WebHeaderCollection类。有些公共头被认为是受限制的,它们或者直接由 API (比如 Content-Type)公开,或者由系统保护,不能更改。

受限制的标题如下:

  • Accept
  • Connection
  • Content-Length
  • Content-Type
  • Date
  • Expect
  • Host
  • If-Modified-Since
  • Range
  • Referer
  • Transfer-Encoding
  • User-Agent
  • Proxy-Connection

因此,下次当您遇到这种异常并且不知道如何解决这个问题时,请记住有一些受限制的头部,解决方案是使用来自 WebRequest/HttpWebRequest类的适当属性显式地修改它们的值。


编辑: (有用的,来自用户 Kaido的评论)

解决方案是在调用 add 之前检查头是否已经存在或者是否受到限制(WebHeaderCollection.IsRestricted(key))

以前的所有答案都描述了这个问题,但没有提供解决方案。下面是一个扩展方法,它通过允许您通过字符串名称设置任何头来解决这个问题。

用法

HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
request.SetRawHeader("content-type", "application/json");

推广班

public static class HttpWebRequestExtensions
{
static string[] RestrictedHeaders = new string[] {
"Accept",
"Connection",
"Content-Length",
"Content-Type",
"Date",
"Expect",
"Host",
"If-Modified-Since",
"Keep-Alive",
"Proxy-Connection",
"Range",
"Referer",
"Transfer-Encoding",
"User-Agent"
};


static Dictionary<string, PropertyInfo> HeaderProperties = new Dictionary<string, PropertyInfo>(StringComparer.OrdinalIgnoreCase);


static HttpWebRequestExtensions()
{
Type type = typeof(HttpWebRequest);
foreach (string header in RestrictedHeaders)
{
string propertyName = header.Replace("-", "");
PropertyInfo headerProperty = type.GetProperty(propertyName);
HeaderProperties[header] = headerProperty;
}
}


public static void SetRawHeader(this HttpWebRequest request, string name, string value)
{
if (HeaderProperties.ContainsKey(name))
{
PropertyInfo property = HeaderProperties[name];
if (property.PropertyType == typeof(DateTime))
property.SetValue(request, DateTime.Parse(value), null);
else if (property.PropertyType == typeof(bool))
property.SetValue(request, Boolean.Parse(value), null);
else if (property.PropertyType == typeof(long))
property.SetValue(request, Int64.Parse(value), null);
else
property.SetValue(request, value, null);
}
else
{
request.Headers[name] = value;
}
}
}

场景

我为 HttpWebRequest编写了一个包装器,并且不想在包装器中将所有13个受限头作为属性公开。相反,我想使用一个简单的 Dictionary<string, string>

另一个例子是 HTTP 代理,您需要在请求中获取标头并将其转发给接收方。

还有很多其他的场景,在这些场景中,使用属性是不切实际的,也是不可能的。强制用户通过属性设置标头是一种非常不灵活的设计,这就是为什么需要反射的原因。好的一面是反射被抽象出来了,它仍然很快(在我的测试中是0.001秒) ,而且作为一个扩展方法感觉很自然。

笔记

对于 RFC,http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2,头名是不区分大小写的

上面的答案都很好,但问题的实质是,一些标头是按照一种方式设置的,而另一些标头是按照其他方式设置的。参见上面的“受限头”列表。对于这些,您只需将它们设置为一个属性。对于其他用户,实际上需要添加标题。看这里。

    request.ContentType = "application/x-www-form-urlencoded";


request.Accept = "application/json";


request.Headers.Add(HttpRequestHeader.Authorization, "Basic " + info.clientId + ":" + info.clientSecret);

我用的是:

request.ContentType = "application/json; charset=utf-8"

您只需将 WebRequest 强制转换为如下所示的 HttpWebRequest:

var request = (HttpWebRequest)WebRequest.Create(myUri);

然后,不要试图操作头列表,而是将其直接应用到请求属性请求中:

request.Referer = "yourReferer";

这些属性在请求对象中可用。

当我的代码试图像下面这样设置“ Accept”头部值时,我遇到了同样的异常:

WebRequest request = WebRequest.Create("http://someServer:6405/biprws/logon/long");
request.Headers.Add("Accept", "application/json");

解决办法是把它改成这样:

HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://someServer:6405/biprws/logon/long");
request.Accept = "application/json";

注意: 此解决方案将与 WebClientSocket 以及 HttpWebRequest 或任何其他使用 WebHeaderCollection 处理头的类一起工作。

如果你看一下 webheadercollection.cs 的源代码,你会发现 Hinfo 是用来保存所有已知头信息的:

private static readonly HeaderInfoTable HInfo = new HeaderInfoTable();

查看 HeaderInfoTable 类,可以注意到所有数据都存储在散列表中

private static Hashtable HeaderHashTable;

此外,在 HeaderInfoTable 的静态构造函数中,可以看到所有已知的头都添加到 HeaderInfo 数组中,然后复制到散列表中。

HeaderInfo 类的最后一个查看显示了字段的名称。

internal class HeaderInfo {


internal readonly bool IsRequestRestricted;
internal readonly bool IsResponseRestricted;
internal readonly HeaderParser Parser;


//
// Note that the HeaderName field is not always valid, and should not
// be used after initialization. In particular, the HeaderInfo returned
// for an unknown header will not have the correct header name.
//


internal readonly string HeaderName;
internal readonly bool AllowMultiValues;
...
}

因此,综上所述,下面的代码使用反射在 HeaderInfoTable 类中查找静态 Hashtable,然后将散列表中每个受请求限制的 HeaderInfo 更改为无限制的

        // use reflection to remove IsRequestRestricted from headerInfo hash table
Assembly a = typeof(HttpWebRequest).Assembly;
foreach (FieldInfo f in a.GetType("System.Net.HeaderInfoTable").GetFields(BindingFlags.NonPublic | BindingFlags.Static))
{
if (f.Name == "HeaderHashTable")
{
Hashtable hashTable = f.GetValue(null) as Hashtable;
foreach (string sKey in hashTable.Keys)
{


object headerInfo = hashTable[sKey];
//Console.WriteLine(String.Format("{0}: {1}", sKey, hashTable[sKey]));
foreach (FieldInfo g in a.GetType("System.Net.HeaderInfo").GetFields(BindingFlags.NonPublic | BindingFlags.Instance))
{


if (g.Name == "IsRequestRestricted")
{
bool b = (bool)g.GetValue(headerInfo);
if (b)
{
g.SetValue(headerInfo, false);
Console.WriteLine(sKey + "." + g.Name + " changed to false");
}


}
}


}
}
}

我遇到了同样的问题下面的一段代码为我工作

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);


request.Headers["UserAgent"] = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1;
Trident/5.0)"
request.Headers.UserAgent.Add(new ProductInfoHeaderValue("my_string"));