Não é possível definir alguns cabeçalhos HTTP ao usar System.Net.WebRequest

130

Quando tento adicionar um par de chave / valor de cabeçalho HTTP em um WebRequestobjeto, recebo a seguinte exceção:

Este cabeçalho deve ser modificado usando a propriedade apropriada

Tentei adicionar novos valores à Headerscoleção usando o método Add (), mas ainda recebo a mesma exceção.

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

Eu posso contornar isso lançando o objeto WebRequest para um HttpWebRequest e definindo as propriedades como httpWebReq.Referer ="http://stackoverflow.com", mas isso funciona apenas para um punhado de cabeçalhos expostos por meio de propriedades.

Gostaria de saber se existe uma maneira de obter um controle mais refinado sobre a modificação de cabeçalhos com uma solicitação de recurso remoto.

Navalha
fonte

Respostas:

182

Se você precisar da resposta curta e técnica, vá direto para a última seção da resposta.

Se você quiser conhecer melhor, leia tudo, e espero que você goste ...


Eu lutei contra esse problema também hoje, e o que descobri hoje é o seguinte:

  1. as respostas acima são verdadeiras, como:

    1.1, está lhe dizendo que o cabeçalho que você está tentando adicionar já existe e você deve modificar seu valor usando a propriedade apropriada (o indexador, por exemplo), em vez de tentar adicioná-lo novamente.

    1.2 Sempre que você alterar os cabeçalhos de um HttpWebRequest, precisará usar as propriedades apropriadas no próprio objeto, se existirem.

Obrigado e Jvenema pelas principais diretrizes ...

  1. Mas, o que eu descobri, e essa foi a peça que faltava no quebra - cabeça é que:

    2.1 A WebHeaderCollectionclasse geralmente é acessada através de WebRequest.Headers ou WebResponse.Headers. Alguns cabeçalhos comuns são considerados restritos e são expostos diretamente pela API (como Tipo de conteúdo) ou protegidos pelo sistema e não podem ser alterados.

Os cabeçalhos restritos são:

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

Portanto, da próxima vez que você enfrentar essa exceção e não souber como solucionar isso, lembre-se de que existem alguns cabeçalhos restritos e a solução é modificar seus valores usando a propriedade apropriada explicitamente na classe WebRequest/ HttpWebRequest.


Editar: (útil, de comentários, comentário do usuário Kaido )

A solução é verificar se o cabeçalho já existe ou está restrito ( WebHeaderCollection.IsRestricted(key)) antes de chamar add

dubi
fonte
8
"modificar seus valores utilizando a propriedade adequada" diz tudo
CRice
76
Esta resposta está apenas repetindo a mensagem das exceções sem fornecer uma solução para o problema.
000
11
Solução é verificar se o cabeçalho já existe ou está restrito (WebHeaderCollection.IsRestricted (chave)) antes de chamar add
Kaido
7
Leia a seção 1.1, que resolve o problema. isso significa que a propriedade que estamos tentando adicionar Headers.Add()já existe; portanto, devemos modificá-la.
Junaid Qadir
4
"Sinto que é importante ressaltar que essa restrição é um recurso do .NET Framework" - prefiro não ter esse tipo de recurso.
Herberth Amaral
76

Corri para esse problema com um cliente Web personalizado. Eu acho que as pessoas podem estar ficando confusas por causa de várias maneiras de fazer isso. Ao usar, WebRequest.Create()você pode converter em HttpWebRequeste usar a propriedade para adicionar ou modificar um cabeçalho. Ao usar um, WebHeaderCollectionvocê pode usar o .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
fonte
1
O Ex 1 resolveu meu problema com essa exceção. Então eu mudei client.Headers ["referer"] = url; para client.Headers.Add ("referer", url); e as coisas funcionam. Obrigado.
000
2
tenha em atenção que esta resposta contém uma suposição feliz de que você está trabalhando no tempo de execução .Net da área de trabalho e solicitando http. O WebRequest.Create pode retornar uma variedade de objetos diferentes, dependendo do prefixo do protocolo usado. Está relacionado ao CustomProtocolHandlers se alguém estiver interessado neles. E no WP7 ou no Silverlight, as classes de implementação de solicitação também são um pouco diferentes. Apenas tenha cuidado com isso.
quetzalcoatl
1
Mas não consigo modificar o cabeçalho "Aceitar". Como posso modificar isso?
User
O primeiro exemplo ainda está me dando o mesmo erro
mrid 18/08/19
29

Todas as respostas anteriores descrevem o problema sem fornecer uma solução. Aqui está um método de extensão que resolve o problema, permitindo que você defina qualquer cabeçalho através do nome da string.

Uso

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

Classe de extensão

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

Cenários

Eu escrevi um wrapper para HttpWebRequeste não queria expor todos os 13 cabeçalhos restritos como propriedades no meu wrapper. Em vez disso, eu queria usar um simples Dictionary<string, string>.

Outro exemplo é um proxy HTTP, no qual você precisa pegar os cabeçalhos de uma solicitação e encaminhá-los ao destinatário.

