.Net HttpWebRequest.GetResponse () вызывает исключение при возврате кода состояния http 400 (неверный запрос)

205

Я нахожусь в ситуации, когда я получаю код HTTP 400 с сервера, это совершенно законный способ, когда сервер сообщает мне, что случилось с моим запросом (используя сообщение в содержимом ответа HTTP)

Однако .NET HttpWebRequest вызывает исключение, когда код состояния равен 400.

Как мне справиться с этим? Для меня 400 вполне законно и довольно полезно. Содержимое HTTP содержит некоторую важную информацию, но исключение сбивает меня с пути.

chefsmart
источник
6
Я испытывал то же самое. Я отправил предложение в команду .NET Framework. Не стесняйтесь голосовать за это: connect.microsoft.com/VisualStudio/feedback/details/575075/…
Джонас Ставски

Ответы:

344

Было бы неплохо, если бы был какой-то способ отключить «бросить на неуспешный код», но если вы перехватываете WebException, вы можете по крайней мере использовать ответ:

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

Возможно, вы захотите заключить бит «получите ответ, даже если это не код успеха» в отдельный метод. (Я бы посоветовал вам все равно бросить, если нет ответа, например, если вы не можете подключиться.)

Если реакция на ошибку может быть большой (что необычно), вы можете настроить ее, HttpWebRequest.DefaultMaximumErrorResponseLengthчтобы убедиться, что вы получите всю ошибку.

Джон Скит
источник
5
Содержимое потока, возвращаемого функцией GetResponseStream () в ответе, прикрепленном к WebException, является просто именем кода состояния (например, «Неверный запрос»), а не ответом, фактически возвращаемым сервером. Есть ли способ получить эту информацию?
Марк Уоттс
@MarkWatts: Это должно быть то, что было возвращено сервером и было в каждой ситуации, которую я видел. Можете ли вы воспроизвести это с определенным внешним URL? Я предлагаю вам задать новый вопрос (ссылаясь на этот) и показать, что происходит.
Джон Скит
Оказывается, он делает это только тогда, когда длина содержимого ответа равна нулю; он добавляет текстовое описание кода состояния HTTP - 400 - просто «Плохой запрос», но некоторые другие являются более описательными.
Марк Уоттс
Если кто-нибудь знает о хорошей обёртке для этого класса, напишите сюда ссылку. System.Net.WebClient ведет себя так же, вызывая систему обработки исключений.
Джон К
1
@AnkushJain: я верю, что он вернется нормально для 2XX; перенаправление может произойти для 3XX в зависимости от конфигурации - я ожидаю, что что-то еще вызовет исключение, хотя я могу ошибаться. (Для 4XX, вполне возможно, что он затем применяет аутентификационную информацию, если она у нее есть, но она еще не использовалась.)
Джон Скит,
48

Я знаю, что на это уже давным-давно ответили, но я сделал метод расширения, чтобы, надеюсь, помочь другим людям, которые приходят на этот вопрос.

Код:

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

Использование:

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);
}
Мэтью
источник
2
WebException.Responseможет и может быть null. Вы должны сбросить, если это так.
Ян Кемп
@ DavidP Я согласен, использование немного дрянное, хотя для большинства вещей, вероятно, вы должны использовать HttpClientвместо этого, это гораздо более настраиваемый, и я считаю, что это путь в будущее.
Мэтью
Проверка запроса == null действительно необходима? Поскольку это метод расширения, попытка использовать его для нулевого объекта должна вызвать исключение нулевой ссылки, прежде чем оно попадет в код метода расширения ...... верно?
kwill
@kwill Методы расширения - это просто статические методы, поэтому следует избегать путаницы с правильной проверкой входных данных, особенно если они не вызываются с помощью синтаксиса метода расширения. Кроме того, это последовательный подход, используемый командами .NET: github.com/dotnet/corefx/blob/…
Матфея
1
Извините, я неправильно понял ваш второй вопрос. ((WebRequest) null).GetResponseWithoutException()на самом деле не вызовет a NullReferenceException, так как он скомпилирован в эквивалент WebRequestExtensions.GetResponseWithoutException(null), который не приведет к a NullReferenceException, следовательно, требуется проверка входных данных.
Матфея
13

Интересно, HttpWebResponse.GetResponseStream()что то, что вы получаете от, WebException.Responseне совпадает с потоком ответов, который вы получили бы от сервера. В нашей среде мы теряем фактические ответы сервера, когда код состояния HTTP 400 возвращается клиенту с помощью HttpWebRequest/HttpWebResponseобъектов. Из того, что мы видели, поток ответов, связанный с, WebException's HttpWebResponseгенерируется на клиенте и не содержит тела ответа от сервера. Очень расстраивает, так как мы хотим сообщить клиенту причину плохого запроса.

Кристофер Бартлинг
источник
Я использую метод HEAD и вызываю это исключение, но при использовании GET нет никаких проблем. В чем проблема метода HEAD?
Мохаммед Афраштех
12

У меня были похожие проблемы при попытке подключиться к сервису Google OAuth2.

Я закончил писать POST вручную, не используя WebRequest, например так:

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

Ответ, который записывается в поток ответов, содержит конкретный текст ошибки, который вы ищете.

В частности, моя проблема заключалась в том, что я помещал конечные строки между фрагментами данных, закодированными в URL. Когда я их вынул, все работало. Возможно, вы сможете использовать аналогичную технику для подключения к вашей службе и чтения фактического текста ошибки ответа.

Jugglist
источник
2
HttpWebRequest так запутался. Даже сокеты легче (потому что они не скрывают ошибок от вас).
Agent_L
6

Попробуйте это (это VB-код :-):

Try

Catch exp As WebException
  Dim sResponse As String = New StreamReader(exp.Response.GetResponseStream()).ReadToEnd
End Try
Бернд
источник
3

Асинхронная версия функции расширения:

    public static async Task<WebResponse> GetResponseAsyncNoEx(this WebRequest request)
    {
        try
        {
            return await request.GetResponseAsync();
        }
        catch(WebException ex)
        {
            return ex.Response;
        }
    }
Джейсон Ху
источник
0

Это решило это для меня:
https://gist.github.com/beccasaurus/929007/a8f820b153a1cfdee3d06a9c0a1d7ebfced8bb77

TL; DR:
Проблема:
LOCALHOST возвращает ожидаются содержание, удаленный IP изменяет содержание 400 «Bad Request»
Решение:
Добавление <httpErrors existingResponse="PassThrough"></httpErrors>к web.config/configuration/system.webServerрешить это для меня; теперь все серверы (локальные и удаленные) возвращают один и тот же контент (созданный мной) независимо от того, какой IP-адрес и / или HTTP-код я возвращаю.

dlchambers
источник