Java 嵌入式 HttpServer:套接字泄漏

Java embedded HttpServer: socket leak

我有下一个代理处理程序(使用嵌入式 HttpServer 和 Jersey 客户端):

@Override
public void handle(HttpExchange httpExchange) throws IOException {
    ....
    ....
            WebTarget webTarget = httpClientHolder.getClient().target(url);
            try (Response response = webTarget.request().get()) {
                if (response.getStatusInfo().getStatusCode() != HttpStatus.SC_OK) {
                    logger.warn("Failed to download resource, url = " + url + " code = " +
                            response.getStatusInfo().getStatusCode());
                    throw new HttpException("Not found", 404);
                }
                httpExchange.sendResponseHeaders(200, response.getLength());
                final InputStream is = response.readEntity(InputStream.class);
                try (final OutputStream os = httpExchange.getResponseBody()) {
                    IOUtils.copy(is, os, 128);
                } catch (IOException e) {
                    logger.warn("Error proxy data", e);
                }
            }
    ....
}

有时会发生以下情况

Error proxy data
java.io.IOException: Broken pipe
        at sun.nio.ch.FileDispatcherImpl.write0(Native Method) ~[?:1.8.0_191]
        at sun.nio.ch.SocketDispatcher.write(SocketDispatcher.java:47) ~[?:1.8.0_191]
        at sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:93) ~[?:1.8.0_191]
        at sun.nio.ch.IOUtil.write(IOUtil.java:65) ~[?:1.8.0_191]
        at sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:471) ~[?:1.8.0_191]
        at sun.net.httpserver.Request$WriteStream.write(Request.java:391) ~[?:1.8.0_191]
        at sun.net.httpserver.FixedLengthOutputStream.write(FixedLengthOutputStream.java:78) ~[?:1.8.0_191]
        at sun.net.httpserver.PlaceholderOutputStream.write(ExchangeImpl.java:444) ~[?:1.8.0_191]
        at org.apache.commons.io.IOUtils.copyLarge(IOUtils.java:2315) ~[cache-1.0-SNAPSHOT-jar-with-dependencies.jar:?]
        at org.apache.commons.io.IOUtils.copy(IOUtils.java:2270) ~[cache-1.0-SNAPSHOT-jar-with-dependencies.jar:?]
        at server.TimelineVttHandler.handle(TimelineVttHandler.java:129) [cache-1.0-SNAPSHOT-jar-with-dependencies.jar:?]
        at com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:79) [?:1.8.0_191]
        at sun.net.httpserver.AuthFilter.doFilter(AuthFilter.java:83) [?:1.8.0_191]
        at com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:82) [?:1.8.0_191]
        at sun.net.httpserver.ServerImpl$Exchange$LinkHandler.handle(ServerImpl.java:675) [?:1.8.0_191]
        at com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:79) [?:1.8.0_191]
        at sun.net.httpserver.ServerImpl$Exchange.run(ServerImpl.java:647) [?:1.8.0_191]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_191]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_191]
        at java.lang.Thread.run(Thread.java:748) [?:1.8.0_191]
        Suppressed: java.io.IOException: insufficient bytes written to stream
                at sun.net.httpserver.FixedLengthOutputStream.close(FixedLengthOutputStream.java:89) ~[?:1.8.0_191]
                at sun.net.httpserver.PlaceholderOutputStream.close(ExchangeImpl.java:454) ~[?:1.8.0_191]
                at server.TimelineVttHandler.handle(TimelineVttHandler.java:130) [cache-1.0-SNAPSHOT-jar-with-dependencies.jar:?]
                at com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:79) [?:1.8.0_191]
                at sun.net.httpserver.AuthFilter.doFilter(AuthFilter.java:83) [?:1.8.0_191]
                at com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:82) [?:1.8.0_191]
                at sun.net.httpserver.ServerImpl$Exchange$LinkHandler.handle(ServerImpl.java:675) [?:1.8.0_191]
                at com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:79) [?:1.8.0_191]
                at sun.net.httpserver.ServerImpl$Exchange.run(ServerImpl.java:647) [?:1.8.0_191]
                at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_191]
                at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_191]
                at java.lang.Thread.run(Thread.java:748) [?:1.8.0_191]

这对客户端来说不是什么大问题,但资源仍然存在,我们可以通过 lsof 查看:

lsof -n -p 14121  | grep TCPv6
java    13161 cache  143u     sock                0,9      0t0 3617991518 protocol: TCPv6
java    13161 cache  180u     sock                0,9      0t0 3618027565 protocol: TCPv6
java    13161 cache  184u     sock                0,9      0t0 3618017649 protocol: TCPv6

此套接字在 运行 应用程序时永远不会关闭。 如何正确执行它?

我通过从 handle 方法中抛出 IOException 解决了这个问题。

                try (final OutputStream os = httpExchange.getResponseBody()) {
                    IOUtils.copy(is, os, 128);
                } catch (IOException e) {
                    logger.warn("Error proxy data", e);
                    throw e;
                }

这个想法是从方法契约中得到的