如何保持多个 Java HttpConnections 对同一目的地开放

发布于 2024-08-15 23:07:12 字数 1012 浏览 10 评论 0原文

我们经常使用 HttpURLConnection API 向同一提供者调用 REST API(一种聚合用例)。我们希望保持 5 个连接池始终对提供商主机开放(始终相同的 IP)。

正确的解决方案是什么?这是我们尝试过的:


System.setProperty("http.maxConnections", 5);  // set globally only once
...
// everytime we need a connection, we use the following
HttpURLConnection conn = (HttpURLConnection) (new URL(url)).openConnection();
conn.setRequestMethod("GET");
conn.setDoInput(true);
conn.setDoOutput(false);
conn.setUseCaches(true);
...
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
...

此时我们读取输入流,直到 BufferedReader 不再返回字节。如果我们想重用与提供者的底层连接,那么在那之后我们该怎么办?我们的印象是,如果输入流被完全读取,连接就会被添加回池中。

它已经以这种方式工作了几个星期,但今天它停止工作并产生此异常: java.net.SocketException: Too much open files

我们发现许多套接字处于 CLOSE_WAIT 状态,如下所示(通过运行 >lsof): java 1814 root 97u IPv6 844702 TCP colinux:58517->123.123.254.205:www (CLOSE_WAIT)

conn.getInputStream().close() 或 conn.disconnect() 都不会完全关闭连接并将其从池中删除?

We are using HttpURLConnection API to invoke a REST API to the same provider often (kind of an aggregation usecase). We want to keep a pool of 5 connections always open to the provider host (always the same IP).

What is the proper solution? Here is what we tried:


System.setProperty("http.maxConnections", 5);  // set globally only once
...
// everytime we need a connection, we use the following
HttpURLConnection conn = (HttpURLConnection) (new URL(url)).openConnection();
conn.setRequestMethod("GET");
conn.setDoInput(true);
conn.setDoOutput(false);
conn.setUseCaches(true);
...
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
...

At this point we read the input stream until the BufferedReader returns no more bytes. What do we do after that point if we want to reuse the underlying connection to the provider? We were under the impression that if the input stream is completely read, the connection is then added back to the pool.

It's been working for several weeks this way, but today it stopped working producing this exception: java.net.SocketException: Too many open files

We found numerous sockets in the CLOSE_WAIT state like this (by running lsof):
java 1814 root 97u IPv6 844702 TCP colinux:58517->123.123.254.205:www (CLOSE_WAIT)

Won't either conn.getInputStream().close() or conn.disconnect() completely close the connection and remove it from the pool?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

梦途 2024-08-22 23:07:12

我们在 Java 5 上也遇到了这个问题,我们的解决方案是切换到带有池连接管理器的 Apache HttpClient。

Sun 的 HTTP URL 处理程序的 keepalive 实现有很多错误。没有维护线程来关闭空闲连接。

keepalive 的另一个更大的问题是你需要删除响应。否则,该连接也将被孤立。大多数人没有正确处理错误流。请参阅我对此问题的回答,了解有关如何正确读取错误响应的示例,

HttpURLConnection.getResponseCode() 在第二次调用时返回 -1

We had this problem also on Java 5 and our solution is to switch to Apache HttpClient with pooled connection manager.

The keepalive implementation of Sun's URL handler for HTTP is very buggy. There is no maintenance thread to close idle connections.

Another bigger problem with keepalive is that you need to delete responses. Otherwise, the connection will be orphaned also. Most people don't handle error stream correctly. Please see my answer to this question for an example on how to read error responses correctly,

HttpURLConnection.getResponseCode() returns -1 on second invocation

夜无邪 2024-08-22 23:07:12

来自此处

当前实现不缓冲响应正文。这意味着应用程序必须完成读取响应正文或调用 close() 放弃响应正文的其余部分,以便重用该连接。此外,当前的实现在清理连接时不会尝试块读取,这意味着如果整个响应正文不可用,则不会重用该连接。

我读到这篇文章就好像您的解决方案应该有效,但您也可以自由调用 close 并且连接仍将被重用。

From here:

The current implementation doesn't buffer the response body. Which means that the application has to finish reading the response body or call close() to abandon the rest of the response body, in order for that connection to be reused. Furthermore, current implementation will not try block-reading when cleaning up the connection, meaning if the whole response body is not available, the connection will not be reused.