Existem muitos outros cenários em que simplesmente não é prático ou possível usar propriedades. Forçar o usuário a definir o cabeçalho por meio de uma propriedade é um design muito inflexível e é por isso que a reflexão é necessária. O lado positivo é que a reflexão é abstraída, ainda é rápida (0,001 segundo nos meus testes) e, como um método de extensão, parece natural.

Notas

Os nomes de cabeçalho não diferenciam maiúsculas de minúsculas de acordo com o RFC, http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2

Despertar
fonte
i usá-lo para Proxy-Connection, mas depois de dizer, sim eu conter a chave para "Proxy-Connection" que o retorno de null, que levam a exceção de referência nula
deadManN
Obrigado pela solução inteligente. Eu deixei a extensão definir todos os cabeçalhos: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

Eu tive a mesma exceção quando meu código tentou definir o valor do cabeçalho "Accept" como este:

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

A solução foi alterá-lo para isso:

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

Sempre que você alterar os cabeçalhos de um HttpWebRequest, precisará usar as propriedades apropriadas no próprio objeto, se existirem. Se você tem uma planície WebRequest, certifique-se de convertê-la para uma HttpWebRequestprimeira. Então, Referrerno seu caso, pode ser acessado via ((HttpWebRequest)request).Referrer, para que você não precise modificar o cabeçalho diretamente - basta definir a propriedade com o valor certo. ContentLength, ContentType, UserAgent, Etc, tudo precisa ser definida desta forma.

IMHO, isso é uma falha da parte do MS ... definir os cabeçalhos via Headers.Add()deve chamar automaticamente a propriedade apropriada nos bastidores, se é isso que eles querem fazer.

jvenema
fonte
7

WebRequest sendo abstrato (e como qualquer classe herdada deve substituir a propriedade Headers) .. qual WebRequest concreto você está usando? Em outras palavras, como você consegue alinhar esse objeto WebRequest?

ehr .. mnour resposta me fez perceber que a mensagem de erro que você estava recebendo está realmente correta: está dizendo que o cabeçalho que você está tentando adicionar já existe e você deve modificar seu valor usando a propriedade apropriada (o indexador, por exemplo ), em vez de tentar adicioná-lo novamente. Provavelmente é tudo o que você estava procurando.

Outras classes herdadas do WebRequest podem ter propriedades ainda melhores envolvendo determinados cabeçalhos; Veja este post, por exemplo.

PARA
fonte
Na verdade, WebRequest.Create (url) cria uma instância de um objeto WebRequest.
Igal Tabachnik
2

As respostas acima são boas, mas a essência do problema é que alguns cabeçalhos são definidos de uma maneira e outros são definidos de outras maneiras. Veja acima as listas de 'cabeçalho restrito'. Para isso, basta defini-los como uma propriedade. Para outros, você realmente adiciona o cabeçalho. Veja aqui.

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

    request.Accept = "application/json";

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

Basicamente, não. Como é um cabeçalho http, é razoável converter HttpWebRequeste definir o .Referer(como você indica na pergunta):

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

Nota: esta solução funcionará com WebClientSocket, HttpWebRequest ou qualquer outra classe que use WebHeaderCollection para trabalhar com cabeçalhos.

Se você olhar o código fonte do WebHeaderCollection.cs, verá que o Hinfo é usado para manter informações de todos os cabeçalhos conhecidos:

private static readonly HeaderInfoTable HInfo = new HeaderInfoTable();

Observando a classe HeaderInfoTable, você pode observar que todos os dados são armazenados na tabela de hash

private static Hashtable HeaderHashTable;

Além disso, no contratador estático do HeaderInfoTable, é possível ver todos os cabeçalhos conhecidos sendo adicionados à matriz HeaderInfo e copiados para a hashtable.

A análise final da classe HeaderInfo mostra os nomes dos 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;
    ...
    }

Portanto, com todo o exposto, aqui está um código que usa reflexão para localizar Hashtable estático na classe HeaderInfoTable e altera todos os HeaderInfo restritos a solicitações dentro da tabela de hash para ser irrestrito

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

                        }
                    }

                }
            }
        } 
Dorminhoco
fonte
Brilhante! Isso também faz com que seja possível definir os cabeçalhos do pedido usado quando a criação de Web Sockets e trabalhar, assim, contornar este problema: github.com/dotnet/corefx/issues/26627
Øystein Kolsrud
Esse deve ser o caso, porque todos eles usam o WebHeaderCollection para manipular cabeçalhos. Eu testei no HttpWebRequest apenas tho.
Sleeper
0

Estou usando apenas:

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

Você pode simplesmente converter o WebRequest em um HttpWebRequest mostrado abaixo:

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

e, em vez de tentar manipular a lista de cabeçalhos, aplique-a diretamente na propriedade de solicitação request.Referer:

request.Referer = "yourReferer";

Essas propriedades estão disponíveis no objeto de solicitação.

Bonomi
fonte