No se pueden establecer algunos encabezados HTTP cuando se usa System.Net.WebRequest

130

Cuando intento agregar un par clave / valor de encabezado HTTP en un WebRequestobjeto, obtengo la siguiente excepción:

Este encabezado debe modificarse utilizando la propiedad apropiada

Intenté agregar nuevos valores a la Headerscolección usando el método Add () pero aún obtengo la misma excepción.

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

Puedo evitar esto convirtiendo el objeto WebRequest en un HttpWebRequest y estableciendo las propiedades como httpWebReq.Referer ="http://stackoverflow.com", pero esto solo funciona para un puñado de encabezados que se exponen a través de propiedades.

Me gustaría saber si hay una manera de obtener un control más preciso sobre la modificación de los encabezados con una solicitud de un recurso remoto.

Maquinilla de afeitar
fuente

Respuestas:

182

Si necesita una respuesta breve y técnica, vaya directamente a la última sección de la respuesta.

Si quieres saber mejor, léelo todo, y espero que disfrutes ...


También he contrarrestado este problema hoy, y lo que descubrí hoy es que:

  1. Las respuestas anteriores son verdaderas, como:

    1.1 le dice que el encabezado que está tratando de agregar ya existe y que debe modificar su valor utilizando la propiedad apropiada (el indexador, por ejemplo), en lugar de intentar agregarlo nuevamente.

    1.2 Cada vez que cambie los encabezados de un HttpWebRequest, debe usar las propiedades apropiadas en el objeto, si existen.

Gracias por y Jvenema por las pautas principales ...

  1. Pero, lo que descubrí, y esa fue la pieza que faltaba en el rompecabezas es que:

    2.1 WebHeaderCollectionGeneralmente se accede a la clase a través de WebRequest.Headers o WebResponse.Headers. Algunos encabezados comunes se consideran restringidos y están expuestos directamente por la API (como Content-Type) o protegidos por el sistema y no se pueden cambiar.

Los encabezados restringidos son:

  • Accept
  • Connection
  • Content-Length
  • Content-Type
  • Date
  • Expect
  • Host
  • If-Modified-Since
  • Range
  • Referer
  • Transfer-Encoding
  • User-Agent
  • Proxy-Connection

Entonces, la próxima vez que se enfrente a esta excepción y no sepa cómo resolver esto, recuerde que hay algunos encabezados restringidos, y la solución es modificar sus valores usando la propiedad apropiada explícitamente de la clase WebRequest/ HttpWebRequest.


Editar: (útil, de los comentarios, comentario del usuario Kaido )

La solución es verificar si el encabezado ya existe o está restringido ( WebHeaderCollection.IsRestricted(key)) antes de llamar a agregar

dubi
fuente
8
"modificar sus valores usando la propiedad apropiada" lo dice todo
CRice
76
Esta respuesta es solo repetir el Mensaje de las excepciones sin dar una solución al problema.
000
11
La solución es verificar si el encabezado ya existe o está restringido (WebHeaderCollection.IsRestricted (key)) antes de llamar a add
Kaido
77
@Sam leyó la sección 1.1 que resuelve el problema. eso significa que la propiedad que estamos tratando de agregar Headers.Add()ya existe, por lo tanto, deberíamos modificarla.
Junaid Qadir
44
"Creo que es importante señalar que esta restricción es una característica de .NET Framework". Prefiero no tener este tipo de característica.
Herberth Amaral
76

Me encontré con este problema con un cliente web personalizado. Creo que las personas pueden estar confundidas debido a múltiples formas de hacer esto. Cuando lo use WebRequest.Create(), puede emitir HttpWebRequesty usar la propiedad para agregar o modificar un encabezado. Cuando use un WebHeaderCollectionpuede usar el.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();
Chmod
fuente
1
El ex 1 resolvió mi problema con esta excepción. Así que cambié client.Headers ["referer"] = url; a client.Headers.Add ("referente", url); y las cosas se ponen a funcionar. Gracias.
000
2
tenga en cuenta que esta respuesta contiene una suposición feliz de que está trabajando en tiempo de ejecución .Net de escritorio y solicita http. WebRequest.Create puede devolver una variedad de objetos diferentes según el prefijo de protocolo que utilice. Está relacionado con CustomProtocolHandlers si alguien está interesado en ellos ... Y en WP7 o Silverlight las clases de implementación de solicitudes también son un poco diferentes. Solo ten cuidado con esto.
quetzalcoatl
1
Pero no puedo modificar el encabezado "Aceptar". ¿Cómo puedo modificar esto?
usuario
El primer ejemplo todavía me da el mismo error
mrid
29

Todas las respuestas anteriores describen el problema sin proporcionar una solución. Aquí hay un método de extensión que resuelve el problema al permitirle configurar cualquier encabezado a través de su nombre de cadena.

Uso

HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
request.SetRawHeader("content-type", "application/json");

Clase de extensión

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;
        }
    }
}

Escenarios

Escribí un contenedor para HttpWebRequesty no quería exponer los 13 encabezados restringidos como propiedades en mi contenedor. En cambio, quería usar un simpleDictionary<string, string> .

Otro ejemplo es un proxy HTTP donde necesita tomar encabezados en una solicitud y reenviarlos al destinatario.