I read this as if your solution should work, but that you are also free to call close and the connection will still be reused.

秋意浓 2024-08-22 23:07:12

disown 引用的 参考 是真正有帮助的是什么。

我们知道 Apache HttpClient 更好,但这需要另一个 jar,并且我们可能会在小程序中使用此代码。

调用 HttpURLConnection.connect() 是不必要的。我不确定它是否会阻止连接重用,但我们将其删除了。关闭流是安全的,但在连接上调用 disconnect() 将阻止重用。此外,设置 sun.net.http.errorstream.enableBuffering=true 也有帮助。

这是我们最终使用的:


System.setProperty("http.maxConnections", String.valueOf(CONST.CONNECTION_LIMIT));
System.setProperty("sun.net.http.errorstream.enableBuffering", "true");

...

int responseCode = -1;
HttpURLConnection conn = null;
BufferedReader reader = null;
try {
 conn = (HttpURLConnection) (new URL(url)).openConnection();
 conn.setRequestProperty("Accept-Encoding", "gzip");

 // this blocks until the connection responds
 InputStream in = new GZIPInputStream(conn.getInputStream());

 reader = new BufferedReader(new InputStreamReader(in));
 StringBuffer sb = new StringBuffer();
 char[] buff = new char[CONST.HTTP_BUFFER_SIZE];
 int cnt;

 while((cnt = reader.read(buff)) > 0) sb.append(buff, 0, cnt);

 reader.close();

 responseCode = conn.getResponseCode();
 if(responseCode != HttpURLConnection.HTTP_OK) throw new IOException("abnormal HTTP response code:"+responseCode);

 return sb.toString();

} catch(IOException e) {
    // consume error stream, otherwise, connection won't be reused
    if(conn != null) {
     try {
         InputStream in = ((HttpURLConnection)conn).getErrorStream();
         in.close();
         if(reader != null) reader.close();
     } catch(IOException ex) {
         log.fine(ex);
     }
    }

    // log exception    
    String rc = (responseCode == -1) ? "unknown" : ""+responseCode;
    log.severe("Error for HttpUtil.httpGet("+url+")\nServer returned an HTTP response code of '"+rc+"'");
    log.severe(e);
}

The reference cited by disown was what really helped.

We know Apache HttpClient is better, but that would require another jar and we might use this code in an applet.

Calling HttpURLConnection.connect() was unnecessary. I'm not sure if it prevents connection reuse, but we took it out. It is safe to close the stream, but calling disconnect() on the connection will prevent reuse. Also, setting sun.net.http.errorstream.enableBuffering=true helps.

Here is what we ended up using:


System.setProperty("http.maxConnections", String.valueOf(CONST.CONNECTION_LIMIT));
System.setProperty("sun.net.http.errorstream.enableBuffering", "true");

...

int responseCode = -1;
HttpURLConnection conn = null;
BufferedReader reader = null;
try {
 conn = (HttpURLConnection) (new URL(url)).openConnection();
 conn.setRequestProperty("Accept-Encoding", "gzip");

 // this blocks until the connection responds
 InputStream in = new GZIPInputStream(conn.getInputStream());

 reader = new BufferedReader(new InputStreamReader(in));
 StringBuffer sb = new StringBuffer();
 char[] buff = new char[CONST.HTTP_BUFFER_SIZE];
 int cnt;

 while((cnt = reader.read(buff)) > 0) sb.append(buff, 0, cnt);

 reader.close();

 responseCode = conn.getResponseCode();
 if(responseCode != HttpURLConnection.HTTP_OK) throw new IOException("abnormal HTTP response code:"+responseCode);

 return sb.toString();

} catch(IOException e) {
    // consume error stream, otherwise, connection won't be reused
    if(conn != null) {
     try {
         InputStream in = ((HttpURLConnection)conn).getErrorStream();
         in.close();
         if(reader != null) reader.close();
     } catch(IOException ex) {
         log.fine(ex);
     }
    }

    // log exception    
    String rc = (responseCode == -1) ? "unknown" : ""+responseCode;
    log.severe("Error for HttpUtil.httpGet("+url+")\nServer returned an HTTP response code of '"+rc+"'");
    log.severe(e);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文