Не удается установить некоторые заголовки HTTP при использовании System.Net.WebRequest
Когда я пытаюсь добавить пару ключ / значение заголовка HTTP на WebRequest
объект, я получаю следующее исключение:
Этот заголовок должен быть изменен с использованием соответствующего свойства
Я пытался добавить новые значения в Headers
сбор с помощью метода Add(), но я все еще получаю то же исключение.
webRequest.Headers.Add(HttpRequestHeader.Referer, "http://stackru.com");
Я могу обойти это, приведя объект WebRequest к HttpWebRequest и установив такие свойства, как httpWebReq.Referer ="http://stackru.com"
, но это работает только для нескольких заголовков, которые открываются через свойства.
Я хотел бы знать, есть ли способ получить более точный контроль над изменением заголовков с помощью запроса на удаленный ресурс.
13 ответов
Если вам нужен краткий и технический ответ, перейдите прямо к последнему разделу ответа.
Если вы хотите узнать лучше, прочитайте все это, и я надеюсь, вам понравится...
Я тоже сегодня противостоял этой проблеме, и сегодня я обнаружил, что:
Вышеуказанные ответы верны, так как:
1.1 он говорит вам, что заголовок, который вы пытаетесь добавить, уже существует, и вы должны затем изменить его значение, используя соответствующее свойство (например, индексатор), вместо того, чтобы пытаться добавить его снова.
1.2 В любое время вы меняете заголовки
HttpWebRequest
Вам необходимо использовать соответствующие свойства самого объекта, если они существуют.
Спасибо ЗА и 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)
Решение состоит в том, чтобы проверить, существует ли заголовок уже или ограничен (
WebHeaderCollection.IsRestricted(key)
) перед звонком добавить
I ran into this problem with a custom web client. I think people may be getting confused because of multiple ways to do this. Когда используешь WebRequest.Create()
you can cast to an HttpWebRequest
and use the property to add or modify a header. При использовании WebHeaderCollection
Вы можете использовать .Add("referer","my_url")
,
Ex 1
WebClient client = new WebClient();
client.Headers.Add("referer", "http://stackru.com");
client.Headers.Add("user-agent", "Mozilla/5.0");
Ex 2
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Referer = "http://stackru.com";
request.UserAgent = "Mozilla/5.0";
response = (HttpWebResponse)request.GetResponse();
Все предыдущие ответы описывают проблему, не предоставляя решения. Вот метод расширения, который решает проблему, позволяя вам установить любой заголовок через его строковое имя.
использование
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
У меня было то же исключение, когда мой код пытался установить значение заголовка "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";
Каждый раз, когда вы меняете заголовки HttpWebRequest
Вам необходимо использовать соответствующие свойства самого объекта, если они существуют. Если у вас есть равнина WebRequest
, обязательно приведи его к HttpWebRequest
первый. затем Referrer
в вашем случае можно получить доступ через ((HttpWebRequest)request).Referrer
, так что вам не нужно изменять заголовок напрямую - просто установите для свойства правильное значение. ContentLength
, ContentType
, UserAgent
и т. д., все должно быть установлено таким образом.
ИМХО, это недостаток со стороны MS... настройка заголовков через Headers.Add()
должен автоматически вызвать соответствующее свойство за кулисами, если это то, что они хотят сделать.
WebRequest является абстрактным (и поскольку любой наследующий класс должен переопределять свойство Headers).. какой конкретный WebRequest вы используете? Другими словами, как заставить этот объект WebRequest связываться?
эх.. mnour ответ заставил меня понять, что сообщение об ошибке, которое вы получаете, на самом деле точно: оно сообщает вам, что заголовок, который вы пытаетесь добавить, уже существует, и вам нужно затем изменить его значение, используя соответствующее свойство (например, индексатор)), вместо того, чтобы пытаться добавить его снова. Это, наверное, все, что вы искали.
Другие классы, наследуемые от WebRequest, могут иметь даже лучшие свойства, заключающие в себе определенные заголовки; Смотрите этот пост, например.
Примечание: это решение будет работать с 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");
}
}
}
}
}
}
Все приведенные выше ответы хороши, но суть проблемы заключается в том, что некоторые заголовки задаются одним способом, а другие - другим. См. Выше для списков "ограниченного заголовка". Для этого вы просто устанавливаете их как собственность. Для других вы фактически добавляете заголовок. Посмотреть здесь.
request.ContentType = "application/x-www-form-urlencoded";
request.Accept = "application/json";
request.Headers.Add(HttpRequestHeader.Authorization, "Basic " + info.clientId + ":" + info.clientSecret);
В основном нет. Это заголовок http, поэтому целесообразно привести HttpWebRequest
и установить .Referer
(как вы указали в вопросе):
HttpWebRequest req = ...
req.Referer = "your url";
Я использую всего лишь:
request.ContentType = "application/json; charset=utf-8"
Вы можете просто привести WebRequest к HttpWebRequest, показанному ниже:
var request = (HttpWebRequest)WebRequest.Create(myUri);
и затем вместо того, чтобы пытаться манипулировать списком заголовков, примените его непосредственно в свойстве запроса request.Referer:
request.Referer = "yourReferer";
Эти свойства доступны в объекте запроса.
У меня возникла такая же проблема, как у меня работал фрагмент кода.
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"));