Hay muchos otros escenarios en los que no es práctico ni posible usar propiedades. Forzar al usuario a establecer el encabezado a través de una propiedad es un diseño muy inflexible, por lo que se necesita reflexión. Lo bueno es que el reflejo se abstrae, sigue siendo rápido (0,001 segundo en mis pruebas) y, como método de extensión, se siente natural.

Notas

Los nombres de encabezado no distinguen entre mayúsculas y minúsculas según RFC, http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2

Despertar
fuente
lo uso para Proxy-Connection, pero después de decir, sí, contengo la clave para "Proxy-Connection", devuelve nulo, lo que conduce a una excepción de referencia nula
deadManN
Gracias por la solución inteligente. 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); } } }
Dejo
13

Tuve la misma excepción cuando mi código intentó establecer el valor del encabezado "Aceptar" de esta manera:

WebRequest request = WebRequest.Create("http://someServer:6405/biprws/logon/long");
request.Headers.Add("Accept", "application/json");

La solución fue cambiarlo a esto:

HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://someServer:6405/biprws/logon/long");
request.Accept = "application/json";
Mike Gledhill
fuente
12

Cada vez que cambie los encabezados de un HttpWebRequest, debe usar las propiedades apropiadas en el objeto, si existen. Si tienes un llano WebRequest, asegúrate de lanzarlo por HttpWebRequestprimera vez. Luego, Referreren su caso, se puede acceder a través de ((HttpWebRequest)request).Referrer, por lo que no necesita modificar el encabezado directamente, solo configure la propiedad en el valor correcto. ContentLength, ContentType, UserAgent, Etc, todo es necesario establecer de esta manera.

En mi humilde opinión, esta es una deficiencia en la parte de MS ... configurar los encabezados a través de Headers.Add()debería llamar automáticamente a la propiedad apropiada detrás de escena, si eso es lo que quieren hacer.

jvenema
fuente
7

WebRequest es abstracto (y dado que cualquier clase debe anular la propiedad Headers) ... ¿qué WebRequest concreta está utilizando? En otras palabras, ¿cómo se consigue que ese objeto WebRequest se alinee?

ehr ... nuestra respuesta me hizo darme cuenta de que el mensaje de error que estaba recibiendo es acertado: le dice que el encabezado que está intentando agregar ya existe y que debe modificar su valor utilizando la propiedad apropiada (el indexador, por ejemplo ), en lugar de intentar agregarlo nuevamente. Eso es probablemente todo lo que estabas buscando.

Otras clases que heredan de WebRequest pueden tener propiedades aún mejores que envuelven ciertos encabezados; Ver esta publicación, por ejemplo.

PARA
fuente
En realidad, WebRequest.Create (url) crea una instancia de un objeto WebRequest.
Igal Tabachnik
2

Las respuestas anteriores están bien, pero la esencia del problema es que algunos encabezados se configuran de una manera, y otros se configuran de otra manera. Ver arriba para las listas de 'encabezado restringido'. Para estos, simplemente los configura como una propiedad. Para otros, en realidad agrega el encabezado. Mira aquí.

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

    request.Accept = "application/json";

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

Básicamente no. Ese es un encabezado http, por lo que es razonable enviar HttpWebRequesty configurar el .Referer(como indica en la pregunta):

HttpWebRequest req = ...
req.Referer = "your url";
Marc Gravell
fuente
1

Nota: esta solución funcionará con WebClientSocket, así como con HttpWebRequest o cualquier otra clase que use WebHeaderCollection para trabajar con encabezados.

Si observa el código fuente de WebHeaderCollection.cs, verá que Hinfo se utiliza para mantener información de todos los encabezados conocidos:

private static readonly HeaderInfoTable HInfo = new HeaderInfoTable();

Al observar la clase HeaderInfoTable, puede observar que todos los datos se almacenan en la tabla hash

private static Hashtable HeaderHashTable;

Además, en el constructor estático de HeaderInfoTable, puede ver que todos los encabezados conocidos se agregan en la matriz HeaderInfo y luego se copian en la tabla hash.

La última mirada a la clase HeaderInfo muestra los nombres de los campos.

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;
    ...
    }

Entonces, con todo lo anterior, aquí hay un código que usa la reflexión para encontrar Hashtable estático en la clase HeaderInfoTable y luego cambia cada tabla de hash dentro de la tabla hash restringida por solicitud para que no esté restringida

        // 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");
                            }

                        }
                    }

                }
            }
        } 
Durmiente
fuente
¡Brillante! Esto también hace posible establecer esos encabezados para la solicitud utilizada al configurar los sockets web y, por lo tanto, solucionar
Øystein Kolsrud
Ese debería ser el caso porque todos usan WebHeaderCollection para manipular encabezados. Lo he probado solo en HttpWebRequest.
Durmiente el
0

Estoy usando solo:

request.ContentType = "application/json; charset=utf-8"
Stefan Michev
fuente
0

Puede enviar WebRequest a una HttpWebRequest que se muestra a continuación:

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

y luego, en lugar de tratar de manipular la lista de encabezados, aplíquela directamente en la solicitud de propiedad de solicitud.

request.Referer = "yourReferer";

Estas propiedades están disponibles en el objeto de solicitud.

Bonomi
fuente