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

130

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

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

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

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

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

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

бритва
источник

Ответы:

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.


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

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

Dubi
источник
8
«изменить их значения с помощью соответствующего свойства» говорит
само за себя
76
Этот ответ просто повторяет сообщение об исключениях, не давая решения проблемы.
000
11
Решение состоит в том, чтобы проверить, существует ли заголовок уже или ограничен (WebHeaderCollection.IsRestricted (key)) перед вызовом add
Кайдо
7
@Sam прочитайте раздел 1.1, который решает проблему. это означает, что свойство, которое мы пытаемся добавить, Headers.Add()уже существует, поэтому мы должны изменить его.
Джунаид Кадир
4
«Я считаю важным отметить, что это ограничение является особенностью .NET Framework» - я бы предпочел не иметь такой возможности.
Herberth Amaral,
77

Я столкнулся с этой проблемой с помощью специального веб-клиента. Я думаю, что люди могут запутаться из-за множества способов сделать это. При использовании 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();
Chmod
источник
1
Ex 1 решил мою проблему с этим исключением. Поэтому я изменил client.Headers ["referer"] = url; в client.Headers.Add ("referer", url); и все начинает работать. Спасибо.
000
2
имейте в виду, что этот ответ содержит счастливое предположение, что вы работаете в среде выполнения рабочего стола .Net и запрашиваете http. WebRequest.Create может возвращать множество различных объектов в зависимости от того, какой префикс протокола вы используете. Это связано с CustomProtocolHandlers, если они кому-то интересны. А в WP7 или Silverlight классы реализации запросов тоже немного отличаются. Просто будь осторожен с этим.
quetzalcoatl
1
Но я не могу изменить заголовок «Принять». Как я могу это изменить?
пользователь
Первый пример все еще дает мне ту же ошибку
мрид
30

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

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

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, что приводит к исключению нулевой ссылки
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

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

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, поэтому вам не нужно напрямую изменять заголовок - просто установите для свойства правильное значение. ContentLength, ContentType, UserAgentИ т.д., все должны быть установлены таким образом.

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

jvenema
источник
7

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

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

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

ДЛЯ
источник
Фактически WebRequest.Create (url) создает экземпляр объекта WebRequest.
Игал Табачник
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;
    ...
    }

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

                        }
                    }

                }
            }
        } 
шпала
источник
Brilliant! Это также позволяет установить эти заголовки для запроса, используемого при настройке веб-сокетов, и, таким образом,
решить
Так должно быть, потому что все они используют WebHeaderCollection для управления заголовками. Я тестировал его только на HttpWebRequest.
Sleeper
0

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

request.ContentType = "application/json; charset=utf-8"
Стефан Мичев
источник
0

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

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

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

request.Referer = "yourReferer";

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

Bonomi
источник