.Net HttpWebRequest.GetResponse () gera uma exceção quando o código de status http 400 (solicitação incorreta) é retornado

205

Estou em uma situação em que, quando recebo um código HTTP 400 do servidor, é uma maneira completamente legal do servidor me dizer o que havia de errado com minha solicitação (usando uma mensagem no conteúdo da resposta HTTP)

No entanto, o .NET HttpWebRequest gera uma exceção quando o código de status é 400.

Como eu manejo isso? Para mim, um 400 é completamente legal e bastante útil. O conteúdo HTTP tem algumas informações importantes, mas a exceção me tira do meu caminho.

chefsmart
fonte
6
Eu estava experimentando a mesma coisa. Enviei uma sugestão para a equipe do .NET Framework. Sinta-se à vontade para votar a favor: connect.microsoft.com/VisualStudio/feedback/details/575075/…
Jonas Stawski

Respostas:

344

Seria bom se houvesse alguma maneira de desativar o "código de ativação sem êxito", mas se você capturar o WebException, pode pelo menos usar a resposta:

using System;
using System.IO;
using System.Web;
using System.Net;

public class Test
{
    static void Main()
    {
        WebRequest request = WebRequest.Create("http://csharpindepth.com/asd");
        try
        {
            using (WebResponse response = request.GetResponse())
            {
                Console.WriteLine("Won't get here");
            }
        }
        catch (WebException e)
        {
            using (WebResponse response = e.Response)
            {
                HttpWebResponse httpResponse = (HttpWebResponse) response;
                Console.WriteLine("Error code: {0}", httpResponse.StatusCode);
                using (Stream data = response.GetResponseStream())
                using (var reader = new StreamReader(data))
                {
                    string text = reader.ReadToEnd();
                    Console.WriteLine(text);
                }
            }
        }
    }
}

Você pode encapsular o bit "me dê uma resposta mesmo que não seja um código de sucesso" em um método separado. (Eu sugiro que você ainda jogue se não houver uma resposta, por exemplo, se você não conseguir se conectar.)

Se a resposta do erro for grande (o que é incomum), convém ajustar HttpWebRequest.DefaultMaximumErrorResponseLengthpara garantir que você receba o erro inteiro.

Jon Skeet
fonte
5
O conteúdo do fluxo retornado por GetResponseStream () na resposta anexada ao WebException é apenas o nome do código de status (por exemplo, "Solicitação incorreta"), em vez da resposta realmente retornada pelo servidor. Existe alguma maneira de obter essas informações?
Mark Watts
@ MarkWatts: deve ser o que foi devolvido pelo servidor e está em todas as situações que já vi. Você pode reproduzir isso com um URL externo específico? Sugiro que você faça uma nova pergunta (referindo-se a esta) e mostre o que está acontecendo.
perfil completo de Jon Skeet
Acontece que isso é feito apenas quando o tamanho da resposta é zero; ele adiciona uma descrição textual do código de status HTTP - 400 é apenas "Solicitação inválida", mas algumas outras são mais descritivas.
Mark Watts
Se alguém souber de um bom wrapper para esta classe, coloque um link para ele aqui. System.Net.WebClient se comporta da mesma maneira, invocando o sistema de tratamento de exceções.
John K
1
@AnkushJain: Eu acredito que ele retornará normalmente para 2XX; o redirecionamento pode ocorrer para o 3XX, dependendo da configuração - eu esperaria que qualquer outra coisa causasse uma exceção, embora eu possa estar errado. (Para 4XX, é possível que ele irá em seguida, aplicar auth informações se ele tem, mas não tinha já usou.)
Jon Skeet
48

Sei que isso já foi respondido há muito tempo, mas criei um método de extensão para ajudar outras pessoas que chegam a essa pergunta.

Código:

public static class WebRequestExtensions
{
    public static WebResponse GetResponseWithoutException(this WebRequest request)
    {
        if (request == null)
        {
            throw new ArgumentNullException("request");
        }

        try
        {
            return request.GetResponse();
        }
        catch (WebException e)
        {
            if (e.Response == null)
            {
                throw;
            }

            return e.Response;
        }
    }
}

Uso:

var request = (HttpWebRequest)WebRequest.CreateHttp("http://invalidurl.com");

//... (initialize more fields)

