在Java中读取错误响应体

101

在Java中,当HTTP结果为404时,这段代码会抛出一个异常:

URL url = new URL("http://stackoverflow.com/asdf404notfound");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.getInputStream(); // throws!

在我的情况下,我知道内容是404,但我仍然想读取响应的正文。

(在我的实际情况中,响应代码为403,但响应正文解释了拒绝的原因,我想向用户显示该信息。)

我如何访问响应正文?


你确定服务器正在发送正文吗? - Hank Gay
2
@jdigital:HttpURLConnection.getInputStream() 抛出的异常是 java.io.FileNotFoundException。(主要提到这一点是为了更好地进行谷歌搜索。) - Jonik
8个回答

187

这是错误报告(关闭,不会修复,不是错误)。

他们在那里的建议是这样编码:

HttpURLConnection httpConn = (HttpURLConnection)_urlConnection;
InputStream _is;
if (httpConn.getResponseCode() < HttpURLConnection.HTTP_BAD_REQUEST) {
    _is = httpConn.getInputStream();
} else {
     /* error from server */
    _is = httpConn.getErrorStream();
}

6
你是否希望在响应码大于等于400时获取错误流,而不是反过来获取? - Stephen Swensen
3
如果出现错误,getInputStream()将抛出一个IO异常。您应该捕获该异常并使用getErrorStream()从错误流中读取。这似乎比检查httpresponse代码要好。 - Sudarshan Bhat
4
问题在于,如果你查看HttpUrlConnection.getErrorStream()的代码,你会发现它总是返回null。(Java 6):-( - Gangnus
6
其他像“201 CREATED”这样的成功代码会失败吗? - Rich
4
该错误报告建议检查 httpConn.getResponseCode() >= 400(并且他们的解决方法有一个错误,翻转了输入流的使用)。 - Dag
显示剩余5条评论

17

这和我之前遇到的问题一样:

HttpUrlConnection

如果您尝试从连接读取getInputStream(),会返回FileNotFoundException。当状态码大于400时,您应该使用getErrorStream()

请注意,不仅200是成功的状态码,甚至201、204等也经常被用作成功的状态。

以下是我如何处理它的示例:

... connection code code code ...

// Get the response code 
int statusCode = connection.getResponseCode();

InputStream is = null;

if (statusCode >= 200 && statusCode < 400) {
   // Create an InputStream in order to extract the response object
   is = connection.getInputStream();
}
else {
   is = connection.getErrorStream();
}

... callback/response to your handler....

通过这种方式,您将能够在成功和错误情况下获得所需的响应。

希望这可以帮到您!


14

在 .Net 中,你可以通过 WebException 的 Response 属性访问异常上的流。所以我想这也是 Java 的一种好方法...

private InputStream dispatch(HttpURLConnection http) throws Exception {
    try {
        return http.getInputStream();
    } catch(Exception ex) {
        return http.getErrorStream();
    }
}

或者我使用的一个实现方式。(可能需要更改编码或其他内容,适用于当前环境。)

private String dispatch(HttpURLConnection http) throws Exception {
    try {
        return readStream(http.getInputStream());
    } catch(Exception ex) {
        readAndThrowError(http);
        return null; // <- never gets here, previous statement throws an error
    }
}

private void readAndThrowError(HttpURLConnection http) throws Exception {
    if (http.getContentLengthLong() > 0 && http.getContentType().contains("application/json")) {
        String json = this.readStream(http.getErrorStream());
        Object oson = this.mapper.readValue(json, Object.class);
        json = this.mapper.writer().withDefaultPrettyPrinter().writeValueAsString(oson);
        throw new IllegalStateException(http.getResponseCode() + " " + http.getResponseMessage() + "\n" + json);
    } else {
        throw new IllegalStateException(http.getResponseCode() + " " + http.getResponseMessage());
    }
}

private String readStream(InputStream stream) throws Exception {
    StringBuilder builder = new StringBuilder();
    try (BufferedReader in = new BufferedReader(new InputStreamReader(stream))) {
        String line;
        while ((line = in.readLine()) != null) {
            builder.append(line); // + "\r\n"(no need, json has no line breaks!)
        }
        in.close();
    }
    System.out.println("JSON: " + builder.toString());
    return builder.toString();
}

这应该是被接受的答案...我想知道为什么人们仍然验证魔数而不是处理异常... - SparK
我想知道为什么这不是被接受的答案。对我帮助很大。谢谢! - Nikhil Jain

2

我知道这不是直接回答问题,但是你可以考虑使用Commons HttpClient,而不是Sun提供的HTTP连接库。在我看来,它具有更简单易用的API。


4
我不太同意。只要你做一些非常简单的东西,Sun 的 API 要容易得多,这些简单的东西就是没有太多错误处理的 GET 请求,这对于许多情况已经足够了。当然,HttpClient 在功能上远远优于它。 - Michael Piefel
截至2014年,最好的选择可能是OkHttp(实际上在打开URL时返回HttpURLConnection实例)。特别是在Android上,它可以帮助您避免使用普通的HttpURLConnection和Apache HttpClient时遇到的一些麻烦问题。 - Jonik

2

首先检查响应代码,然后使用 HttpURLConnection.getErrorStream() 方法。


1
InputStream is = null;
if (httpConn.getResponseCode() !=200) {
    is = httpConn.getErrorStream();
} else {
     /* error from server */
    is = httpConn.getInputStream();
}

5
其他成功状态码,比如“201 CREATED”,在这里会失败吗? - Rich
是的@Rich,这就是为什么最好这样做:if (httpConn.getResponseCode() < HttpURLConnection.HTTP_BAD_REQUEST) { - AO_

1
我的运行代码。
  HttpURLConnection httpConn = (HttpURLConnection) urlConn;    
 if (httpConn.getResponseCode() < HttpURLConnection.HTTP_BAD_REQUEST) {
                        in = new InputStreamReader(urlConn.getInputStream());
                        BufferedReader bufferedReader = new BufferedReader(in);
                        if (bufferedReader != null) {
                            int cp;
                            while ((cp = bufferedReader.read()) != -1) {
                                sb.append((char) cp);
                            }
                            bufferedReader.close();
                        }
                            in.close();

                    } else {
                        /* error from server */
                        in = new InputStreamReader(httpConn.getErrorStream());
                    BufferedReader bufferedReader = new BufferedReader(in);
                    if (bufferedReader != null) {
                        int cp;
                        while ((cp = bufferedReader.read()) != -1) {
                            sb.append((char) cp);
                        }
                        bufferedReader.close();
                    }    
                    in.close();
                    }
                    System.out.println("sb="+sb);

那真的很糟糕。正确的版本应该是将设置放在常规或错误流中,然后仅有一个if()序列读取。这段代码非常冗余。 - zakmck

0

如何在Java中读取404响应体:

使用Apache库 - https://hc.apache.org/httpcomponents-client-4.5.x/httpclient/apidocs/

或者 Java 11 - https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpClient.html

下面给出的代码片段使用了Apache:

import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.util.EntityUtils;

CloseableHttpClient client = HttpClients.createDefault();
CloseableHttpResponse resp = client.execute(new HttpGet(domainName + "/blablablabla.html"));
String response = EntityUtils.toString(resp.getEntity());

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