.Net HttpWebRequest.GetResponse () zgłasza wyjątek, gdy zwracany jest kod stanu HTTP 400 (złe żądanie)

205

Mam sytuację, w której kiedy otrzymuję kod HTTP 400 z serwera, jest to całkowicie legalny sposób, w jaki serwer mówi mi, co było nie tak z moim żądaniem (za pomocą komunikatu w treści odpowiedzi HTTP)

Jednak HttpWebRequest .NET zgłasza wyjątek, gdy kod stanu to 400.

Jak sobie z tym poradzić? Dla mnie 400 jest całkowicie legalne i raczej pomocne. Treść HTTP zawiera pewne ważne informacje, ale wyjątek zrzuca mnie ze ścieżki.

szef kuchni
źródło
6
Doświadczyłem tego samego. Przesłałem sugestię do zespołu .NET Framework. Głosuj na to: connect.microsoft.com/VisualStudio/feedback/details/575075/...
Jonas Stawski

Odpowiedzi:

344

Byłoby miło, gdyby istniał jakiś sposób wyłączenia „wrzuć kod nieudany”, ale jeśli złapiesz WebException, możesz przynajmniej użyć odpowiedzi:

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

Być może chciałbyś zawrzeć bit „daj mi odpowiedź, nawet jeśli nie jest to kod sukcesu” w osobnej metodzie. (Sugeruję, abyś nadal rzucał, jeśli nie ma odpowiedzi, np. Jeśli nie możesz się połączyć).

Jeśli odpowiedź na błąd może być duża (co jest nietypowe), możesz dostosować HttpWebRequest.DefaultMaximumErrorResponseLengthją tak, aby uzyskać cały błąd.

Jon Skeet
źródło
5
Zawartość strumienia zwróconego przez GetResponseStream () w odpowiedzi dołączonej do WebException to tylko nazwa kodu stanu (np. „Błędne żądanie”), a nie odpowiedź faktycznie zwrócona przez serwer. Czy jest jakiś sposób na uzyskanie tych informacji?
Mark Watts
@MarkWatts: To powinno być to, co zostało zwrócone przez serwer, i było w każdej sytuacji, jaką widziałem. Czy możesz to odtworzyć z określonym zewnętrznym adresem URL? Proponuję zadać nowe pytanie (odnosząc się do tego) i pokazać, co się dzieje.
Jon Skeet
Okazuje się, że robi to tylko wtedy, gdy długość treści odpowiedzi wynosi zero; dodaje tekstowy opis kodu statusu HTTP - 400 to po prostu „Błędne żądanie”, ale niektóre inne są bardziej opisowe.
Mark Watts
Jeśli ktoś wie o fajnym opakowaniu dla tej klasy, upuść link do niego tutaj. System.Net.WebClient zachowuje się w ten sam sposób, wywołując system obsługi wyjątków.
John K
1
@AnkushJain: Wierzę, że normalnie zwróci 2XX; przekierowanie może się zdarzyć dla 3XX w zależności od konfiguracji - oczekiwałbym, że cokolwiek innego spowoduje wyjątek, chociaż mogę się mylić. (Dla 4XX, to po prostu możliwe, że będzie następnie zastosować auth informacje, jeśli ma go, ale nie już wykorzystane.)
Jon Skeet
48

Wiem, że już dawno na nie udzielono odpowiedzi, ale mam nadzieję, że zastosowałem metodę rozszerzenia, aby pomóc innym osobom, które odpowiedzą na to pytanie.

Kod:

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

Stosowanie:

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);
}
Mateusz
źródło
2
WebException.Responsemoże i może być null. W takim przypadku powinieneś rzucić ponownie.
Ian Kemp,
@DavidP Zgadzam się, użycie jest trochę szalone, chociaż w większości rzeczy prawdopodobnie powinieneś używać HttpClientzamiast tego, jest o wiele bardziej konfigurowalne i wierzę, że jest to droga przyszłości.
Matthew
Czy sprawdzanie żądania == null jest naprawdę konieczne? Ponieważ jest to metoda rozszerzenia, próba użycia jej na obiekcie o wartości NULL powinna zgłosić wyjątek odwołania o wartości NULL, zanim trafi ona na kod metody rozszerzenia ...... prawda?
kwill
@kwill Metody rozszerzeń są tylko metodami statycznymi, należy wykonać poprawną weryfikację danych wejściowych, aby uniknąć nieporozumień, szczególnie gdy nie są wywoływane ze składnią metody rozszerzenia. Jest to również konsekwentne podejście zespołów .NET: github.com/dotnet/corefx/blob/…
Matthew
1
Przepraszam, źle zrozumiałem twoje drugie pytanie. ((WebRequest) null).GetResponseWithoutException()w rzeczywistości nie spowoduje a NullReferenceException, ponieważ jest skompilowany do odpowiednika WebRequestExtensions.GetResponseWithoutException(null), co nie spowodowałoby a NullReferenceException, stąd potrzeba weryfikacji danych wejściowych.
Matthew
13

Co ciekawe, to HttpWebResponse.GetResponseStream(), co otrzymujesz z, WebException.Responsenie jest tym samym, co strumień odpowiedzi, który otrzymałeś z serwera. W naszym środowisku tracimy rzeczywiste odpowiedzi serwera, gdy kod statusu 400 HTTP jest zwracany klientowi za pomocą HttpWebRequest/HttpWebResponseobiektów. Z tego, co widzieliśmy, strumień odpowiedzi powiązany z WebException's HttpWebResponsegenerowany jest na kliencie i nie zawiera żadnej treści odpowiedzi z serwera. Bardzo frustrujące, ponieważ chcemy przekazać klientowi wiadomość o przyczynie złej prośby.

Christopher Bartling
źródło
Używam metody HEAD i powoduję ten wyjątek, ale przy użyciu GET nie ma żadnego problemu. Na czym dokładnie polega metoda HEAD?
Mohammad Afrashteh
12

Miałem podobne problemy podczas próby połączenia się z usługą Google OAuth2.

W końcu napisałem POST ręcznie, nie używając WebRequest, jak to:

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

Odpowiedź zapisywana do strumienia odpowiedzi zawiera konkretny tekst błędu, którego szukasz.

W szczególności mój problem polegał na tym, że umieszczałem punkty końcowe między danymi zakodowanymi w adresie URL. Kiedy je wyjąłem, wszystko działało. Możesz być w stanie użyć podobnej techniki, aby połączyć się z usługą i odczytać tekst błędu odpowiedzi.

Jugglist
źródło
2
HttpWebRequest jest tak popsuty. Nawet gniazda są łatwiejsze (ponieważ nie ukrywają przed tobą błędów).
Agent_L,
6

Spróbuj tego (to kod VB :-):

Try

Catch exp As WebException
  Dim sResponse As String = New StreamReader(exp.Response.GetResponseStream()).ReadToEnd
End Try
Bernd
źródło
3

Asynchroniczna wersja funkcji rozszerzenia:

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

To rozwiązało dla mnie:
https://gist.github.com/beccasaurus/929007/a8f820b153a1cfdee3d06a9c0a1d7ebfced8bb77

TL; DR:
Problem:
localhost zwraca oczekiwaną treść, zdalny adres IP zmienia treść 400 na „Złe żądanie”
Rozwiązanie:
Dodanie <httpErrors existingResponse="PassThrough"></httpErrors>do web.config/configuration/system.webServermnie rozwiązało; teraz wszystkie serwery (lokalny i zdalny) zwracają dokładnie tę samą treść (wygenerowaną przeze mnie) bez względu na adres IP i / lub kod HTTP, który zwracam.

dlchambers
źródło