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

139

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

必须使用相应的属性修改此标题

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

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

我可以通过将 WebRequest 对象转换为 HttpWebRequest 并设置属性(例如 httpWebReq.Referer ="http://stackoverflow.com")来实现此目的,但这仅适用于一些通过属性公开的标头。

我想知道是否有一种方法可以更精细地控制使用远程资源的请求的标头修改。

13个回答

188
如果您只需要简短和技术性的答案,请直接跳到答案的最后一节。
如果您想更好地了解,请阅读全部内容,我希望您会喜欢...
我今天也遇到了这个问题,我发现:
以上答案是正确的,因为:
1.1 它告诉你正在尝试添加的标头已经存在,并且您应该使用适当的属性(例如索引器)修改其值,而不是尝试再次添加它。
1.2 每当更改 HttpWebRequest 的标题时,如果存在相应的属性,则必须在对象本身上使用适当的属性。
感谢 FOR 和 Jvenema 提供的指导...
但是,我发现的,也是这个难题中缺失的部分是:
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

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


9
“使用适当的属性修改它们的值”这句话已经说明了一切。 - CRice
84
这个回答只是重复例外情况的信息,没有给出解决问题的方案。 - 000
12
解决方案是在调用add之前检查标头是否已经存在或受限(WebHeaderCollection.IsRestricted(key))。 - Kaido
8
@Sam请阅读解决问题的1.1小节。这意味着我们试图通过Headers.Add()添加的属性已经存在,因此我们应该进行修改而不是添加。 - Junaid Qadir Shekhanzai
5
我觉得有必要指出,这个限制是.NET Framework的一个特性。 -- 我宁愿没有这种特性。 - Herberth Amaral
显示剩余5条评论

81

我在一个自定义的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");
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Referer = "http://stackoverflow.com";
request.UserAgent = "Mozilla/5.0";
response = (HttpWebResponse)request.GetResponse();

1
Ex 1解决了我的异常问题。所以我将client.Headers["referer"] = url;更改为client.Headers.Add("referer", url);,然后一切正常了。谢谢。 - 000
2
请注意,以下内容仅适用于您在桌面.NET运行时环境下处理HTTP的情况。根据所使用的协议前缀,WebRequest.Create可能返回各种不同的对象。如果有人对CustomProtocolHandlers感兴趣,它与此相关。而且,在WP7或Silverlight上,请求实现类也略有不同。请小心使用。 - quetzalcoatl
1
但是我无法修改“接受”标头。我该如何修改它? - user
第一个例子仍然给我相同的错误。 - mrid

34

所有之前的回答都描述了问题,但未提供解决方案。这里有一个扩展方法,它可以通过允许您使用其字符串名称设置任何标题来解决该问题。

用法

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’的键”之后,它返回null,这导致了空引用异常。 - Hassan Faghihi
感谢您的巧妙修复。我让扩展设置所有标头: static WebRequestExtensions() { // 获取受限标头的属性信息。 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

14

当我的代码试图像这样设置“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";

13
无论何时您更改HttpWebRequest的标头,都需要使用对象本身上适当的属性(如果存在)。如果您有一个普通的WebRequest,请确保首先将其强制转换为HttpWebRequest。然后,在您的情况下可以通过((HttpWebRequest)request).Referrer访问Referrer,因此您不需要直接修改标头-只需将属性设置为正确的值。 ContentLengthContentTypeUserAgent等都需要以这种方式设置。

在我看来,这是微软的缺陷...通过Headers.Add()设置标头应该自动在幕后调用适当的属性,如果他们想要这样做的话。


8

WebRequest是抽象的(而且由于任何继承类必须重写Headers属性),那么你使用的是哪个具体的 WebRequest?换句话说,你如何获取那个 WebRequest 对象?

额..我的回答让我意识到你收到的错误消息实际上是正确的:它告诉你正在尝试添加的标头已经存在,你应该使用适当的属性(例如索引器)来修改它的值,而不是尝试再次添加它。这可能就是你一直在寻找的。

其他从 WebRequest 继承的类可能会有更好的属性,用于包装某些标题;例如,参见this post


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

4
注意:此解决方案适用于WebClientSocket以及使用WebHeaderCollection处理头部的HttpWebRequest或任何其他类。
如果您查看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");
                            }

                        }
                    }

                }
            }
        } 

2
太棒了!这也使得在设置Web套接字时为请求设置标头成为可能,从而解决了此问题:https://github.com/dotnet/corefx/issues/26627 - Øystein Kolsrud
这应该是正确的,因为它们都使用WebHeaderCollection来操作标头。不过我只在HttpWebRequest上进行了测试。 - Sleeper
我已经使用Web Sockets进行了测试,并成功地去除了限制,现在我可以轻松设置标头。 - Gray Programmerz

3
上述答案都不错,但问题的本质是有些标头设置方式不同。请参见“受限标头”列表。对于这些标头,您只需将它们设置为属性即可。对于其他标头,您需要添加标头。请参见此处。
    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";

0

我只是使用:

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

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