HttpUrlConnection 中的 Errorstream

26

我想向自己编写的HTTP Servlet发送POST请求。使用URL.openConnection()方法可以正常工作(HTTP响应代码为200)。但是如果我收到期望的错误响应代码(例如400),则认为我必须使用HttpUrlConnection.getErrorStream()。但是,即使在错误情况下我从servlet发送数据,ErrorStream对象也为空(我想评估此数据以生成错误消息)。 这是我的代码:

HttpURLConnection con = null;
        try {
            //Generating request String
            String request = "request="+URLEncoder.encode(xmlGenerator.getStringFromDocument(xmlGenerator.generateConnectRequest(1234)),"UTF-8");
            //Receiving HttpUrlConnection (DoOutput = true; RequestMethod is set to "POST")
            con = openConnection();
            if (con != null){
                PrintWriter pw = new PrintWriter(con.getOutputStream());
                pw.println(request);
                pw.flush();
                pw.close();
                InputStream errorstream = con.getErrorStream();

                BufferedReader br = null;
                if (errorstream == null){
                    InputStream inputstream = con.getInputStream();
                    br = new BufferedReader(new InputStreamReader(inputstream));
                }else{
                    br = new BufferedReader(new InputStreamReader(errorstream));
                }
                String response = "";
                String nachricht;
                while ((nachricht = br.readLine()) != null){
                    response += nachricht;
                }
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
所以我的问题是,为什么调用getErrorStream()方法返回null,尽管状态码是400(我可以在调用con.getInputStream()时抛出的IOException中看到)。
谢谢。

你确定服务器返回的不是状态码以外的内容吗? - Grooveek
5个回答

22

从Java文档中关于getErrorStream()的说明:

如果连接失败但服务器仍然发送了有用的数据,则返回错误流。典型示例是当HTTP服务器响应404时,将在连接中抛出FileNotFoundException,但服务器发送带有建议的HTML帮助页面。如果连接未连接,或者服务器在连接时没有错误,或者服务器有错误但没有发送错误数据,则此方法将返回null。这是默认值。

因此,如果您没有到达服务器(例如,URL错误)或服务器在响应中没有发送任何内容,则getErrorStream()将返回null。


我们能否使用系统错误码来创建这些流,这是一种好的方法吗?我正在Android中处理基于Java的JSON响应,并且即使在错误情况下,在onSucess()回调中也总是会得到系统错误,在错误情况下,它们应该出现在onError()回调中,请帮助我理解这个问题。 - Saad Bilal

15
InputStream inputStream = null;     
try {
    inputStream = connection.getInputStream();
} catch(IOException exception) {
   inputStream = connection.getErrorStream();
}

当你将响应头状态码设置为200以外的任何值时,连接对象将被重置。在获取输入流时,它会生成SocketTimeoutException,但是当它进入catch块时,它仍会提供输入流,这正是你所期望的。


13

深入研究JDK代码后,我终于找到了原因。当接收到401或407时,HttpURLConnection#getErrorStream()返回null,不是因为抽象类中的空实现,而是因为HttpURLConnection在流模式(即POST)下检测到401/407时会立即关闭/清除连接。详见HttpURLConnection的具体实现源代码:http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/sun/net/www/protocol/http/HttpURLConnection.java#1079

也就是说,当调用getInputStream()时捕获IOException时,与服务器的连接已经关闭,并且底层套接字已被清除,因此调用getErrorStream()时总是返回null。

许多人提出的另一种选择是在调用getInputStream或getErrorStream之前检查状态码。但这对于401和407也不起作用,因为只有在调用getInputStream时内部的errorStream才会被设置,即当状态码!=200时它基本上是inputStream的副本。但是,再次调用getInputStream时,连接将被关闭。


哇!谢谢你。你知道任何解决方法吗?或者有版本没有这个问题吗? - Sam
3
请注意,这个答案涉及Java 6,并且在Java 8中HttpURLConnection的实现看起来有很大不同。 - jaco0646

6
将语句conn.setAllowUserInteraction(true);放在请求执行之前,连接将不会被关闭,即使接收到401状态。
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setAllowUserInteraction(true); //                <<<--- ### HERE
//do something
conn.connect();
boolean isError = (conn.getResponseCode() >= 400);
InputSteam is = isError ? con.getErrorStream() : con.getInputStream();

4

根据Android文档的建议:

String responseString;
try {
    responseString = readInputStream(con.getInputStream());
} catch (final IOException e) {
    // This means that an error occurred, read the error from the ErrorStream
    try {
        responseString = readInputStream(con.getErrorStream());
    } catch (IOException e1) {
        throw new IllegalStateException("Unable to read error body.", e);
    }
}

private String readInputStream(final InputStream inputStream) throws IOException {
    final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
    final StringBuilder responseString = new StringBuilder();
    String line;
    while ((line = bufferedReader.readLine()) != null) {
        responseString.append(line);
    }
    bufferedReader.close();
    return responseString.toString();
}

如果inputStream为空,请尝试捕获NPE并读取errorStream。 (仅在Android 4.1.2上发生-不确定原因) - hb0

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