using (var response = (HttpWebResponse)request.GetResponseWithoutException())
{
    Console.WriteLine("I got Http Status Code: {0}", response.StatusCode);
}
Mateus
fonte
2
WebException.Responsepode e pode ser null. Você deve repetir se for esse o caso.
Ian Kemp
@ Davidid Concordo, o uso é um pouco irregular, embora, para a maioria das coisas, você deva estar usando em HttpClientvez disso, é muito mais configurável e acredito que seja o caminho do futuro.
Matthew
A verificação da solicitação == null é realmente necessária? Como esse é um método de extensão, a tentativa de usá-lo em um objeto nulo deve gerar uma exceção de referência nula antes de atingir o código do método de extensão ...... certo?
kwill
@kwill Os métodos de extensão são apenas métodos estáticos, fazendo uma validação de entrada adequada para evitar confusões, especialmente quando não são invocados com a sintaxe do método de extensão. Além disso, esta é a abordagem consistente feita pelas equipes do .NET: github.com/dotnet/corefx/blob/…
Matthew
1
Desculpe, eu não entendi sua segunda pergunta. ((WebRequest) null).GetResponseWithoutException()de fato, não causará a NullReferenceException, uma vez que é compilado com o equivalente a WebRequestExtensions.GetResponseWithoutException(null), o que não resultaria em a NullReferenceException, daí a necessidade de validação de entrada.
Mateus
13

Curiosamente, o HttpWebResponse.GetResponseStream()que você obtém do WebException.Responsenão é o mesmo que o fluxo de resposta que você teria recebido do servidor. Em nosso ambiente, estamos perdendo respostas reais do servidor quando um código de status HTTP 400 é retornado ao cliente usando os HttpWebRequest/HttpWebResponseobjetos. Pelo que vimos, o fluxo de resposta associado ao WebException's HttpWebResponseé gerado no cliente e não inclui nenhum corpo de resposta do servidor. Muito frustrante, pois queremos enviar uma mensagem ao cliente sobre o motivo da solicitação incorreta.

Christopher Bartling
fonte
Eu uso o método HEAD e causa essa exceção, mas ao usar GET, não há nenhum problema. Qual é o problema exatamente com o método HEAD?
Mohammad Afrashteh
12

Tive problemas semelhantes ao tentar me conectar ao serviço OAuth2 do Google.

Acabei escrevendo o POST manualmente, sem usar o WebRequest, assim:

TcpClient client = new TcpClient("accounts.google.com", 443);
Stream netStream = client.GetStream();
SslStream sslStream = new SslStream(netStream);
sslStream.AuthenticateAsClient("accounts.google.com");

{
    byte[] contentAsBytes = Encoding.ASCII.GetBytes(content.ToString());

    StringBuilder msg = new StringBuilder();
    msg.AppendLine("POST /o/oauth2/token HTTP/1.1");
    msg.AppendLine("Host: accounts.google.com");
    msg.AppendLine("Content-Type: application/x-www-form-urlencoded");
    msg.AppendLine("Content-Length: " + contentAsBytes.Length.ToString());
    msg.AppendLine("");
    Debug.WriteLine("Request");
    Debug.WriteLine(msg.ToString());
    Debug.WriteLine(content.ToString());

    byte[] headerAsBytes = Encoding.ASCII.GetBytes(msg.ToString());
    sslStream.Write(headerAsBytes);
    sslStream.Write(contentAsBytes);
}

Debug.WriteLine("Response");

StreamReader reader = new StreamReader(sslStream);
while (true)
{  // Print the response line by line to the debug stream for inspection.
    string line = reader.ReadLine();
    if (line == null) break;
    Debug.WriteLine(line);
}

A resposta que é gravada no fluxo de resposta contém o texto de erro específico que você procura.

Em particular, meu problema era que eu estava colocando linhas finais entre dados codificados em url. Quando os tirei, tudo funcionou. Você pode usar uma técnica semelhante para se conectar ao seu serviço e ler o texto real do erro de resposta.

Malabarista
fonte
2
HttpWebRequest está tão bagunçado. Até soquetes são mais fáceis (porque eles não escondem os erros de você).
Agent_L 26/02/12
6

Tente isso (é código VB :-):

Try

Catch exp As WebException
  Dim sResponse As String = New StreamReader(exp.Response.GetResponseStream()).ReadToEnd
End Try
Bernd
fonte
3

Uma versão assíncrona da função de extensão:

    public static async Task<WebResponse> GetResponseAsyncNoEx(this WebRequest request)
    {
        try
        {
            return await request.GetResponseAsync();
        }
        catch(WebException ex)
        {
            return ex.Response;
        }
    }
Jason Hu
fonte
0

Isso resolveu para mim:
https://gist.github.com/beccasaurus/929007/a8f820b153a1cfdee3d06a9c0a1d7ebfced8bb77

TL; DR:
Problema: o
host local retorna o conteúdo esperado, o IP remoto altera o conteúdo de 400 para "Solicitação incorreta"
Solução:
Adicionando <httpErrors existingResponse="PassThrough"></httpErrors>para web.config/configuration/system.webServerresolver isso para mim; agora todos os servidores (local e remoto) retornam exatamente o mesmo conteúdo (gerado por mim), independentemente do endereço IP e / ou código HTTP que eu retorno.

dlchambers
fonte