Nie można ustawić niektórych nagłówków HTTP podczas korzystania z System.Net.WebRequest

131

Kiedy próbuję dodać parę klucz / wartość nagłówka HTTP do WebRequestobiektu, pojawia się następujący wyjątek:

Ten nagłówek należy zmodyfikować przy użyciu odpowiedniej właściwości

Próbowałem dodać nowe wartości do Headerskolekcji za pomocą metody Add (), ale nadal otrzymuję ten sam wyjątek.

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

Mogę obejść ten problem, rzutując obiekt WebRequest na HttpWebRequest i ustawiając właściwości, takie jak httpWebReq.Referer ="http://stackoverflow.com", ale działa to tylko w przypadku kilku nagłówków, które są ujawniane za pośrednictwem właściwości.

Chciałbym wiedzieć, czy istnieje sposób na uzyskanie dokładniejszej kontroli nad modyfikowaniem nagłówków za pomocą żądania zdalnego zasobu.

Brzytwa
źródło

Odpowiedzi:

182

Jeśli potrzebujesz krótkiej i technicznej odpowiedzi, przejdź od razu do ostatniej części odpowiedzi.

Jeśli chcesz wiedzieć lepiej, przeczytaj to wszystko i mam nadzieję, że Ci się spodoba ...


Dzisiaj również rozwiązałem ten problem i dzisiaj odkryłem, że:

  1. powyższe odpowiedzi są prawdziwe, ponieważ:

    1.1 mówi ci, że nagłówek, który próbujesz dodać, już istnieje i powinieneś zmodyfikować jego wartość przy użyciu odpowiedniej właściwości (na przykład indeksatora), zamiast próbować dodać go ponownie.

    1.2 Za każdym razem, gdy zmieniasz nagłówki an HttpWebRequest, musisz użyć odpowiednich właściwości samego obiektu, jeśli takie istnieją.

Dzięki za i Jvenema za wiodące wytyczne ...

  1. Ale dowiedziałem się, a to był brakujący element układanki :

    2.1 WebHeaderCollectionKlasa jest ogólnie dostępna poprzez WebRequest.Headers lub WebResponse.Headers. Niektóre typowe nagłówki są uważane za ograniczone i są albo ujawniane bezpośrednio przez API (takie jak Content-Type), albo chronione przez system i nie można ich zmienić.

Ograniczone nagłówki to:

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

Więc następnym razem, gdy napotkasz ten wyjątek i nie wiesz, jak go rozwiązać, pamiętaj, że istnieją pewne ograniczone nagłówki, a rozwiązaniem jest zmodyfikowanie ich wartości za pomocą odpowiedniej właściwości jawnie z WebRequest/ HttpWebRequestclass.


Edycja: (przydatne, z komentarzy, komentarz użytkownika Kaido )

Rozwiązaniem jest sprawdzenie, czy nagłówek już istnieje lub czy jest ograniczony ( WebHeaderCollection.IsRestricted(key)) przed wywołaniem add

dubi
źródło
8
„modyfikuj ich wartości przy użyciu odpowiedniej właściwości” mówi wszystko
CRice,
76
Ta odpowiedź jest po prostu powtórzeniem komunikatu o wyjątkach bez podania rozwiązania problemu.
000
11
Rozwiązaniem jest sprawdzenie, czy nagłówek już istnieje lub jest ograniczona (WebHeaderCollection.IsRestricted (key)) przed wywołaniem dodatku
Kaido
7
@Sam przeczytaj sekcję 1.1, która rozwiązuje problem. oznacza to, że właściwość, którą próbujemy dodać, Headers.Add()już istnieje, dlatego powinniśmy ją zmodyfikować.
Junaid Qadir
4
„Czuję, że ważne jest, aby podkreślić, że to ograniczenie jest cechą .NET Framework” - wolałbym nie mieć tego rodzaju funkcji.
Herberth Amaral
78

