HttpURLConnection的安全使用

62

当使用HttpURLConnection时,如果我们没有“获取并使用”InputStream,是否需要关闭它?

也就是说,这样做安全吗?

HttpURLConnection conn = (HttpURLConnection) uri.getURI().toURL().openConnection();
conn.connect();
// check for content type I don't care about
if (conn.getContentType.equals("image/gif") return; 
// get stream and read from it
InputStream is = conn.getInputStream();
try {
    // read from is
} finally {
    is.close();
}

其次,如果在完全读取所有内容之前关闭InputStream,是否安全?

会不会存在将底层套接字留在ESTABLISHED甚至CLOSE_WAIT状态的风险?

7个回答

47
根据http://docs.oracle.com/javase/6/docs/technotes/guides/net/http-keepalive.html和OpenJDK源代码。(当keepAlive == true时)如果客户端调用了HttpURLConnection.getInputSteam().close(),随后对HttpURLConnection.disconnect()的调用不会关闭Socket,即Socket将被重用(缓存)。如果客户端没有调用close(),则调用disconnect()会关闭InputStream并关闭Socket

因此,为了重用Socket,只需调用InputStream.close()。不要调用HttpURLConnection.disconnect()


35
在所有内容都没有被读取的情况下关闭一个InputStream是否安全?
在关闭InputStream之前,需要先读取其中的所有数据,以便底层TCP连接可以被缓存。我已经阅读过最新版Java不需要这样做,但是为了重新使用连接,始终要求读取整个响应。
请参考此帖子:Java6中的keep-alive

非常有趣。这个问题实际上是我遇到的一个背景,我看到了很多CLOSE_WAIT套接字连接到同一个IP,但由于缓存(我没有显式调用URLConnection.disconnect()),我希望只有一个套接字被重复使用。 - Joel
4
通过调用HttpUrlConnection.disconnect()方法可以关闭底层的tcp套接字。通过关闭输入流,底层的tcp套接字可供以后重用。唯一的注意事项是必须从输入流中读取整个响应(或整个错误响应)才能将tcp连接缓存。无论您实际上是否需要来自流的全部数据,这都是建议的。请查看我回答中的帖子。 - Cratylus
2
好的文章,谢谢。还有几件事情我不是很清楚:1)在丢弃缓存的连接之前,它会被保存多长时间?我没有看到“60秒无活动后丢弃”这样的设置。2)对我来说,调用close但未读取所有内容之前连接的状态并不清楚——它说它将不可重新使用/缓存,这很好——但基础套接字是否会被关闭呢? - Joel
1
@Joel:你的问题与HTTP协议有关。连接必须保持活动状态,时间由服务器在HTTP响应中指定(服务器在HTTP头中发送此连接可使用的最大请求数或保持连接打开的最长时间段)。HTTP客户端必须遵守此规定,这也是HttpURLConnection的行为。如果服务器在响应中未发送此类信息,则连接将很快关闭(我认为在几秒钟的不活动后),以免浪费资源。 - Cratylus
另外,根据“java6中的keep-alive”链接所述,您需要捕获IOException并关闭getErrorStream()(如果它返回非空)。 - Brett Kail
显示剩余2条评论

22
这里有关于保持活动缓存的一些信息。所有这些信息都适用于Java 6,但对于许多之前和之后的版本也可能是准确的。
从我所了解的情况来看,代码归结为以下几点:
1. 如果远程服务器发送了一个带有可解析为正整数的“Keep-Alive”头部的“timeout”值,那么该秒数将用作超时时间。 2. 如果远程服务器发送了一个带有“Keep-Alive”头部但没有可解析为正整数的“timeout”值并且“usingProxy”为真,则超时时间为60秒。 3. 在所有其他情况下,超时时间为5秒。
这个逻辑分散在两个地方:sun.net.www.http.HttpClient的第725行附近(在"parseHTTPHeader"方法中),以及sun.net.www.http.KeepAliveCache的第120行附近(在"put"方法中)。
所以,有两种方法来控制超时时间:
1. 控制远程服务器并配置它发送带有适当超时字段的Keep-Alive头部。 2. 修改JDK源代码并构建自己的版本。
人们可能会认为可以在不重新编译内部JDK类的情况下更改明显任意的五秒默认值,但事实并非如此。2005年提出了一个bug,要求提供这种能力,但Sun拒绝了。

3
很棒的研究,涉及的主题文献较少。感谢您的分享。 - Mike Clark

7
如果您确实希望确保连接关闭,应调用conn.disconnect()
您观察到的开放式连接是由于HTTP 1.1连接保持活动功能(也称为HTTP持久连接)。如果服务器支持HTTP 1.1并且在响应标头中未发送Connection:close,则Java在关闭输入流时不会立即关闭底层TCP连接。相反,它保持打开状态并尝试重用它以进行对同一服务器的下一个HTTP请求。
如果您根本不想采用这种行为,则可以将系统属性http.keepAlive设置为false:
System.setProperty("http.keepAlive","false");

1
谢谢。假定连接未被使用,您知道它在关闭之前将缓存多长时间,并且是否有任何方法来控制此超时期? - Joel

2
在使用HttpURLConnection时,即使不“获取”并使用输入流,输入流也需要关闭。是的,它总是需要被关闭的。但如果不关闭,存在空指针异常的风险。更安全的做法是:
InputStream is = null;
try {
    is = conn.getInputStream()
    // read from is
} finally {
    if (is != null) {
        is.close();
    }
}

1
第二个问题涉及底层套接字状态,我故意发布了一个不完整的片段,关于完整的运行时代码安全性。我真的想知道,在读取所有内容之前关闭套接字是否会导致套接字保留在CLOSE_WAIT或ESTABLISHED状态下存在危险。 - Joel
1
或者 IOUtils.closeQuietly(is) - Kirby
目前,IOUtils.closeQuietly @Deprecated - zeugor

2
你还需要关闭错误流(除了200之外的任何东西)如果HTTP请求失败:
try {
  ...
}
catch (IOException e) {
  connection.getErrorStream().close();
}

如果您不这样做,所有未返回200(例如超时)的请求都将泄漏一个套接字。

1
不太确定 - 最后一个源代码(JDK 8u74)读取 public InputStream getErrorStream() { return null; } - FelixJongleur42
finally块怎么样?你可以使用finally来关闭流,而不是使用catch - HAXM
ErrorStream只是一个缓冲区,使用InputStream即可。关闭InputStream就足够了。 - bebbo

1
自Java 7以来,推荐的方式是:


try (InputStream is = conn.getInputStream()) {
    // read from is
    // ...
}

对于实现 Closable 接口的所有其他类,close() 方法将在 try {...} 块结束时调用。
关闭输入流也意味着您已经完成了读取。否则,连接会一直保持到终结器关闭流为止。
如果要发送数据,则同样适用于输出流。
不需要获取并关闭 ErrorStream。即使它实现了 InputStream 接口:它与缓冲区一起使用 InputStream。关闭 InputStream 即可。

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