安全使用 HttpURLConnection
Posted
技术标签:
【中文标题】安全使用 HttpURLConnection【英文标题】:Safe use of HttpURLConnection 【发布时间】:2011-06-13 15:28:48 【问题描述】:在使用 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 状态的风险?
【问题讨论】:
【参考方案1】:根据http://docs.oracle.com/javase/6/docs/technotes/guides/net/http-keepalive.html 和 OpenJDK 源代码。
(当keepAlive == true
)
如果客户端调用HttpURLConnection.getInputSteam().<strong>close</strong>()
,则稍后调用HttpURLConnection.<strong>disconnect</strong>()
将不会关闭Socket
。即 Socket
被重用(缓存)
如果客户端不调用close()
,调用disconnect()
将关闭InputStream
并关闭Socket
。
所以为了重用Socket
,只需调用InputStream.<strong>close</strong>()
。请勿拨打HttpURLConnection.<strong>disconnect</strong>()
。
【讨论】:
【参考方案2】:关闭 InputStream 是否安全 在它的所有内容被 阅读
您需要在关闭输入流之前读取输入流中的所有数据,以便缓存底层 TCP 连接。我读过最新的 Java 不应该要求它,但它总是被要求读取整个响应以重新使用连接。
查看此帖子:keep-alive in java6
【讨论】:
非常有趣。这个问题实际上是我遇到的一个问题的背景,我看到很多 CLOSE_WAIT 套接字到同一个 IP,但是由于缓存(我没有明确调用 URLConnection.disconnect())我希望只有一个,它应该重复使用。 @Joel:通过调用HttpUrlConnection.disconnect()
,底层 tcp 套接字被关闭。通过关闭输入流,底层的 tcp 套接字被汇集起来供以后重用。唯一需要注意的是,应该从输入流中读取整个响应(或整个错误响应),以便缓存 tcp 连接。无论您实际上需要流中的全部数据,都始终建议您这样做。检查我的答案中的帖子
如果您不读取任何数据怎么办,根据我的第一个示例 - 我猜您仍然需要关闭 IS,但如果没有读取数据,它仍然不会被缓存,但它是仍然关闭。
好文章,谢谢。有几件事我还不清楚。 1) 缓存连接在被丢弃之前会保留多长时间?我看不到“60 秒不活动后丢弃”类型设置。 2) 我不清楚在调用 close 之后连接会处于什么状态,但在读取所有内容之前是这样的 - 它说它不能用于重用/缓存,这很好 - 但底层会套接字实际上被关闭了吗?
@Joel:您的问题与 HTTP 协议有关。连接必须在服务器在 HTTP 响应中指定的时间范围内保持活动状态(服务器在 HTTP 标头中发送此连接可以的最大请求数被使用或保持连接打开的最大时间段)。http客户端必须遵守这一点,这也是HttpURLConnection的行为。如果服务器在响应中没有发送此类信息,则连接很快就会关闭(我认为arround几秒钟不活动后)不要浪费资源。【参考方案3】:
以下是有关保持活动缓存的一些信息。所有这些信息都与 Java 6 相关,但对于许多以前和以后的版本来说可能也是准确的。
据我所知,代码归结为:
-
如果远程服务器发送带有“超时”值的“Keep-Alive”标头,该值可以解析为正整数,则该秒数用于超时。
如果远程服务器发送了“Keep-Alive”标头,但没有可以解析为正整数的“超时”值并且“usingProxy”为真,则超时为 60 秒。
在所有其他情况下,超时时间为 5 秒。
这个逻辑分为两个地方:sun.net.www.http.HttpClient 的第 725 行附近(在“parseHTTPHeader”方法中)和sun.net.www.http.KeepAliveCache 的第 120 行附近(在“put”方法中)。
所以,有两种方法可以控制超时时间:
-
控制远程服务器并将其配置为发送带有适当超时字段的 Keep-Alive 标头
修改 JDK 源代码并构建自己的。
人们可能会认为无需重新编译内部 JDK 类就可以更改明显任意的 5 秒默认值,但事实并非如此。 2005 年提交了一封bug 请求此功能,但 Sun 拒绝提供。
【讨论】:
对一个记录不佳的主题进行了很好的研究。谢谢分享。【参考方案4】:如果你真的想确保连接是关闭的,你应该打电话给conn.disconnect()
。
您观察到的打开连接是由于 HTTP 1.1 连接保持活动功能(也称为HTTP Persistent Connections)。
如果服务器支持 HTTP 1.1 并且未在响应标头中发送 Connection: close
,则当您关闭输入流时,Java 不会立即关闭底层 TCP 连接。相反,它保持打开状态并尝试将其重用于对同一服务器的下一个 HTTP 请求。
如果您根本不想要这种行为,您可以将系统属性 http.keepAlive
设置为 false:
System.setProperty("http.keepAlive","false");
【讨论】:
谢谢。假设连接没有被使用,你知道它在关闭之前被缓存了多长时间,有没有办法控制这个超时时间?【参考方案5】:如果 HTTP 请求失败(除了 200),您还必须关闭错误流:
try
...
catch (IOException e)
connection.getErrorStream().close();
如果你不这样做,所有不返回 200(例如超时)的请求都会泄漏一个套接字。
【讨论】:
不太确定 - 最后一个源代码(JDK 8u74)读取public InputStream getErrorStream() return null;
finally
块怎么样。您可以使用 finally 来关闭流而不是 catch
【参考方案6】:
在使用 HttpURLConnection 时,如果我们不“获取”并使用它,是否需要关闭 InputStream?
是的,它总是需要关闭。
即这安全吗?
不是 100%,您有获得 NPE 的风险。更安全的是:
InputStream is = null;
try
is = conn.getInputStream()
// read from is
finally
if (is != null)
is.close();
【讨论】:
第二个问题是关于底层套接字状态的,关于完整的运行时代码安全,我故意发布了一个不完整的 sn-p。我真的很想知道在读取所有内容之前关闭套接字是否存在 CLOSE_WAIT 或 ESTABLISED 中遗留套接字的危险, 或IOUtils.closeQuietly(is)
目前,IOUtils.closeQuietly @Deprecated【参考方案7】:
从 Java 7 开始推荐的方式是
try (InputStream is = conn.getInputStream())
// read from is
// ...
对于所有其他实现Closable
的类。 close()
在 try ...
块的末尾被调用。
关闭输入流也意味着您已完成阅读。否则连接会一直挂起,直到终结器关闭流。
如果您要发送数据,同样适用于输出流。
【讨论】:
以上是关于安全使用 HttpURLConnection的主要内容,如果未能解决你的问题,请参考以下文章
Kotlin空安全 ② ( 手动空安全管理 | 空安全调用操作符 ? | let 函数结合空安全调用操作符使用 )