Napotkałem ten problem z niestandardowym klientem internetowym. Myślę, że ludzie mogą być zdezorientowani z powodu wielu sposobów, aby to zrobić. Podczas używania WebRequest.Create()możesz rzutować na HttpWebRequesti użyć właściwości, aby dodać lub zmodyfikować nagłówek. Podczas korzystania z pliku WebHeaderCollectionmożesz użyć .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
źródło
1
Ex 1 rozwiązał mój problem z tym wyjątkiem. Więc zmieniłem client.Headers ["referer"] = url; do client.Headers.Add ("referer", url); i wszystko zaczyna działać. Dzięki.
000
2
uważaj, ta odpowiedź zawiera szczęśliwe założenie, że pracujesz w środowisku wykonawczym .Net na komputerze i pytasz o http. WebRequest.Create może zwrócić wiele różnych obiektów w zależności od używanego prefiksu protokołu. Jest to związane z CustomProtocolHandlers, jeśli ktoś jest nimi zainteresowany. A na WP7 lub Silverlight klasy implementacji żądań są też trochę inne. Po prostu uważaj na to.
quetzalcoatl
1
Ale nie mogę zmodyfikować nagłówka „Akceptuj”. Jak mogę to zmienić?
użytkownik
Pierwszy przykład wciąż daje ten sam błąd
mrid
31

Wszystkie poprzednie odpowiedzi opisują problem bez podania rozwiązania. Oto metoda rozszerzenia, która rozwiązuje problem, umożliwiając ustawienie dowolnego nagłówka za pomocą nazwy ciągu.

Stosowanie

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

Klasa rozszerzenia

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

Scenariusze

Napisałem opakowanie dla HttpWebRequesti nie chciałem ujawniać wszystkich 13 ograniczonych nagłówków jako właściwości w moim opakowaniu. Zamiast tego chciałem użyć prostego pliku Dictionary<string, string>.

Innym przykładem jest serwer proxy HTTP, w którym musisz pobrać nagłówki w żądaniu i przesłać je do odbiorcy.

Istnieje wiele innych scenariuszy, w których użycie właściwości jest po prostu niepraktyczne lub niemożliwe. Zmuszanie użytkownika do ustawienia nagłówka za pomocą właściwości jest bardzo nieelastycznym projektem, dlatego potrzebne jest odbicie. Zaletą jest to, że odbicie jest wyabstrahowane, nadal jest szybkie (0,001 sekundy w moich testach), a jako metoda rozszerzenia wydaje się naturalne.

Uwagi

W nazwach nagłówków wielkość liter nie jest rozróżniana zgodnie z RFC, http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2

Despertar
źródło
używam go do połączenia proxy, ale po tym, jak mówię, tak, zawieram klucz do połączenia proxy, zwraca wartość null, co prowadzi do zerowego wyjątku odniesienia
deadManN
Dziękuję za sprytną naprawę. Pozwoliłem rozszerzeniu ustawić wszystkie nagłówki: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

Wystąpił ten sam wyjątek, gdy mój kod próbował ustawić wartość nagłówka „Accept” w następujący sposób:

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

Rozwiązaniem było zmienić to na to:

HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://someServer:6405/biprws/logon/long");
request.Accept = "application/json";
Mike Gledhill
źródło
12

Za każdym razem, gdy zmieniasz nagłówki an HttpWebRequest, musisz użyć odpowiednich właściwości samego obiektu, jeśli istnieją. Jeśli masz zwykły WebRequest, pamiętaj, aby rzucić go na HttpWebRequestpierwszy. Następnie Referrerw twoim przypadku można uzyskać dostęp za pośrednictwem ((HttpWebRequest)request).Referrer, więc nie musisz bezpośrednio modyfikować nagłówka - po prostu ustaw właściwość na odpowiednią wartość. ContentLength, ContentType,UserAgent , Itp, wszyscy muszą być ustawione w ten sposób.

IMHO, to jest wada w części MS ... ustawienie nagłówków przez Headers.Add()powinno automatycznie wywoływać odpowiednią właściwość za kulisami, jeśli to jest to, co chcą zrobić.

jvenema
źródło
7

