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


129

当我尝试在WebRequest对象上添加HTTP标头键/值对时,出现以下异常:

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

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

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

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

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

Answers:


182

如果您需要简短的技术性答案,请转到答案的最后一部分。

如果您想了解更多,请阅读所有内容,希望您会喜欢...


我今天也解决了这个问题,今天我发现的是:

  1. 以上答案是正确的,因为:

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

    1.2随时更改的标头HttpWebRequest,都需要在对象本身上使用适当的属性(如果存在)。

感谢FOR和Jvenema提供的领先指南...

  1. 但是,我发现的是,这个难题中缺少的部分是:

    2.1 WebHeaderCollection通常通过WebRequest.Headers或WebResponse.Headers 访问该类。一些常见的标头被认为是受限制的,或者直接由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的评论)

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


8
“全部使用适当的属性修改值”
CRice 2011年

76
这个答案只是重复异常的消息,而没有给出解决方案。
000

11
解决办法是检查头调用add之前已经存在或已限制(WebHeaderCollection.IsRestricted(键))
海堂

7
@Sam阅读第1.1节,该问题已解决。这意味着我们试图通过添加的属性Headers.Add()已经存在,因此我们应该对其进行修改。
Junaid Qadir

4
“我认为必须指出此限制是.NET Framework的功能,这一点很重要。”我宁愿没有这种功能。
赫伯特·阿玛拉尔(Herberth Amaral)2015年

76

我在使用自定义Web客户端时遇到了这个问题。我认为人们可能会因为采取多种方法而感到困惑。使用时,WebRequest.Create()您可以强制转换为,HttpWebRequest并使用属性添加或修改标题。使用时,WebHeaderCollection您可以使用.Add("referer","my_url")

例1

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

例2

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

1
防爆1解决了我的异常问题。所以我更改了client.Headers [“ referer”] = url; 到client.Headers.Add(“ referer”,url); 事情就开始了。谢谢。
000

2
请注意,此答案包含一个快乐的假设,即您正在使用桌面.Net运行时并要求输入http。WebRequest.Create可以根据您使用的协议前缀返回各种不同的对象。如果有人对此感兴趣,它与CustomProtocolHandlers有关。在WP7或Silverlight上,请求实现类也有些不同。请注意这一点。
quetzalcoatl 2012年

1
但我无法修改“接受”标头。我该如何修改?
用户

第一个例子是仍然给我同样的错误
MRID

29

先前的所有答案都描述了该问题,而没有提供解决方案。这是一种扩展方法,可通过允许您通过其字符串名称设置任何标题来解决该问题。

用法

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标题名称不区分大小写


我将其用于Proxy-Connection,但在说完之后,是的,我包含“ Proxy-Connection”的键,它返回null,这将导致null引用异常
deadManN

谢谢您的巧妙修复。我让扩展程序设置所有标头:static Dictionary<string, PropertyInfo> HeaderProperties = new Dictionary<string, PropertyInfo>(StringComparer.InvariantCultureIgnoreCase); static WebRequestExtensions() { // Get property info for restricted headers. Type type = typeof(HttpWebRequest); foreach (string header in Enum.GetNames(typeof(HttpRequestHeader))) { var property = type.GetProperty(header.ToString()); if (property != null) { HeaderProperties.Add(property.Name, property); } } }
Suncat2000

13

当我的代码尝试设置“ 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";

12

每当您更改的标头时HttpWebRequest,都需要在对象本身上使用适当的属性(如果存在)。如果您有一个平原WebRequest,请确保将其转换为HttpWebRequest第一个。然后Referrer,可以通过来访问您的情况((HttpWebRequest)request).Referrer,因此您无需直接修改标头-只需将属性设置为正确的值即可。ContentLengthContentTypeUserAgent,等等,都需要这样设置。

恕我直言,这是MS部件上的一个缺陷...如果通过标题设置标题,Headers.Add()则会自动在幕后调用适当的属性。


7

WebRequest是抽象的(并且因为任何继承的类都必须重写Headers属性)..您正在使用哪个具体的WebRequest?换句话说,如何获取该WebRequest对象?

嗯.. mnour的答案使我意识到,实际上收到的错误消息是存在的:它告诉您您要添加的标头已经存在,然后应使用适当的属性(例如,索引器)修改其值。 ),而不是尝试再次添加它。那可能就是您想要的。

从WebRequest继承的其他类可能具有更好的包装某些标头的属性。例如,请参阅此帖子


实际上,WebRequest.Create(url)创建一个WebRequest对象的实例。
伊格(Igal Tabachnik)

2

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

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

    request.Accept = "application/json";

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

1

基本上没有 那是一个HTTP标头,因此将其强制转换HttpWebRequest并设置.Referer(如您在问题中所指出的)是合理的:

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

1

注意:此解决方案将与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");
                            }

                        }
                    }

                }
            }
        } 

辉煌!这也使得它可以设置使用的请求的头设置在网络插座,从而解决此问题的工作:github.com/dotnet/corefx/issues/26627
奥伊斯坦科尔斯鲁德

之所以如此,是因为它们都使用WebHeaderCollection来操作标头。我只在HttpWebRequest上测试过它。
露宿者


0

您可以将WebRequest强制转换为如下所示的HttpWebRequest:

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

然后不要尝试操作标题列表,而是直接将其应用于request属性request.Referer:

request.Referer = "yourReferer";

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.