.Net HttpWebRequest.GetResponse () genera una excepción cuando se devuelve el código de estado http 400 (solicitud incorrecta)

205

Estoy en una situación en la que cuando obtengo un código HTTP 400 del servidor, es una forma completamente legal de que el servidor me diga qué estuvo mal con mi solicitud (usando un mensaje en el contenido de la respuesta HTTP)

Sin embargo, .NET HttpWebRequest genera una excepción cuando el código de estado es 400.

¿Cómo manejo esto? Para mí, un 400 es completamente legal y bastante útil. El contenido HTTP tiene información importante, pero la excepción me saca de mi camino.

chefsmart
fuente
66
Estaba experimentando lo mismo. Envié una sugerencia al equipo de .NET Framework. Siéntase libre de votar: connect.microsoft.com/VisualStudio/feedback/details/575075/…
Jonas Stawski

Respuestas:

344

Sería bueno si hubiera alguna forma de desactivar "lanzar código sin éxito", pero si detecta WebException, al menos puede usar la respuesta:

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

Es posible que desee encapsular el bit "obtenme una respuesta incluso si no es un código de éxito" en un método separado. (Te sugiero que sigas lanzando si no hay una respuesta, por ejemplo, si no puedes conectarte).

Si la respuesta de error puede ser grande (lo cual es inusual), es posible que desee ajustar HttpWebRequest.DefaultMaximumErrorResponseLengthpara asegurarse de obtener el error completo.

Jon Skeet
fuente
55
El contenido de la secuencia devuelta por GetResponseStream () en la respuesta adjunta a la WebException es solo el nombre del código de estado (por ejemplo, "Solicitud incorrecta") en lugar de la respuesta realmente devuelta por el servidor. ¿Hay alguna forma de obtener esta información?
Mark Watts el
@ MarkWatts: Debería ser lo que haya devuelto el servidor y haya estado en todas las situaciones que he visto. ¿Puedes reproducir esto con una URL externa particular? Le sugiero que haga una nueva pregunta (refiriéndose a esta) y muestre lo que está sucediendo.
Jon Skeet
Resulta que solo hace esto cuando la longitud del contenido de la respuesta es cero; agrega una descripción textual del código de estado HTTP: 400 es solo "Solicitud incorrecta", pero algunos de los otros son más descriptivos.
Mark Watts el
Si alguien sabe de un buen contenedor para esta clase, suelte un enlace aquí. System.Net.WebClient se comporta de la misma manera al invocar el sistema de manejo de excepciones.
John K
1
@AnkushJain: Creo que volverá normalmente para 2XX; la redirección puede suceder para 3XX dependiendo de la configuración; esperaría que cualquier otra cosa cause una excepción, aunque podría estar equivocado. (Para 4XX, es posible que luego aplique la información de autenticación si la tiene pero aún no la ha usado)
Jon Skeet
48

Sé que esto ya ha sido respondido hace mucho tiempo, pero hice un método de extensión para ayudar a otras personas a responder esta pregunta.

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);
}
Mateo
fuente
2
WebException.Responsepuede y puede ser null. Debería volver a lanzar si este es el caso.
Ian Kemp
@DavidP Estoy de acuerdo, el uso es un poco extraño, aunque para la mayoría de las cosas, probablemente deberías usarlo en su HttpClientlugar, es mucho más configurable y creo que es el camino del futuro.
Mateo
¿Es realmente necesario verificar la solicitud == nulo? Dado que este es un método de extensión, intentar usarlo en un objeto nulo debería generar una excepción de referencia nula antes de que llegue al código del método de extensión ... ¿verdad?
kwill
@kwill Los métodos de extensión son solo métodos estáticos, se debe realizar una validación de entrada adecuada para evitar confusiones, especialmente cuando no se invocan con la sintaxis del método de extensión. Además, este es el enfoque coherente realizado por los equipos .NET: github.com/dotnet/corefx/blob/…
Mateo
1
Lo siento, entendí mal tu segunda pregunta. ((WebRequest) null).GetResponseWithoutException()de hecho, no causará a NullReferenceException, ya que se compila al equivalente de WebRequestExtensions.GetResponseWithoutException(null), lo que no daría como resultado a NullReferenceException, de ahí la necesidad de validación de entrada.
Mateo
13

Curiosamente, lo HttpWebResponse.GetResponseStream()que obtienes del WebException.Responseno es lo mismo que el flujo de respuesta que hubieras recibido del servidor. En nuestro entorno, estamos perdiendo respuestas reales del servidor cuando se devuelve un código de estado 400 HTTP al cliente utilizando los HttpWebRequest/HttpWebResponseobjetos. Por lo que hemos visto, el flujo de respuesta asociado con el WebException's HttpWebResponsese genera en el cliente y no incluye ninguno de los cuerpos de respuesta del servidor. Muy frustrante, ya que queremos enviarle un mensaje al cliente el motivo de la mala solicitud.

Christopher Bartling
fuente
Uso el método HEAD y causo esta excepción, pero cuando uso GET no hay ningún problema. ¿Cuál es el problema con el método HEAD exactamente?
Mohammad Afrashteh
12

Tuve problemas similares al intentar conectarme al servicio OAuth2 de Google.

Terminé escribiendo el POST manualmente, sin usar WebRequest, así:

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

La respuesta que se escribe en la secuencia de respuesta contiene el texto de error específico que está buscando.

En particular, mi problema era que estaba poniendo líneas finales entre los datos codificados en URL. Cuando los saqué, todo funcionó. Es posible que pueda utilizar una técnica similar para conectarse a su servicio y leer el texto de error de respuesta real.

Jugglist
fuente
2
HttpWebRequest está muy desordenado. Incluso los sockets son más fáciles (porque no te ocultan los errores).
Agent_L
6

Pruebe esto (es el código VB :-):

Try

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

Una versión asincrónica de la función de extensión:

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

Esto lo resolvió para mí:
https://gist.github.com/beccasaurus/929007/a8f820b153a1cfdee3d06a9c0a1d7ebfced8bb77

TL; DR:
Problema:
localhost devuelve el contenido esperado, la IP remota altera el contenido de 400 a "Mala solicitud"
Solución:
Agregar<httpErrors existingResponse="PassThrough"></httpErrors> a web.config/configuration/system.webServerresolver esto por mí; ahora todos los servidores (locales y remotos) devuelven exactamente el mismo contenido (generado por mí) independientemente de la dirección IP y / o el código HTTP que devuelvo.

dlchambers
fuente