Impossible de définir certains en-têtes HTTP lors de l'utilisation de System.Net.WebRequest

130

Lorsque j'essaie d'ajouter une paire clé / valeur d'en-tête HTTP sur un WebRequestobjet, j'obtiens l'exception suivante:

Cet en-tête doit être modifié à l'aide de la propriété appropriée

J'ai essayé d'ajouter de nouvelles valeurs à la Headerscollection en utilisant la méthode Add () mais j'obtiens toujours la même exception.

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

Je peux contourner cela en convertissant l'objet WebRequest en HttpWebRequest et en définissant les propriétés telles que httpWebReq.Referer ="http://stackoverflow.com", mais cela ne fonctionne que pour une poignée d'en-têtes exposés via des propriétés.

J'aimerais savoir s'il existe un moyen d'obtenir un contrôle plus fin sur la modification des en-têtes avec une demande de ressource distante.

Le rasoir
la source

Réponses:

182

Si vous avez besoin d'une réponse courte et technique, passez directement à la dernière section de la réponse.

Si vous voulez en savoir plus, lisez tout cela et j'espère que vous apprécierez ...


J'ai également combattu ce problème aujourd'hui, et ce que j'ai découvert aujourd'hui, c'est que:

  1. les réponses ci-dessus sont vraies, comme:

    1.1 cela vous indique que l'en-tête que vous essayez d'ajouter existe déjà et que vous devez alors modifier sa valeur en utilisant la propriété appropriée (l'indexeur, par exemple), au lieu d'essayer de l'ajouter à nouveau.

    1.2 Chaque fois que vous modifiez les en-têtes d'un HttpWebRequest, vous devez utiliser les propriétés appropriées sur l'objet lui-même, si elles existent.

Merci POUR et Jvenema pour les principales directives ...

  1. Mais, ce que j'ai découvert, et c'était la pièce manquante dans le puzzle, c'est que:

    2.1 La WebHeaderCollectionclasse est généralement accessible via WebRequest.Headers ou WebResponse.Headers. Certains en-têtes courants sont considérés comme restreints et sont soit exposés directement par l'API (comme Content-Type), soit protégés par le système et ne peuvent pas être modifiés.

Les en-têtes restreints sont:

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

Ainsi, la prochaine fois que vous rencontrez cette exception et que vous ne savez pas comment résoudre cela, rappelez-vous qu'il existe des en-têtes restreints et que la solution est de modifier leurs valeurs en utilisant la propriété appropriée explicitement de la classe WebRequest/ HttpWebRequest.


Edit: (utile, à partir des commentaires, commentaire de l'utilisateur Kaido )

La solution consiste à vérifier si l'en-tête existe déjà ou est restreint ( WebHeaderCollection.IsRestricted(key)) avant d'appeler add

Dubi
la source
8
"modifier leurs valeurs en utilisant la propriété appropriée" dit tout
CRice
76
Cette réponse ne fait que répéter le message des exceptions sans donner de solution au problème.
000
11
La solution consiste à vérifier si l'en-tête existe déjà ou est restreint (WebHeaderCollection.IsRestricted (key)) avant d'appeler add
Kaido
7
@Sam lisez la section 1.1 qui résout le problème. cela signifie que la propriété que nous essayons d'ajouter via Headers.Add()existe déjà, nous devrions donc la modifier à la place.
Junaid Qadir
4
"Je pense qu'il est important de souligner que cette restriction est une fonctionnalité du .NET Framework" - Je préfère ne pas avoir ce type de fonctionnalité.
Herberth Amaral
76

J'ai rencontré ce problème avec un client Web personnalisé. Je pense que les gens peuvent être confus à cause de plusieurs façons de le faire. Lors de l'utilisation, WebRequest.Create()vous pouvez effectuer un cast en HttpWebRequestet utiliser la propriété pour ajouter ou modifier un en-tête. Lorsque vous utilisez un, WebHeaderCollectionvous pouvez utiliser le .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
la source
1
L'Ex 1 a résolu mon problème avec cette exception. J'ai donc changé client.Headers ["referer"] = url; à client.Headers.Add ("referer", url); et les choses fonctionnent. Merci.
000
2
sachez que cette réponse contient une bonne hypothèse que vous travaillez sur le runtime .Net de bureau et que vous demandez http. WebRequest.Create peut renvoyer une variété d'objets différents en fonction du préfixe de protocole que vous utilisez. Il est lié à CustomProtocolHandlers si quelqu'un est intéressé par eux. Et sur WP7 ou Silverlight, les classes d'implémentation des requêtes sont également un peu différentes. Soyez prudent avec ça.
quetzalcoatl
1
Mais je ne peux pas modifier l'en-tête "Accepter". Comment puis-je modifier cela?
utilisateur
Le premier exemple me donne toujours la même erreur
mrid
29

Toutes les réponses précédentes décrivent le problème sans apporter de solution. Voici une méthode d'extension qui résout le problème en vous permettant de définir n'importe quel en-tête via son nom de chaîne.

Usage

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

Classe d'extension

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

Scénarios

J'ai écrit un wrapper pour HttpWebRequestet je ne voulais pas exposer les 13 en-têtes restreints en tant que propriétés dans mon wrapper. Au lieu de cela, je voulais utiliser un simple Dictionary<string, string>.

Un autre exemple est un proxy HTTP dans lequel vous devez prendre des en-têtes dans une requête et les transmettre au destinataire.

Il existe de nombreux autres scénarios où il n'est tout simplement pas pratique ou possible d'utiliser des propriétés. Forcer l'utilisateur à définir l'en-tête via une propriété est une conception très rigide, c'est pourquoi une réflexion est nécessaire. L'avantage est que la réflexion est abstraite, elle est toujours rapide (0,001 seconde dans mes tests) et, en tant que méthode d'extension, elle semble naturelle.

Remarques

Les noms d'en-tête sont insensibles à la casse selon la RFC, http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2

Despertar
la source
Je l'utilise pour Proxy-Connection, mais après cela, oui, je contient la clé pour "Proxy-Connection", il retourne null, ce qui conduit à une exception de référence nulle
deadManN
Merci pour la solution intelligente. Je laisse l'extension définir tous les en-têtes: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

J'ai eu la même exception lorsque mon code a essayé de définir la valeur d'en-tête "Accepter" comme ceci:

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

La solution était de le changer en ceci:

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

Chaque fois que vous modifiez les en-têtes d'un HttpWebRequest, vous devez utiliser les propriétés appropriées sur l'objet lui-même, si elles existent. Si vous avez une plaine WebRequest, assurez-vous de la lancer en HttpWebRequestpremier. Ensuite, Referrerdans votre cas, vous pouvez accéder via ((HttpWebRequest)request).Referrer, vous n'avez donc pas besoin de modifier l'en-tête directement - définissez simplement la propriété sur la bonne valeur. ContentLength, ContentType, UserAgent, Etc, doivent tous être mis de cette façon.

À mon humble avis, c'est une lacune de la part de MS ... la définition des en-têtes via Headers.Add()devrait automatiquement appeler la propriété appropriée dans les coulisses, si c'est ce qu'ils veulent faire.

jvenema
la source
7

WebRequest étant abstrait (et puisque toute classe héritière doit remplacer la propriété Headers) .. quelle WebRequest concrète utilisez-vous? En d'autres termes, comment obtenir cet objet WebRequest avec lequel s'aligner?

ehr .. ma réponse m'a fait réaliser que le message d'erreur que vous obteniez est en fait parfait: il vous dit que l'en-tête que vous essayez d'ajouter existe déjà et que vous devez ensuite modifier sa valeur en utilisant la propriété appropriée (l'indexeur, par exemple ), au lieu d'essayer de l'ajouter à nouveau. C'est probablement tout ce que vous recherchiez.

D'autres classes héritant de WebRequest peuvent avoir des propriétés encore meilleures encapsulant certains en-têtes; Voir cet article par exemple.

POUR
la source
En fait, WebRequest.Create (url) crée une instance d'un objet WebRequest.
Igal Tabachnik
2

Les réponses ci-dessus sont toutes correctes, mais l'essence du problème est que certains en-têtes sont définis dans un sens et d'autres dans d'autres. Voir ci-dessus pour les listes «d'en-têtes restreints». Pour ceux-ci, vous les définissez simplement comme une propriété. Pour les autres, vous ajoutez en fait l'en-tête. Vois ici.

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

    request.Accept = "application/json";

    request.Headers.Add(HttpRequestHeader.Authorization, "Basic " + info.clientId + ":" + info.clientSecret);
Rob
la source
1

En gros, non. Il s'agit d'un en-tête http, il est donc raisonnable de convertir HttpWebRequestet de définir le .Referer(comme vous l'indiquez dans la question):

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

Remarque: cette solution fonctionnera avec WebClientSocket ainsi qu'avec HttpWebRequest ou toute autre classe qui utilise WebHeaderCollection pour travailler avec les en-têtes.

Si vous regardez le code source de WebHeaderCollection.cs, vous verrez que Hinfo est utilisé pour conserver les informations de tous les en-têtes connus:

private static readonly HeaderInfoTable HInfo = new HeaderInfoTable();

En regardant la classe HeaderInfoTable, vous pouvez remarquer que toutes les données sont stockées dans une table de hachage

private static Hashtable HeaderHashTable;

En outre, dans le constructeur statique de HeaderInfoTable, vous pouvez voir que tous les en-têtes connus sont ajoutés dans le tableau HeaderInfo, puis copiés dans la table de hachage.

Un dernier regard sur la classe HeaderInfo montre les noms des champs.

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

Donc, avec tout ce qui précède, voici un code qui utilise la réflexion pour trouver Hashtable statique dans la classe HeaderInfoTable, puis modifie chaque HeaderInfo restreint à la demande dans la table de hachage pour ne pas être restreint

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

                        }
                    }

                }
            }
        } 
Dormeur
la source
Brillant! Cela permet également de définir ces en-têtes pour la requête utilisée lors de la configuration des sockets Web et ainsi de contourner ce problème: github.com/dotnet/corefx/issues/26627
Øystein Kolsrud
Cela devrait être le cas car ils utilisent tous WebHeaderCollection pour manipuler les en-têtes. Je l'ai testé sur HttpWebRequest uniquement.
Sleeper
0

J'utilise juste:

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

Vous pouvez simplement convertir la WebRequest en une HttpWebRequest présentée ci-dessous:

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

puis au lieu d'essayer de manipuler la liste des en-têtes, appliquez-la directement dans la demande de propriété de la requête.

request.Referer = "yourReferer";

Ces propriétés sont disponibles dans l'objet de requête.

Bonomi
la source