Невозможно установить некоторые заголовки HTTP при использовании System.Net.WebRequest

Когда я пытаюсь добавить пару ключ / значение HTTP-заголовка к объекту WebRequest, я получаю следующее исключение:

Этот заголовок необходимо изменить с помощью соответствующего свойства.

Я пробовал добавлять новые значения в коллекцию Headers с помощью метода Add (), но все равно получаю то же исключение.

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

Я могу обойти это, преобразовав объект WebRequest в HttpWebRequest и установив такие свойства, как httpWebReq.Referer ="http://stackoverflow.com", но это работает только для нескольких заголовков, которые отображаются через свойства.

Я хотел бы знать, есть ли способ получить более точный контроль над изменением заголовков с запросом удаленного ресурса.


person Razor    schedule 27.10.2008    source источник


Ответы (12)


Если вам нужен краткий и технический ответ, перейдите к последнему разделу ответа.

Если вы хотите узнать лучше, прочтите все это, и я надеюсь, вам понравится ...


Сегодня я тоже решил эту проблему, и сегодня я обнаружил следующее:

  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

Итак, в следующий раз, когда вы столкнетесь с этим исключением и не знаете, как его решить, помните, что есть некоторые ограниченные заголовки, и решение состоит в том, чтобы изменить их значения, используя соответствующее свойство явно из класса _18 _ / _ 19_.


Изменить: (полезно, из комментариев, комментарий пользователя Кайдо)

Решение состоит в том, чтобы проверить, существует ли заголовок уже или ограничен (WebHeaderCollection.IsRestricted(key)) перед вызовом add

person dubi    schedule 20.01.2011
comment
изменить их значения, используя соответствующее свойство, говорит само за себя - person CRice; 14.06.2011
comment
Этот ответ просто повторяет сообщение об исключениях, не давая решения проблемы. - person 000; 09.12.2011
comment
Решение состоит в том, чтобы проверить, существует ли заголовок уже или ограничен (WebHeaderCollection.IsRestricted (key)) перед вызовом add - person Kaido; 26.07.2012
comment
@Sam прочитайте раздел 1.1, который решает проблему. это означает, что свойство, которое мы пытаемся добавить через Headers.Add(), уже существует, поэтому вместо этого мы должны изменить его. - person Junaid Qadir Shekhanzai; 24.01.2013
comment
Я считаю важным отметить, что это ограничение является особенностью .NET Framework. Некоторые разработчики предполагали, что эта ошибка означает, что спецификация HTTP каким-то образом ограничивает использование этих заголовков, что не так. - person Shawn South; 25.09.2014
comment
Мне действительно интересно, есть ли способ инициализировать / установить заголовки сразу из NameValueCollection без использования отражений или явного использования свойств. - person wodzu; 02.11.2014
comment
Я считаю важным отметить, что это ограничение является особенностью .NET Framework - я бы предпочел не иметь такой возможности. - person Herberth Amaral; 12.03.2015
comment
Этот ответ говорит о многом, но не говорит о том, как вы можете установить заголовок, который OP хочет установить. - person Superbest; 06.04.2015
comment
Ответ Chmod ниже действительно дает вам то, что вам нужно. т.е. приведен к HttpWebRequest, чтобы вы могли напрямую установить свойство. - person Lorenz03Tx; 02.12.2015

Я столкнулся с этой проблемой с настраиваемым веб-клиентом. Я думаю, что люди могут запутаться из-за множества способов сделать это. При использовании WebRequest.Create() вы можете преобразовать его в HttpWebRequest и использовать свойство для добавления или изменения заголовка. При использовании WebHeaderCollection вы можете использовать .Add("referer","my_url").

Ex 1

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

Ex 2

HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Referer = "http://stackoverflow.com";
request.UserAgent = "Mozilla/5.0";
response = (HttpWebResponse)request.GetResponse();
person Chmod    schedule 08.03.2010
comment
Ex 1 решил мою проблему с этим исключением. Поэтому я изменил client.Headers [referer] = url; в client.Headers.Add (referer, url); и все начинает работать. Спасибо. - person 000; 09.12.2011
comment
имейте в виду, что этот ответ содержит счастливое предположение, что вы работаете в среде выполнения рабочего стола .Net и запрашиваете http. WebRequest.Create может возвращать множество различных объектов в зависимости от того, какой префикс протокола вы используете. Это связано с CustomProtocolHandlers, если они кому-то интересны. А в WP7 или Silverlight классы реализации запросов тоже немного отличаются. Просто будь осторожен с этим. - person quetzalcoatl; 27.04.2012
comment
Но я не могу изменить заголовок Accept. Как я могу это изменить? - person user; 02.08.2013
comment
Первый пример все еще дает мне ту же ошибку - person mrid; 18.08.2019

