.Net HttpWebRequest.GetResponse () lève une exception lorsque le code d'état http 400 (mauvaise demande) est renvoyé

205

Je suis dans une situation où lorsque j'obtiens un code HTTP 400 du serveur, c'est un moyen complètement légal du serveur de me dire ce qui n'allait pas avec ma demande (en utilisant un message dans le contenu de la réponse HTTP)

Toutefois, le .NET HttpWebRequest déclenche une exception lorsque le code d'état est 400.

Comment dois-je gérer cela? Pour moi, un 400 est tout à fait légal et plutôt utile. Le contenu HTTP contient des informations importantes mais l'exception me jette hors de mon chemin.

chefsmart
la source
6
Je vivais la même chose. J'ai soumis une suggestion à l'équipe .NET Framework. N'hésitez pas à voter pour cela: connect.microsoft.com/VisualStudio/feedback/details/575075/…
Jonas Stawski

Réponses:

344

Ce serait bien s'il y avait un moyen de désactiver "jeter sur le code de non-réussite" mais si vous attrapez WebException, vous pouvez au moins utiliser la réponse:

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

Vous voudrez peut-être encapsuler le bit "obtenez-moi une réponse même si ce n'est pas un code de réussite" dans une méthode distincte. (Je vous suggère de toujours lancer s'il n'y a pas de réponse, par exemple si vous ne parvenez pas à vous connecter.)

Si la réponse d'erreur peut être importante (ce qui est inhabituel), vous pouvez modifier HttpWebRequest.DefaultMaximumErrorResponseLengthpour vous assurer d'obtenir l'erreur complète.

Jon Skeet
la source
5
Le contenu du flux renvoyé par GetResponseStream () sur la réponse attachée à WebException n'est que le nom du code d'état (par exemple "Bad Request") plutôt que la réponse réellement retournée par le serveur. Existe-t-il un moyen d'obtenir ces informations?
Mark Watts
@MarkWatts: Ce devrait être tout ce qui a été retourné par le serveur, et cela a été le cas dans toutes les situations que j'ai vues. Pouvez-vous reproduire cela avec une URL externe particulière? Je vous suggère de poser une nouvelle question (en référence à celle-ci) et de montrer ce qui se passe.
Jon Skeet
Il s'avère que cela ne se produit que lorsque la longueur du contenu de la réponse est nulle; il ajoute une description textuelle du code de statut HTTP - 400 est juste "Bad Request" mais certains des autres sont plus descriptifs.
Mark Watts
Si quelqu'un connaît un bon emballage pour cette classe, déposez un lien vers celui-ci ici. System.Net.WebClient se comporte de la même manière en appelant le système de gestion des exceptions.
John K
1
@AnkushJain: Je pense qu'il reviendra normalement pour 2XX; la redirection peut se produire pour 3XX selon la configuration - je m'attendrais à ce que quoi que ce soit d'autre cause une exception, bien que je puisse me tromper. (Pour 4XX, il est juste possible qu'il applique ensuite les informations d'authentification s'il les possède mais ne les a pas déjà utilisées.)
Jon Skeet
48

Je sais que cela a déjà été répondu il y a longtemps, mais j'ai fait une méthode d'extension pour, espérons-le, aider d'autres personnes qui viennent à cette question.

Code:

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

Usage:

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);
}
Matthieu
la source
2
WebException.Responsepeut et peut être null. Vous devriez recommencer si c'est le cas.
Ian Kemp
@DavidP Je suis d'accord, l'utilisation est un peu bizarre, mais pour la plupart des choses, vous devriez probablement utiliser à la HttpClientplace, c'est beaucoup plus configurable, et je crois que c'est la voie de l'avenir.
Matthew
La vérification de la demande == null est-elle vraiment nécessaire? Puisqu'il s'agit d'une méthode d'extension, essayer de l'utiliser sur un objet nul devrait lever une exception de référence nulle avant qu'elle ne frappe le code de la méthode d'extension ...
kwill
@kwill Les méthodes d'extension ne sont que des méthodes statiques, une validation d'entrée correcte doit être effectuée pour éviter toute confusion, en particulier lorsqu'elles ne sont pas invoquées avec la syntaxe de la méthode d'extension. C'est également l'approche cohérente des équipes .NET: github.com/dotnet/corefx/blob/…
Matthieu
1
Désolé d'avoir mal compris votre deuxième question. ((WebRequest) null).GetResponseWithoutException()ne provoquera en fait pas a NullReferenceException, car il est compilé à l'équivalent de WebRequestExtensions.GetResponseWithoutException(null), ce qui n'entraînerait pas de a NullReferenceException, d'où la nécessité d'une validation d'entrée.
Matthew
13

Fait intéressant, le HttpWebResponse.GetResponseStream()que vous obtenez du WebException.Responsen'est pas le même que le flux de réponse que vous auriez reçu du serveur. Dans notre environnement, nous perdons les réponses réelles du serveur lorsqu'un code d' état 400 HTTP est renvoyé au client à l'aide des HttpWebRequest/HttpWebResponseobjets. D'après ce que nous avons vu, le flux de réponse associé au WebException's HttpWebResponseest généré sur le client et n'inclut aucun des corps de réponse du serveur. Très frustrant, car nous voulons signaler au client la raison de la mauvaise demande.

Christopher Bartling
la source
J'utilise la méthode HEAD et provoque cette exception, mais lors de l'utilisation de GET, il n'y a aucun problème. Quel est le problème avec la méthode HEAD exactement?
Mohammad Afrashteh
12

J'ai rencontré des problèmes similaires en essayant de me connecter au service OAuth2 de Google.

J'ai fini par écrire le POST manuellement, sans utiliser WebRequest, comme ceci:

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 réponse qui est écrite dans le flux de réponse contient le texte d'erreur spécifique que vous recherchez.

En particulier, mon problème était que je mettais des lignes de fond entre des données encodées en URL. Quand je les ai sortis, tout a fonctionné. Vous pourrez peut-être utiliser une technique similaire pour vous connecter à votre service et lire le texte de l'erreur de réponse réelle.

Jongleur
la source
2
HttpWebRequest est tellement foiré. Même les prises sont plus faciles (car elles ne cachent pas les erreurs).
Agent_L
6

Essayez ceci (c'est VB-Code :-):

Try

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

Une version asynchrone de la fonction d'extension:

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

Cela m'a résolu:
https://gist.github.com/beccasaurus/929007/a8f820b153a1cfdee3d06a9c0a1d7ebfced8bb77

TL; DR:
Problème: l'
hôte local renvoie le contenu attendu, l'IP distant modifie le contenu 400 en "Bad Request"
Solution:
L'ajout <httpErrors existingResponse="PassThrough"></httpErrors>à web.config/configuration/system.webServerrésolu cela pour moi; maintenant, tous les serveurs (locaux et distants) renvoient exactement le même contenu (généré par moi) indépendamment de l'adresse IP et / ou du code HTTP que je renvoie.

dlchambers
la source