.Net HttpWebRequest.GetResponse () löst eine Ausnahme aus, wenn der http-Statuscode 400 (fehlerhafte Anforderung) zurückgegeben wird

205

Ich bin in einer Situation, in der es eine völlig legale Methode ist, wenn der Server mir sagt, was mit meiner Anfrage nicht stimmte, wenn ich einen HTTP 400-Code vom Server erhalte (mithilfe einer Nachricht im HTTP-Antwortinhalt).

Die .NET HttpWebRequest löst jedoch eine Ausnahme aus, wenn der Statuscode 400 lautet.

Wie gehe ich damit um? Für mich ist eine 400 völlig legal und ziemlich hilfreich. Der HTTP-Inhalt enthält einige wichtige Informationen, aber die Ausnahme wirft mich von meinem Weg ab.

Chefkoch
quelle
6
Ich habe das Gleiche erlebt. Ich habe dem .NET Framework-Team einen Vorschlag unterbreitet. Fühlen Sie sich frei, dafür zu stimmen: connect.microsoft.com/VisualStudio/feedback/details/575075/…
Jonas Stawski

Antworten:

344

Es wäre schön, wenn es eine Möglichkeit gäbe, "Nicht-Erfolgscode auslösen" zu deaktivieren, aber wenn Sie WebException abfangen, können Sie zumindest die folgende Antwort verwenden:

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

Möglicherweise möchten Sie das Bit "Erhalten Sie mir eine Antwort, auch wenn es sich nicht um einen Erfolgscode handelt" in einer separaten Methode zusammenfassen. (Ich würde vorschlagen, dass Sie immer noch werfen, wenn es keine Antwort gibt, z. B. wenn Sie keine Verbindung herstellen konnten.)

Wenn die Fehlerantwort groß ist (was ungewöhnlich ist), möchten Sie möglicherweise eine Optimierung HttpWebRequest.DefaultMaximumErrorResponseLengthvornehmen, um sicherzustellen, dass Sie den gesamten Fehler erhalten.

Jon Skeet
quelle
5
Der Inhalt des Streams, der von GetResponseStream () für die an die WebException angehängte Antwort zurückgegeben wird, ist nur der Name des Statuscodes (z. B. "Bad Request") und nicht die vom Server tatsächlich zurückgegebene Antwort. Gibt es eine Möglichkeit, diese Informationen zu erhalten?
Mark Watts
@ MarkWatts: Es sollte alles sein , was vom Server zurückgegeben wurde und in jeder Situation, die ich gesehen habe. Können Sie dies mit einer bestimmten externen URL reproduzieren? Ich schlage vor, Sie stellen eine neue Frage (unter Bezugnahme auf diese) und zeigen, was los ist.
Jon Skeet
Es stellt sich heraus, dass dies nur dann der Fall ist, wenn die Inhaltslänge der Antwort Null ist. Es wird eine Textbeschreibung des HTTP-Statuscodes hinzugefügt - 400 ist nur "Bad Request", aber einige der anderen sind aussagekräftiger.
Mark Watts
Wenn jemand einen netten Wrapper für diese Klasse kennt, klicken Sie hier. System.Net.WebClient verhält sich beim Aufrufen des Ausnahmebehandlungssystems genauso.
John K
1
@AnkushJain: Ich glaube, dass es für 2XX normal zurückkehren wird; Die Umleitung kann je nach Konfiguration für 3XX erfolgen. Ich würde erwarten, dass alles andere eine Ausnahme verursacht, obwohl ich mich irren könnte. (Für 4XX ist es nur möglich, dass es dann Authentifizierungsinformationen anwendet, wenn es diese hat, sie aber noch nicht verwendet hat.)
Jon Skeet
48

Ich weiß, dass dies bereits vor langer Zeit beantwortet wurde, aber ich habe eine Erweiterungsmethode entwickelt, um hoffentlich anderen Menschen zu helfen, die zu dieser Frage kommen.

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

