diff --git a/src/main/java/com/github/dockerjava/api/command/AttachContainerCmd.java b/src/main/java/com/github/dockerjava/api/command/AttachContainerCmd.java index aeee749e3..abdec8f58 100644 --- a/src/main/java/com/github/dockerjava/api/command/AttachContainerCmd.java +++ b/src/main/java/com/github/dockerjava/api/command/AttachContainerCmd.java @@ -55,6 +55,9 @@ public interface AttachContainerCmd extends DockerCmd{ public AttachContainerCmd withLogs(); /** + * Its the responsibility of the caller to consume and/or close the {@link InputStream} to prevent + * connection leaks. + * * @throws NotFoundException No such container */ @Override diff --git a/src/main/java/com/github/dockerjava/api/command/CopyFileFromContainerCmd.java b/src/main/java/com/github/dockerjava/api/command/CopyFileFromContainerCmd.java index 8d2bfbb41..5e85d9984 100644 --- a/src/main/java/com/github/dockerjava/api/command/CopyFileFromContainerCmd.java +++ b/src/main/java/com/github/dockerjava/api/command/CopyFileFromContainerCmd.java @@ -19,6 +19,9 @@ public interface CopyFileFromContainerCmd extends DockerCmd { public CopyFileFromContainerCmd withHostPath(String hostPath); /** + * Its the responsibility of the caller to consume and/or close the {@link InputStream} to prevent + * connection leaks. + * * @throws NotFoundException No such container */ @Override diff --git a/src/main/java/com/github/dockerjava/api/command/ExecStartCmd.java b/src/main/java/com/github/dockerjava/api/command/ExecStartCmd.java index bcc7a072e..7ccb90cff 100644 --- a/src/main/java/com/github/dockerjava/api/command/ExecStartCmd.java +++ b/src/main/java/com/github/dockerjava/api/command/ExecStartCmd.java @@ -23,6 +23,9 @@ public interface ExecStartCmd extends DockerCmd { public ExecStartCmd withTty(); /** + * Its the responsibility of the caller to consume and/or close the {@link InputStream} to prevent + * connection leaks. + * * @throws com.github.dockerjava.api.NotFoundException * No such exec instance */ diff --git a/src/main/java/com/github/dockerjava/api/command/LogContainerCmd.java b/src/main/java/com/github/dockerjava/api/command/LogContainerCmd.java index 36a41beb5..74512b390 100644 --- a/src/main/java/com/github/dockerjava/api/command/LogContainerCmd.java +++ b/src/main/java/com/github/dockerjava/api/command/LogContainerCmd.java @@ -56,6 +56,9 @@ public interface LogContainerCmd extends DockerCmd{ public LogContainerCmd withTail(int tail); /** + * Its the responsibility of the caller to consume and/or close the {@link InputStream} to prevent + * connection leaks. + * * @throws NotFoundException No such container */ @Override diff --git a/src/main/java/com/github/dockerjava/api/command/PullImageCmd.java b/src/main/java/com/github/dockerjava/api/command/PullImageCmd.java index 27d5b978a..c39617dde 100644 --- a/src/main/java/com/github/dockerjava/api/command/PullImageCmd.java +++ b/src/main/java/com/github/dockerjava/api/command/PullImageCmd.java @@ -29,5 +29,12 @@ public interface PullImageCmd extends DockerCmd{ public static interface Exec extends DockerCmdExec { } + + /** + * Its the responsibility of the caller to consume and/or close the {@link InputStream} to prevent + * connection leaks. + */ + @Override + public InputStream exec(); } \ No newline at end of file diff --git a/src/main/java/com/github/dockerjava/api/command/SaveImageCmd.java b/src/main/java/com/github/dockerjava/api/command/SaveImageCmd.java index 957ae05cd..48c5f3191 100644 --- a/src/main/java/com/github/dockerjava/api/command/SaveImageCmd.java +++ b/src/main/java/com/github/dockerjava/api/command/SaveImageCmd.java @@ -21,6 +21,9 @@ public interface SaveImageCmd extends DockerCmd{ public SaveImageCmd withTag(String tag); /** + * Its the responsibility of the caller to consume and/or close the {@link InputStream} to prevent + * connection leaks. + * * @throws com.github.dockerjava.api.NotFoundException No such image */ public InputStream exec() throws NotFoundException; diff --git a/src/main/java/com/github/dockerjava/jaxrs/AttachContainerCmdExec.java b/src/main/java/com/github/dockerjava/jaxrs/AttachContainerCmdExec.java index 1fafd1f0e..de4bb3f0c 100644 --- a/src/main/java/com/github/dockerjava/jaxrs/AttachContainerCmdExec.java +++ b/src/main/java/com/github/dockerjava/jaxrs/AttachContainerCmdExec.java @@ -10,6 +10,7 @@ import org.slf4j.LoggerFactory; import com.github.dockerjava.api.command.AttachContainerCmd; +import com.github.dockerjava.jaxrs.util.WrappedResponseInputStream; public class AttachContainerCmdExec extends AbstrDockerCmdExec implements @@ -36,9 +37,11 @@ protected InputStream execute(AttachContainerCmd command) { LOGGER.trace("POST: {}", webResource); - return webResource.request() + Response response = webResource.request() .accept(MediaType.APPLICATION_OCTET_STREAM_TYPE) - .post(null, Response.class).readEntity(InputStream.class); + .post(null, Response.class); + + return new WrappedResponseInputStream(response); } } diff --git a/src/main/java/com/github/dockerjava/jaxrs/BuildImageCmdExec.java b/src/main/java/com/github/dockerjava/jaxrs/BuildImageCmdExec.java index 5c2b87fc6..18aceb4ea 100644 --- a/src/main/java/com/github/dockerjava/jaxrs/BuildImageCmdExec.java +++ b/src/main/java/com/github/dockerjava/jaxrs/BuildImageCmdExec.java @@ -21,13 +21,16 @@ import com.github.dockerjava.api.command.BuildImageCmd; import com.github.dockerjava.api.model.AuthConfigurations; import com.github.dockerjava.api.model.EventStreamItem; +import com.github.dockerjava.jaxrs.util.WrappedResponseInputStream; import com.google.common.collect.ImmutableList; -public class BuildImageCmdExec extends AbstrDockerCmdExec implements BuildImageCmd.Exec { - +public class BuildImageCmdExec extends + AbstrDockerCmdExec implements + BuildImageCmd.Exec { + private static final Logger LOGGER = LoggerFactory .getLogger(BuildImageCmdExec.class); - + public BuildImageCmdExec(WebTarget baseResource) { super(baseResource); } @@ -35,72 +38,76 @@ public BuildImageCmdExec(WebTarget baseResource) { @Override protected ResponseImpl execute(BuildImageCmd command) { WebTarget webResource = getBaseResource().path("/build"); - String dockerFilePath = command.getPathToDockerfile(); - - if(command.getTag() != null) { + String dockerFilePath = command.getPathToDockerfile(); + + if (command.getTag() != null) { webResource = webResource.queryParam("t", command.getTag()); } - if (command.hasNoCacheEnabled()) { - webResource = webResource.queryParam("nocache", "true"); - } - if (command.hasRemoveEnabled()) { - webResource = webResource.queryParam("rm", "true"); - } - if (command.isQuiet()) { - webResource = webResource.queryParam("q", "true"); - } - if( dockerFilePath != null && !"Dockerfile".equals(dockerFilePath)) { - webResource = webResource.queryParam("dockerfile", dockerFilePath); - } - - - webResource.property(ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.CHUNKED); - webResource.property(ClientProperties.CHUNKED_ENCODING_SIZE, 1024*1024); - - - LOGGER.debug("POST: {}", webResource); - InputStream is = resourceWithOptionalAuthConfig(command, webResource.request()) - .accept(MediaType.TEXT_PLAIN) - .post(entity(command.getTarInputStream(), "application/tar"), Response.class).readEntity(InputStream.class); - - return new ResponseImpl(is); - + if (command.hasNoCacheEnabled()) { + webResource = webResource.queryParam("nocache", "true"); + } + if (command.hasRemoveEnabled()) { + webResource = webResource.queryParam("rm", "true"); + } + if (command.isQuiet()) { + webResource = webResource.queryParam("q", "true"); + } + if (dockerFilePath != null && !"Dockerfile".equals(dockerFilePath)) { + webResource = webResource.queryParam("dockerfile", dockerFilePath); + } + + webResource.property(ClientProperties.REQUEST_ENTITY_PROCESSING, + RequestEntityProcessing.CHUNKED); + webResource.property(ClientProperties.CHUNKED_ENCODING_SIZE, + 1024 * 1024); + + LOGGER.debug("POST: {}", webResource); + Response response = resourceWithOptionalAuthConfig(command, + webResource.request()) + .accept(MediaType.TEXT_PLAIN) + .post(entity(command.getTarInputStream(), "application/tar"), + Response.class); + + return new ResponseImpl(new WrappedResponseInputStream(response)); + } - private Invocation.Builder resourceWithOptionalAuthConfig(BuildImageCmd command, Invocation.Builder request) { + private Invocation.Builder resourceWithOptionalAuthConfig( + BuildImageCmd command, Invocation.Builder request) { AuthConfigurations authConfigs = command.getBuildAuthConfigs(); if (authConfigs != null) { - request = request.header("X-Registry-Config", registryConfigs(authConfigs)); + request = request.header("X-Registry-Config", + registryConfigs(authConfigs)); } return request; } - - public static class ResponseImpl extends BuildImageCmd.Response { - - private final InputStream proxy; - - public ResponseImpl(InputStream proxy) { - this.proxy = proxy; - } - - @Override - public Iterable getItems() throws IOException { - ObjectMapper mapper = new ObjectMapper(); - // we'll be reading instances of MyBean - ObjectReader reader = mapper.reader(EventStreamItem.class); - // and then do other configuration, if any, and read: - Iterator items = reader.readValues(proxy); - - try { - return ImmutableList.copyOf(items); - } finally { - proxy.close(); - } - } - - @Override - public int read() throws IOException { - return proxy.read(); - } - } + + public static class ResponseImpl extends BuildImageCmd.Response { + + private final InputStream proxy; + + public ResponseImpl(InputStream proxy) { + this.proxy = proxy; + } + + @Override + public Iterable getItems() throws IOException { + ObjectMapper mapper = new ObjectMapper(); + // we'll be reading instances of MyBean + ObjectReader reader = mapper.reader(EventStreamItem.class); + // and then do other configuration, if any, and read: + Iterator items = reader.readValues(proxy); + + try { + return ImmutableList.copyOf(items); + } finally { + proxy.close(); + } + } + + @Override + public int read() throws IOException { + return proxy.read(); + } + } } diff --git a/src/main/java/com/github/dockerjava/jaxrs/CopyFileFromContainerCmdExec.java b/src/main/java/com/github/dockerjava/jaxrs/CopyFileFromContainerCmdExec.java index 5126aaed3..0a738be83 100644 --- a/src/main/java/com/github/dockerjava/jaxrs/CopyFileFromContainerCmdExec.java +++ b/src/main/java/com/github/dockerjava/jaxrs/CopyFileFromContainerCmdExec.java @@ -6,11 +6,13 @@ import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.github.dockerjava.api.command.CopyFileFromContainerCmd; +import com.github.dockerjava.jaxrs.util.WrappedResponseInputStream; public class CopyFileFromContainerCmdExec extends AbstrDockerCmdExec implements CopyFileFromContainerCmd.Exec { @@ -29,7 +31,9 @@ protected InputStream execute(CopyFileFromContainerCmd command) { LOGGER.trace("POST: " + webResource.toString()); - return webResource.request().accept(MediaType.APPLICATION_OCTET_STREAM_TYPE).post(entity(command, MediaType.APPLICATION_JSON)).readEntity(InputStream.class); + Response response = webResource.request().accept(MediaType.APPLICATION_OCTET_STREAM_TYPE).post(entity(command, MediaType.APPLICATION_JSON)); + + return new WrappedResponseInputStream(response); } } diff --git a/src/main/java/com/github/dockerjava/jaxrs/EventsCmdExec.java b/src/main/java/com/github/dockerjava/jaxrs/EventsCmdExec.java index 751145426..78d26fead 100644 --- a/src/main/java/com/github/dockerjava/jaxrs/EventsCmdExec.java +++ b/src/main/java/com/github/dockerjava/jaxrs/EventsCmdExec.java @@ -20,6 +20,7 @@ import com.github.dockerjava.api.command.EventCallback; import com.github.dockerjava.api.command.EventsCmd; import com.github.dockerjava.api.model.Event; +import com.github.dockerjava.jaxrs.util.WrappedResponseInputStream; public class EventsCmdExec extends AbstrDockerCmdExec implements EventsCmd.Exec { private static final Logger LOGGER = LoggerFactory.getLogger(EventsCmdExec.class); @@ -66,7 +67,7 @@ public Void call() throws Exception { Response response = null; try { response = webTarget.request().get(Response.class); - InputStream inputStream = response.readEntity(InputStream.class); + InputStream inputStream = new WrappedResponseInputStream(response); JsonParser jp = JSON_FACTORY.createParser(inputStream); while (jp.nextToken() != JsonToken.END_OBJECT && !jp.isClosed() && eventCallback.isReceiving()) { eventCallback.onEvent(OBJECT_MAPPER.readValue(jp, Event.class)); diff --git a/src/main/java/com/github/dockerjava/jaxrs/ExecStartCmdExec.java b/src/main/java/com/github/dockerjava/jaxrs/ExecStartCmdExec.java index effd39045..cbac951d3 100644 --- a/src/main/java/com/github/dockerjava/jaxrs/ExecStartCmdExec.java +++ b/src/main/java/com/github/dockerjava/jaxrs/ExecStartCmdExec.java @@ -1,12 +1,15 @@ package com.github.dockerjava.jaxrs; import com.github.dockerjava.api.command.ExecStartCmd; +import com.github.dockerjava.jaxrs.util.WrappedResponseInputStream; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; + import java.io.InputStream; import static javax.ws.rs.client.Entity.entity; @@ -26,9 +29,11 @@ protected InputStream execute(ExecStartCmd command) { LOGGER.trace("POST: {}", webResource); - return webResource + Response response = webResource .request() .accept(MediaType.APPLICATION_JSON) - .post(entity(command, MediaType.APPLICATION_JSON), Response.class).readEntity(InputStream.class); + .post(entity(command, MediaType.APPLICATION_JSON), Response.class); + + return new WrappedResponseInputStream(response); } } diff --git a/src/main/java/com/github/dockerjava/jaxrs/LogContainerCmdExec.java b/src/main/java/com/github/dockerjava/jaxrs/LogContainerCmdExec.java index b279ebdcf..7d031bddd 100644 --- a/src/main/java/com/github/dockerjava/jaxrs/LogContainerCmdExec.java +++ b/src/main/java/com/github/dockerjava/jaxrs/LogContainerCmdExec.java @@ -8,6 +8,7 @@ import org.slf4j.LoggerFactory; import com.github.dockerjava.api.command.LogContainerCmd; +import com.github.dockerjava.jaxrs.util.WrappedResponseInputStream; public class LogContainerCmdExec extends AbstrDockerCmdExec implements LogContainerCmd.Exec { @@ -28,7 +29,8 @@ protected InputStream execute(LogContainerCmd command) { .queryParam("tail", command.getTail() < 0 ? "all" : "" + command.getTail()); LOGGER.trace("GET: {}", webResource); - return webResource.request().get().readEntity(InputStream.class); + + return new WrappedResponseInputStream(webResource.request().get()); } } diff --git a/src/main/java/com/github/dockerjava/jaxrs/PullImageCmdExec.java b/src/main/java/com/github/dockerjava/jaxrs/PullImageCmdExec.java index 725483f85..bd89d22f6 100644 --- a/src/main/java/com/github/dockerjava/jaxrs/PullImageCmdExec.java +++ b/src/main/java/com/github/dockerjava/jaxrs/PullImageCmdExec.java @@ -5,12 +5,14 @@ import javax.ws.rs.client.Invocation; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.github.dockerjava.api.command.PullImageCmd; import com.github.dockerjava.api.model.AuthConfig; +import com.github.dockerjava.jaxrs.util.WrappedResponseInputStream; public class PullImageCmdExec extends AbstrDockerCmdExec implements @@ -31,9 +33,10 @@ protected InputStream execute(PullImageCmd command) { .queryParam("registry", command.getRegistry()); LOGGER.trace("POST: {}", webResource); - return resourceWithOptionalAuthConfig(command, webResource.request()) - .accept(MediaType.APPLICATION_OCTET_STREAM_TYPE).post(null) - .readEntity(InputStream.class); + Response response = resourceWithOptionalAuthConfig(command, webResource.request()) + .accept(MediaType.APPLICATION_OCTET_STREAM_TYPE).post(null); + + return new WrappedResponseInputStream(response); } private Invocation.Builder resourceWithOptionalAuthConfig( diff --git a/src/main/java/com/github/dockerjava/jaxrs/PushImageCmdExec.java b/src/main/java/com/github/dockerjava/jaxrs/PushImageCmdExec.java index e0120c2f5..7fc88d91b 100644 --- a/src/main/java/com/github/dockerjava/jaxrs/PushImageCmdExec.java +++ b/src/main/java/com/github/dockerjava/jaxrs/PushImageCmdExec.java @@ -21,6 +21,7 @@ import com.github.dockerjava.api.model.AuthConfig; import com.github.dockerjava.api.model.PushEventStreamItem; +import com.github.dockerjava.jaxrs.util.WrappedResponseInputStream; // Shaded, but imported import com.google.common.collect.ImmutableList; @@ -40,15 +41,14 @@ protected ResponseImpl execute(PushImageCmd command) { final String registryAuth = registryAuth(command.getAuthConfig()); LOGGER.trace("POST: {}", webResource); - InputStream is = webResource + javax.ws.rs.core.Response response = webResource .request() .header("X-Registry-Auth", registryAuth) .accept(MediaType.APPLICATION_JSON) .post( - entity(Response.class, MediaType.APPLICATION_JSON)).readEntity( - InputStream.class); + entity(Response.class, MediaType.APPLICATION_JSON)); - return new ResponseImpl(is); + return new ResponseImpl(new WrappedResponseInputStream(response)); } private String name(PushImageCmd command) { diff --git a/src/main/java/com/github/dockerjava/jaxrs/SaveImageCmdExec.java b/src/main/java/com/github/dockerjava/jaxrs/SaveImageCmdExec.java index b1eccd8f2..8d70e2f6f 100644 --- a/src/main/java/com/github/dockerjava/jaxrs/SaveImageCmdExec.java +++ b/src/main/java/com/github/dockerjava/jaxrs/SaveImageCmdExec.java @@ -4,11 +4,13 @@ import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.github.dockerjava.api.command.SaveImageCmd; +import com.github.dockerjava.jaxrs.util.WrappedResponseInputStream; public class SaveImageCmdExec extends AbstrDockerCmdExec implements SaveImageCmd.Exec { private static final Logger LOGGER = LoggerFactory @@ -24,11 +26,11 @@ protected InputStream execute(SaveImageCmd command) { .queryParam("tag", command.getTag()); LOGGER.trace("GET: {}", webResource); - InputStream is = webResource + Response response = webResource .request() .accept(MediaType.APPLICATION_JSON) - .get().readEntity(InputStream.class); + .get(); - return is; + return new WrappedResponseInputStream(response); } } diff --git a/src/main/java/com/github/dockerjava/jaxrs/util/WrappedResponseInputStream.java b/src/main/java/com/github/dockerjava/jaxrs/util/WrappedResponseInputStream.java new file mode 100644 index 000000000..bf40dbfc0 --- /dev/null +++ b/src/main/java/com/github/dockerjava/jaxrs/util/WrappedResponseInputStream.java @@ -0,0 +1,70 @@ +package com.github.dockerjava.jaxrs.util; + +import java.io.IOException; +import java.io.InputStream; + +import javax.ws.rs.core.Response; + +/** + * This is a wrapper around {@link Response} that acts as a {@link InputStream}. + * When this {@link WrappedResponseInputStream} is closed it closes the + * underlying {@link Response} object also to prevent connection leaks. + * + * @author marcus + */ +public class WrappedResponseInputStream extends InputStream { + + private Response response; + private InputStream delegate; + + public WrappedResponseInputStream(Response response) { + this.response = response; + this.delegate = response.readEntity(InputStream.class); + } + + public int read() throws IOException { + return delegate.read(); + } + + public int hashCode() { + return delegate.hashCode(); + } + + public int read(byte[] b) throws IOException { + return delegate.read(b); + } + + public boolean equals(Object obj) { + return delegate.equals(obj); + } + + public int read(byte[] b, int off, int len) throws IOException { + return delegate.read(b, off, len); + } + + public long skip(long n) throws IOException { + return delegate.skip(n); + } + + public int available() throws IOException { + return delegate.available(); + } + + public void close() throws IOException { + response.close(); + delegate.close(); + } + + public void mark(int readlimit) { + delegate.mark(readlimit); + } + + public void reset() throws IOException { + delegate.reset(); + } + + public boolean markSupported() { + return delegate.markSupported(); + } + +} diff --git a/src/test/java/com/github/dockerjava/core/command/LogContainerCmdImplTest.java b/src/test/java/com/github/dockerjava/core/command/LogContainerCmdImplTest.java index 842992198..94a27b1d3 100644 --- a/src/test/java/com/github/dockerjava/core/command/LogContainerCmdImplTest.java +++ b/src/test/java/com/github/dockerjava/core/command/LogContainerCmdImplTest.java @@ -79,6 +79,41 @@ public void logNonExistingContainer() throws Exception { } catch (NotFoundException e) { } } + + @Test + public void multipleLogContainer() throws Exception { + + String snippet = "hello world"; + + CreateContainerResponse container = dockerClient + .createContainerCmd("busybox").withCmd("/bin/echo", snippet).exec(); + + LOG.info("Created container: {}", container.toString()); + assertThat(container.getId(), not(isEmptyString())); + + dockerClient.startContainerCmd(container.getId()).exec(); + + int exitCode = dockerClient.waitContainerCmd(container.getId()).exec(); + + assertThat(exitCode, equalTo(0)); + + InputStream response = dockerClient.logContainerCmd(container.getId()).withStdErr().withStdOut().exec(); + + response.close(); + + //String log = asString(response); + + response = dockerClient.logContainerCmd(container.getId()).withStdErr().withStdOut().exec(); + + //log = asString(response); + response.close(); + + response = dockerClient.logContainerCmd(container.getId()).withStdErr().withStdOut().exec(); + + String log = asString(response); + + assertThat(log, endsWith(snippet)); + } }