Все предыдущие ответы описывают проблему, не предлагая решения. Вот метод расширения, который решает проблему, позволяя вам установить любой заголовок через его строковое имя.

Использование

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

person Despertar    schedule 14.04.2014
comment
Я использую его для прокси-соединения, но после того, как он сказал, да, я содержал ключ для прокси-соединения, он возвращает значение null, что приводит к исключению нулевой ссылки - person deadManN; 09.02.2016
comment
Спасибо за умное исправление. Я позволил расширению установить все заголовки: 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); } } } - person Suncat2000; 29.04.2019

У меня было такое же исключение, когда мой код пытался установить значение заголовка «Принять» следующим образом:

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";
person Mike Gledhill    schedule 16.03.2016

Каждый раз, когда вы меняете заголовки HttpWebRequest, вам необходимо использовать соответствующие свойства самого объекта, если они существуют. Если у вас есть простой WebRequest, сначала не забудьте преобразовать его в HttpWebRequest. Тогда к Referrer в вашем случае можно будет получить доступ через ((HttpWebRequest)request).Referrer, поэтому вам не нужно напрямую изменять заголовок - просто установите для свойства правильное значение. ContentLength, ContentType, UserAgent и т. Д. Все должны быть настроены таким образом.

IMHO, это недостаток со стороны MS ... установка заголовков через Headers.Add() должна автоматически вызывать соответствующее свойство за кулисами, если это то, что они хотят сделать.

person jvenema    schedule 01.12.2008

WebRequest является абстрактным (и поскольку любой наследующий класс должен переопределять свойство Headers) .. какой конкретный WebRequest вы используете? Другими словами, как заставить этот объект WebRequest соответствовать?

эх .. ваш ответ заставил меня понять, что сообщение об ошибке, которое вы получаете, на самом деле правильное: оно говорит вам, что заголовок, который вы пытаетесь добавить, уже существует, и вы должны затем изменить его значение, используя соответствующее свойство (например, индексатор ) вместо того, чтобы пытаться добавить его снова. Вероятно, это все, что вы искали.

Другие классы, унаследованные от WebRequest, могут иметь даже лучшие свойства, обертывающие определенные заголовки; См. этот пост.

person FOR    schedule 27.10.2008
comment
Фактически WebRequest.Create (url) создает экземпляр объекта WebRequest. - person Igal Tabachnik; 27.10.2008

Все вышеперечисленные ответы хороши, но суть проблемы в том, что одни заголовки задаются одним способом, а другие - другим. См. Выше списки «ограниченных заголовков». Для этого вы просто устанавливаете их как свойство. Для других вы фактически добавляете заголовок. Посмотреть здесь.

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

    request.Accept = "application/json";

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

В основном нет. Это заголовок http, поэтому разумно привести к HttpWebRequest и установить .Referer (как вы указываете в вопросе):

HttpWebRequest req = ...
req.Referer = "your url";
person Marc Gravell    schedule 27.10.2008

Примечание: это решение будет работать с 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;
    ...
    }

Итак, со всем вышеперечисленным, вот код, который использует отражение для поиска статической Hashtable в классе HeaderInfoTable, а затем изменяет каждый ограниченный запросом 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");
                            }

                        }
                    }

                }
            }
        } 
person Sleeper    schedule 28.10.2019
comment
Блестяще! Это также позволяет установить эти заголовки для запроса, используемого при настройке веб-сокетов, и тем самым обойти эту проблему: github.com/dotnet/corefx/issues/26627 - person Øystein Kolsrud; 08.11.2019
comment
Так должно быть, потому что все они используют WebHeaderCollection для управления заголовками. Я тестировал его только на HttpWebRequest. - person Sleeper; 09.11.2019

Я использую только:

request.ContentType = "application/json; charset=utf-8"
person Stefan Michev    schedule 11.03.2015

Вы можете просто передать WebRequest в HttpWebRequest, показанный ниже:

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

а затем вместо того, чтобы пытаться манипулировать списком заголовков, примените его непосредственно в запросе свойства запроса.

request.Referer = "yourReferer";

Эти свойства доступны в объекте запроса.

person Bonomi    schedule 24.04.2015

У меня возникла такая же проблема, как у меня работал фрагмент кода.

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

request.Headers["UserAgent"] = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; 
Trident/5.0)"
person Vaibhav    schedule 25.11.2020