Verwendung:

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);
}
Matthew
quelle
2
WebException.Responsekann und kann sein null. Sie sollten erneut werfen, wenn dies der Fall ist.
Ian Kemp
@DavidP Ich stimme zu, die Verwendung ist ein wenig ruckelig, obwohl Sie für die meisten Dinge wahrscheinlich HttpClientstattdessen verwenden sollten, es ist viel konfigurierbarer und ich glaube, es ist der Weg der Zukunft.
Matthew
Ist eine Überprüfung auf Anfrage == null wirklich notwendig? Da dies eine Erweiterungsmethode ist, sollte der Versuch, sie für ein Nullobjekt zu verwenden, eine Nullreferenzausnahme auslösen, bevor sie auf den Code der Erweiterungsmethode trifft ...... richtig?
kwill
@kwill Erweiterungsmethoden sind nur statische Methoden. Eine ordnungsgemäße Eingabevalidierung sollte durchgeführt werden, um Verwirrung zu vermeiden, insbesondere wenn sie nicht mit der Syntax der Erweiterungsmethode aufgerufen werden. Dies ist auch der konsequente Ansatz der .NET-Teams: github.com/dotnet/corefx/blob/…
Matthew,
1
Entschuldigung, ich habe Ihre zweite Frage falsch verstanden. ((WebRequest) null).GetResponseWithoutException()wird in der Tat kein a verursachen NullReferenceException, da es auf das Äquivalent von kompiliert wird WebRequestExtensions.GetResponseWithoutException(null), was nicht zu einem führen würde NullReferenceException, daher die Notwendigkeit einer Eingabevalidierung.
Matthäus
13

Interessanterweise ist das HttpWebResponse.GetResponseStream(), was Sie von erhalten, WebException.Responsenicht dasselbe wie der Antwortstrom, den Sie vom Server erhalten hätten. In unserer Umgebung verlieren wir die tatsächlichen Serverantworten, wenn ein 400-HTTP-Statuscode mithilfe der HttpWebRequest/HttpWebResponseObjekte an den Client zurückgegeben wird . Wie wir gesehen haben, wird der mit dem verknüpfte Antwortstrom WebException's HttpWebResponseauf dem Client generiert und enthält keinen Antworttext vom Server. Sehr frustrierend, da wir dem Kunden den Grund für die schlechte Anfrage mitteilen möchten.

Christopher Bartling
quelle
Ich benutze die Methode HEAD und verursache diese Ausnahme, aber wenn ich GET benutze, gibt es kein Problem. Was genau ist das Problem mit der HEAD-Methode?
Mohammad Afrashteh
12

Ich hatte ähnliche Probleme beim Versuch, eine Verbindung zum OAuth2-Dienst von Google herzustellen.

Am Ende habe ich den POST manuell geschrieben und WebRequest nicht wie folgt verwendet:

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

Die Antwort, die in den Antwortstrom geschrieben wird, enthält den spezifischen Fehlertext, nach dem Sie suchen.

Insbesondere bestand mein Problem darin, dass ich Endlines zwischen URL-codierten Datenelementen platzierte. Als ich sie herausnahm, funktionierte alles. Möglicherweise können Sie eine ähnliche Technik verwenden, um eine Verbindung zu Ihrem Dienst herzustellen und den eigentlichen Antwortfehlertext zu lesen.

Jongleur
quelle
2
HttpWebRequest ist so durcheinander. Sogar Steckdosen sind einfacher (weil sie Fehler nicht vor Ihnen verbergen).
Agent_L
6

Versuchen Sie dies (es ist VB-Code :-):

Try

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

Eine asynchrone Version der Erweiterungsfunktion:

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

Dies löste es für mich:
https://gist.github.com/beccasaurus/929007/a8f820b153a1cfdee3d06a9c0a1d7ebfced8bb77

TL; DR:
Problem:
localhost gibt den erwarteten Inhalt zurück, Remote-IP ändert 400 Inhalte in "Bad Request".
Lösung:
Hinzufügen, <httpErrors existingResponse="PassThrough"></httpErrors>um web.config/configuration/system.webServerdies für mich zu lösen; Jetzt geben alle Server (lokal und remote) genau den gleichen Inhalt (von mir generiert) zurück, unabhängig von der IP-Adresse und / oder dem HTTP-Code, den ich zurückgebe.

Dlchambers
quelle