当HTTP状态码返回400(错误请求)时,.Net HttpWebRequest.GetResponse()会引发异常

230

我遇到了这样的情况:当服务器返回 HTTP 400 状态码时,它可以通过 HTTP 响应内容中的消息合法地告诉我请求存在问题。

然而,.NET HttpWebRequest 在状态码为 400 时会抛出异常。

我该如何处理这个问题呢?对我来说,400 是完全合法且非常有帮助的。HTTP 内容包含一些重要信息,但异常却使我偏离了正确的路径。


7
我也经历了同样的事情。我向.NET Framework团队提出了建议。欢迎为其投票: https://connect.microsoft.com/VisualStudio/feedback/details/575075/no-exception-on-httpwebrequest-getresponse#details - Jonas Stawski
7个回答

401
如果有一种方法可以关闭“在非成功代码上抛出异常”的话会很好,但是如果你捕获到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)
            {
                // TODO: Handle response being null
                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以确保获取完整的错误信息。

6
在与 WebException 相关联的响应上调用 GetResponseStream() 返回的流的内容只包含状态码的名称(例如“Bad Request”),而不是服务器实际返回的响应。是否有办法获取这些信息? - Mark Watts
@JonSkeet,你必须设置HttpWebRequest.DefaultMaximumErrorResponseLength以控制可用的响应流长度。默认情况下,读取错误情况下的长度可能太短了。 - Ghita
1
@AnkushJain: 我相信它会对2XX正常返回;对于3XX,根据配置可能会发生重定向 - 不过我认为其他任何情况都可能引起异常,尽管我可能错了。(对于4XX,如果有权限信息但尚未使用,则有可能应用它。) - Jon Skeet
1
你救了我。详见此处 https://stackoverflow.com/questions/65542563/microsoft-identity-platform-and-oauth-2-0-authorization-code-flow-error-400-bad/65543128#65543128 非常感谢。 - Manight
1
@Suncat2000:编辑(简要)指明了这一点。 - Jon Skeet
显示剩余12条评论

52

我知道这个问题早已有答案,但我写了一个扩展方法,希望能帮助其他来到这个问题的人。

代码:

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

3
WebException.Response 可能为 null。如果是这种情况,您应该重新抛出异常。 - Ian Kemp
@DavidP 我同意,使用起来有点不太顺畅,但对于大多数情况,你应该使用 HttpClient,它更加可配置,而且我相信这是未来的趋势。 - Matthew
检查请求是否为null真的有必要吗?由于这是一个扩展方法,尝试在空对象上使用它应该会在它到达扩展方法代码之前抛出空引用异常......对吧? - kwill
@kwill 扩展方法只是静态方法,应该进行适当的输入验证以避免混淆,特别是当它们不使用扩展方法语法调用时。此外,这是.NET团队采取的一致方法:https://github.com/dotnet/corefx/blob/6a2b3f07891f5f9fb8936a298840da3c26e95c7a/src/System.Linq/src/System/Linq/Where.cs#L15 - Matthew
1
抱歉,我误解了你的第二个问题。实际上 ((WebRequest) null).GetResponseWithoutException() 不会引起 NullReferenceException,因为它被编译成等同于 WebRequestExtensions.GetResponseWithoutException(null) 的代码,这不会导致 NullReferenceException,因此需要进行输入验证。 - Matthew

15

有趣的是,从WebException.Response获取的HttpWebResponse.GetResponseStream()与从服务器收到的响应流不同。在我们的环境中,当使用HttpWebRequest/HttpWebResponse对象将400 HTTP状态代码返回给客户端时,我们正在失去实际的服务器响应。据我们所见,与WebException的HttpWebResponse相关联的响应流是在客户端生成的,并且不包括来自服务器的任何响应正文。这非常令人沮丧,因为我们想要向客户端传回错误请求的原因。


我使用HEAD方法时出现了异常,但是当使用GET方法时就没有任何问题。到底HEAD方法有什么问题? - Mohammad Afrashteh
@MohammadAfrashteh HEAD在响应中不包含消息体,因此无可用的响应流。 - Suncat2000

13

在尝试连接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编码的数据片段之间放置了换行符。当我将它们删掉后,一切都正常了。您可能能够使用类似的技术连接到您的服务并读取实际的响应错误文本。


2
HttpWebRequest太糟糕了。甚至套接字都更容易(因为它们不会将错误隐藏起来)。 - Agent_L
那么我该如何设置主体? - ilidiocn

6

尝试以下代码(VB 语言):

Try

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

4

扩展函数的异步版本:

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

-1

这对我解决了问题:
https://gist.github.com/beccasaurus/929007/a8f820b153a1cfdee3d06a9c0a1d7ebfced8bb77

简而言之:
问题:
本地主机返回预期内容,远程IP将400内容更改为“错误请求”
解决方案:
web.config/configuration/system.webServer中添加<httpErrors existingResponse="PassThrough"></httpErrors>对我来说解决了这个问题;现在所有服务器(本地和远程)都返回由我生成的完全相同的内容,无论我返回的IP地址和/或HTTP代码是什么。


这与WebHttpRequest在HTTP代码不成功时抛出异常无关。 - GSerg
@GSerg那原因是什么?不要只是批评而没有包含一些您认为评论有效的见解。 - dlchambers
问题的原因是WebHttpRequest类被编写为在HTTP代码不成功时抛出异常,这让很多人感到不方便。这与控制服务器设置的web.config配置无关。你所谈论的问题(观察到的HTTP有效载荷)与WebHttpRequest在HTTP代码不成功时抛出异常无关。 - GSerg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接