Żądanie WebRequest jest abstrakcyjne (i ponieważ każda klasa dziedzicząca musi przesłonić właściwość Headers) .. jakiego konkretnego żądania WebRequest używasz? Innymi słowy, jak sprawić, by obiekt WebRequest był używany?

ehr .. mnour odpowiedź uświadomiła mi, że otrzymany komunikat o błędzie jest rzeczywiście na miejscu: mówi ci, że nagłówek, który próbujesz dodać, już istnieje i powinieneś zmodyfikować jego wartość używając odpowiedniej właściwości (na przykład indeksator ), zamiast próbować dodać go ponownie. To prawdopodobnie wszystko, czego szukałeś.

Inne klasy dziedziczące po WebRequest mogą mieć jeszcze lepsze właściwości zawijające określone nagłówki; Zobacz na przykład ten post .

DLA
źródło
Właściwie WebRequest.Create (url) tworzy instancję obiektu WebRequest.
Igal Tabachnik
2

Wszystkie powyższe odpowiedzi są w porządku, ale istota problemu polega na tym, że niektóre nagłówki są ustawione w jedną stronę, a inne w inne. Zobacz wyżej listę „zastrzeżonych nagłówków”. W przypadku tych po prostu ustawiasz je jako właściwość. W przypadku innych faktycznie dodajesz nagłówek. Spójrz tutaj.

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

    request.Accept = "application/json";

    request.Headers.Add(HttpRequestHeader.Authorization, "Basic " + info.clientId + ":" + info.clientSecret);
Obrabować
źródło
1

Zasadniczo nie. To jest nagłówek http, więc rozsądne jest rzutowanie na HttpWebRequesti ustawienie .Referer(jak wskazałeś w pytaniu):

HttpWebRequest req = ...
req.Referer = "your url";
Marc Gravell
źródło
1

Uwaga: to rozwiązanie będzie działać z WebClientSocket, a także z HttpWebRequest lub dowolną inną klasą, która używa WebHeaderCollection do pracy z nagłówkami.

Jeśli spojrzysz na kod źródłowy WebHeaderCollection.cs, zobaczysz, że Hinfo jest używane do przechowywania informacji o wszystkich znanych nagłówkach:

private static readonly HeaderInfoTable HInfo = new HeaderInfoTable();

Patrząc na klasę HeaderInfoTable, można zauważyć, że wszystkie dane są przechowywane w tablicy skrótów

private static Hashtable HeaderHashTable;

Ponadto w statycznym konstruktorze HeaderInfoTable można zobaczyć, że wszystkie znane nagłówki są dodawane do tablicy HeaderInfo, a następnie kopiowane do tablicy hashy.

Ostateczne spojrzenie na klasę HeaderInfo pokazuje nazwy pól.

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

Tak więc, biorąc pod uwagę wszystkie powyższe, oto kod, który używa odbicia, aby znaleźć statyczną tabelę Hashtable w klasie HeaderInfoTable, a następnie zmienia każdą ograniczoną żądanie HeaderInfo wewnątrz tablicy hash, aby była nieograniczona

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

                        }
                    }

                }
            }
        } 
Podkład
źródło
Znakomity! Umożliwia to również ustawienie tych nagłówków dla żądania używanego podczas konfigurowania gniazd sieciowych, a tym samym obejście tego problemu: github.com/dotnet/corefx/issues/26627
Øystein Kolsrud
Powinno tak być, ponieważ wszystkie one używają WebHeaderCollection do manipulowania nagłówkami. Przetestowałem go tylko na HttpWebRequest.
Sleeper
0

Używam tylko:

request.ContentType = "application/json; charset=utf-8"
Stefan Michev
źródło
0

Możesz po prostu rzucić WebRequest na HttpWebRequest pokazane poniżej:

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

a następnie zamiast próbować manipulować listą nagłówków, zastosuj ją bezpośrednio w żądaniu właściwości żądania.

request.Referer = "yourReferer";

Te właściwości są dostępne w obiekcie żądania.

Bonomi
źródło