diff --git a/pom.xml b/pom.xml
index 72e9dfd0f..302eb7a36 100644
--- a/pom.xml
+++ b/pom.xml
@@ -39,6 +39,13 @@
+
+
+ jcenter
+ http://jcenter.bintray.com
+
+
+
UTF-8
UTF-8
@@ -61,9 +68,9 @@
2.6
1.7.5
1.3.9
- 0.3
18.0
1.51
+ 2014-09-26T18-52-33
1.0.1
@@ -93,12 +100,23 @@
+
+ org.glassfish.jersey.connectors
+ jersey-apache-connector
+ ${jersey.version}
+
org.glassfish.jersey.core
jersey-client
${jersey.version}
+
+ de.gesellix
+ unix-socket-factory
+ ${unix-socket-factory.version}
+
+
org.apache.commons
commons-compress
@@ -120,11 +138,6 @@
${commons-io.version}
-
- com.github.jnr
- jnr-unixsocket
- ${jnr.unixsocket.version}
-
org.slf4j
diff --git a/src/main/java/com/github/dockerjava/api/command/EventCallback.java b/src/main/java/com/github/dockerjava/api/command/EventCallback.java
index 45ac34dc8..17f796aaa 100644
--- a/src/main/java/com/github/dockerjava/api/command/EventCallback.java
+++ b/src/main/java/com/github/dockerjava/api/command/EventCallback.java
@@ -9,4 +9,5 @@ public interface EventCallback {
public void onEvent(Event event);
public void onException(Throwable throwable);
public void onCompletion(int numEvents);
+ public boolean isReceiving();
}
diff --git a/src/main/java/com/github/dockerjava/core/DockerClientConfig.java b/src/main/java/com/github/dockerjava/core/DockerClientConfig.java
index c12585a54..0fa66eca7 100644
--- a/src/main/java/com/github/dockerjava/core/DockerClientConfig.java
+++ b/src/main/java/com/github/dockerjava/core/DockerClientConfig.java
@@ -22,6 +22,10 @@ public class DockerClientConfig {
// this is really confusing, as there are two ways to spell it
private static final String DOCKER_IO_ENABLE_LOGGING_FILTER_PROPERTY = "docker.io.enableLoggingFilter";
private static final String DOCKER_IO_DOCKER_CERT_PATH_PROPERTY = "docker.io.dockerCertPath";
+ // connection pooling properties
+ private static final String DOCKER_IO_MAX_PER_ROUTE_PROPERTY = "docker.io.perRouteConnections";
+ private static final String DOCKER_IO_MAX_TOTAL_PROPERTY = "docker.io.totalConnections";
+
/**
* A map from the environment name to the interval name.
*/
@@ -36,12 +40,17 @@ public class DockerClientConfig {
.put(DOCKER_CERT_PATH_PROPERTY, DOCKER_IO_DOCKER_CERT_PATH_PROPERTY)
.build();
private static final String DOCKER_IO_PROPERTIES_PROPERTY = "docker.io.properties";
- private final URI uri;
+ private URI uri;
private final String version, username, password, email, dockerCertPath;
private final Integer readTimeout;
private final boolean loggingFilterEnabled;
+
+ private final int maxTotalConnections;
+ private final int maxPerRouteConnections;
- DockerClientConfig(URI uri, String version, String username, String password, String email, String dockerCertPath, Integer readTimeout, boolean loggingFilterEnabled) {
+ DockerClientConfig(URI uri, String version, String username, String password, String email,
+ String dockerCertPath, Integer readTimeout, boolean loggingFilterEnabled,
+ int maxTotalConns, int maxPerRouteConns) {
this.uri = uri;
this.version = version;
this.username = username;
@@ -50,6 +59,9 @@ public class DockerClientConfig {
this.dockerCertPath = dockerCertPath;
this.readTimeout = readTimeout;
this.loggingFilterEnabled = loggingFilterEnabled;
+
+ this.maxTotalConnections = maxTotalConns;
+ this.maxPerRouteConnections = maxPerRouteConns;
}
private static Properties loadIncludedDockerProperties(Properties systemProperties) {
@@ -176,7 +188,11 @@ public URI getUri() {
return uri;
}
- public String getVersion() {
+ public void setUri(URI uri) {
+ this.uri = uri;
+ }
+
+ public String getVersion() {
return version;
}
@@ -254,7 +270,7 @@ public String toString() {
public static class DockerClientConfigBuilder {
private URI uri;
private String version, username, password, email, dockerCertPath;
- private Integer readTimeout;
+ private Integer readTimeout, maxTotalConnections, maxPerRouteConnections;
private boolean loggingFilterEnabled;
/**
@@ -271,7 +287,10 @@ public DockerClientConfigBuilder withProperties(Properties p) {
.withEmail(p.getProperty(DOCKER_IO_EMAIL_PROPERTY))
.withReadTimeout(Integer.valueOf(p.getProperty(DOCKER_IO_READ_TIMEOUT_PROPERTY, "0")))
.withLoggingFilter(Boolean.valueOf(p.getProperty(DOCKER_IO_ENABLE_LOGGING_FILTER_PROPERTY, "true")))
- .withDockerCertPath(p.getProperty(DOCKER_IO_DOCKER_CERT_PATH_PROPERTY));
+ .withDockerCertPath(p.getProperty(DOCKER_IO_DOCKER_CERT_PATH_PROPERTY))
+ .withMaxPerRouteConnections(Integer.valueOf(p.getProperty(DOCKER_IO_MAX_PER_ROUTE_PROPERTY, "2")))
+ .withMaxTotalConnections(Integer.valueOf(p.getProperty(DOCKER_IO_MAX_TOTAL_PROPERTY, "20")))
+ ;
}
public final DockerClientConfigBuilder withUri(String uri) {
@@ -304,6 +323,16 @@ public final DockerClientConfigBuilder withReadTimeout(Integer readTimeout) {
this.readTimeout = readTimeout;
return this;
}
+
+ public final DockerClientConfigBuilder withMaxTotalConnections(Integer maxTotalConnections) {
+ this.maxTotalConnections = maxTotalConnections;
+ return this;
+ }
+
+ public final DockerClientConfigBuilder withMaxPerRouteConnections(Integer maxPerRouteConnections) {
+ this.maxPerRouteConnections = maxPerRouteConnections;
+ return this;
+ }
public final DockerClientConfigBuilder withLoggingFilter(boolean loggingFilterEnabled) {
this.loggingFilterEnabled = loggingFilterEnabled;
@@ -324,8 +353,18 @@ public DockerClientConfig build() {
email,
dockerCertPath,
readTimeout,
- loggingFilterEnabled
+ loggingFilterEnabled,
+ maxTotalConnections,
+ maxPerRouteConnections
);
}
}
+
+ public int getMaxTotalConnections() {
+ return maxTotalConnections;
+ }
+
+ public int getMaxPerRoutConnections() {
+ return maxPerRouteConnections;
+ }
}
diff --git a/src/main/java/com/github/dockerjava/jaxrs/ApacheUnixSocket.java b/src/main/java/com/github/dockerjava/jaxrs/ApacheUnixSocket.java
new file mode 100644
index 000000000..a4adb1bb7
--- /dev/null
+++ b/src/main/java/com/github/dockerjava/jaxrs/ApacheUnixSocket.java
@@ -0,0 +1,327 @@
+package com.github.dockerjava.jaxrs;
+/*
+ * Copyright (c) 2014 Spotify AB.
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+
+import com.google.common.collect.Queues;
+
+import org.newsclub.net.unix.AFUNIXSocket;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.nio.channels.SocketChannel;
+import java.util.Queue;
+
+/**
+ * Provides a socket that wraps an org.newsclub.net.unix.AFUNIXSocket and delays setting options
+ * until the socket is connected. This is necessary because the Apache HTTP client attempts to
+ * set options prior to connecting the socket, which doesn't work for Unix sockets since options
+ * are being set on the underlying file descriptor. Until the socket is connected, the file
+ * descriptor doesn't exist.
+ *
+ * This class also noop's any calls to setReuseAddress, which is called by the Apache client but
+ * isn't supported by AFUnixSocket.
+ */
+public class ApacheUnixSocket extends Socket {
+
+ private final AFUNIXSocket inner;
+
+ private final Queue optionsToSet = Queues.newArrayDeque();
+
+ public ApacheUnixSocket() throws IOException {
+ this.inner = AFUNIXSocket.newInstance();
+ }
+
+ @Override
+ public void connect(final SocketAddress endpoint) throws IOException {
+ inner.connect(endpoint);
+ setAllSocketOptions();
+ }
+
+ @Override
+ public void connect(final SocketAddress endpoint, final int timeout) throws IOException {
+ inner.connect(endpoint, timeout);
+ setAllSocketOptions();
+ }
+
+ @Override
+ public void bind(final SocketAddress bindpoint) throws IOException {
+ inner.bind(bindpoint);
+ setAllSocketOptions();
+ }
+
+ @Override
+ public InetAddress getInetAddress() {
+ return inner.getInetAddress();
+ }
+
+ @Override
+ public InetAddress getLocalAddress() {
+ return inner.getLocalAddress();
+ }
+
+ @Override
+ public int getPort() {
+ return inner.getPort();
+ }
+
+ @Override
+ public int getLocalPort() {
+ return inner.getLocalPort();
+ }
+
+ @Override
+ public SocketAddress getRemoteSocketAddress() {
+ return inner.getRemoteSocketAddress();
+ }
+
+ @Override
+ public SocketAddress getLocalSocketAddress() {
+ return inner.getLocalSocketAddress();
+ }
+
+ @Override
+ public SocketChannel getChannel() {
+ return inner.getChannel();
+ }
+
+ @Override
+ public InputStream getInputStream() throws IOException {
+ return inner.getInputStream();
+ }
+
+ @Override
+ public OutputStream getOutputStream() throws IOException {
+ return inner.getOutputStream();
+ }
+
+ private void setSocketOption(final SocketOptionSetter s) throws SocketException {
+ if (inner.isConnected()) {
+ s.run();
+ } else {
+ if (!optionsToSet.offer(s)) {
+ throw new SocketException("Failed to queue option");
+ }
+ }
+ }
+
+ private void setAllSocketOptions() throws SocketException {
+ for (SocketOptionSetter s : optionsToSet) {
+ s.run();
+ }
+ }
+
+ @Override
+ public void setTcpNoDelay(final boolean on) throws SocketException {
+ setSocketOption(new SocketOptionSetter() {
+ @Override
+ public void run() throws SocketException {
+ inner.setTcpNoDelay(on);
+ }
+ });
+ }
+
+ @Override
+ public boolean getTcpNoDelay() throws SocketException {
+ return inner.getTcpNoDelay();
+ }
+
+ @Override
+ public void setSoLinger(final boolean on, final int linger) throws SocketException {
+ setSocketOption(new SocketOptionSetter() {
+ @Override
+ public void run() throws SocketException {
+ inner.setSoLinger(on, linger);
+ }
+ });
+ }
+
+ @Override
+ public int getSoLinger() throws SocketException {
+ return inner.getSoLinger();
+ }
+
+ @Override
+ public void sendUrgentData(final int data) throws IOException {
+ inner.sendUrgentData(data);
+ }
+
+ @Override
+ public void setOOBInline(final boolean on) throws SocketException {
+ setSocketOption(new SocketOptionSetter() {
+ @Override
+ public void run() throws SocketException {
+ inner.setOOBInline(on);
+ }
+ });
+ }
+
+ @Override
+ public boolean getOOBInline() throws SocketException {
+ return inner.getOOBInline();
+ }
+
+ @Override
+ public synchronized void setSoTimeout(final int timeout) throws SocketException {
+ setSocketOption(new SocketOptionSetter() {
+ @Override
+ public void run() throws SocketException {
+ inner.setSoTimeout(timeout);
+ }
+ });
+ }
+
+ @Override
+ public synchronized int getSoTimeout() throws SocketException {
+ return inner.getSoTimeout();
+ }
+
+ @Override
+ public synchronized void setSendBufferSize(final int size) throws SocketException {
+ setSocketOption(new SocketOptionSetter() {
+ @Override
+ public void run() throws SocketException {
+ inner.setSendBufferSize(size);
+ }
+ });
+ }
+
+ @Override
+ public synchronized int getSendBufferSize() throws SocketException {
+ return inner.getSendBufferSize();
+ }
+
+ @Override
+ public synchronized void setReceiveBufferSize(final int size) throws SocketException {
+ setSocketOption(new SocketOptionSetter() {
+ @Override
+ public void run() throws SocketException {
+ inner.setReceiveBufferSize(size);
+ }
+ });
+ }
+
+ @Override
+ public synchronized int getReceiveBufferSize() throws SocketException {
+ return inner.getReceiveBufferSize();
+ }
+
+ @Override
+ public void setKeepAlive(final boolean on) throws SocketException {
+ setSocketOption(new SocketOptionSetter() {
+ @Override
+ public void run() throws SocketException {
+ inner.setKeepAlive(on);
+ }
+ });
+ }
+
+ @Override
+ public boolean getKeepAlive() throws SocketException {
+ return inner.getKeepAlive();
+ }
+
+ @Override
+ public void setTrafficClass(final int tc) throws SocketException {
+ setSocketOption(new SocketOptionSetter() {
+ @Override
+ public void run() throws SocketException {
+ inner.setTrafficClass(tc);
+ }
+ });
+ }
+
+ @Override
+ public int getTrafficClass() throws SocketException {
+ return inner.getTrafficClass();
+ }
+
+ @Override
+ public void setReuseAddress(final boolean on) throws SocketException {
+ // not supported: Apache client tries to set it, but we want to just ignore it
+ }
+
+ @Override
+ public boolean getReuseAddress() throws SocketException {
+ return inner.getReuseAddress();
+ }
+
+ @Override
+ public synchronized void close() throws IOException {
+ inner.close();
+ }
+
+ @Override
+ public void shutdownInput() throws IOException {
+ inner.shutdownInput();
+ }
+
+ @Override
+ public void shutdownOutput() throws IOException {
+ inner.shutdownOutput();
+ }
+
+ @Override
+ public String toString() {
+ return inner.toString();
+ }
+
+ @Override
+ public boolean isConnected() {
+ return inner.isConnected();
+ }
+
+ @Override
+ public boolean isBound() {
+ return inner.isBound();
+ }
+
+ @Override
+ public boolean isClosed() {
+ return inner.isClosed();
+ }
+
+ @Override
+ public boolean isInputShutdown() {
+ return inner.isInputShutdown();
+ }
+
+ @Override
+ public boolean isOutputShutdown() {
+ return inner.isOutputShutdown();
+ }
+
+ @Override
+ public void setPerformancePreferences(final int connectionTime, final int latency,
+ final int bandwidth) {
+ inner.setPerformancePreferences(connectionTime, latency, bandwidth);
+ }
+
+ interface SocketOptionSetter {
+ void run() throws SocketException;
+ }
+}
diff --git a/src/main/java/com/github/dockerjava/jaxrs/ContainerDiffCmdExec.java b/src/main/java/com/github/dockerjava/jaxrs/ContainerDiffCmdExec.java
index 0a657ed90..2b7059b33 100644
--- a/src/main/java/com/github/dockerjava/jaxrs/ContainerDiffCmdExec.java
+++ b/src/main/java/com/github/dockerjava/jaxrs/ContainerDiffCmdExec.java
@@ -26,7 +26,8 @@ protected List execute(ContainerDiffCmd command) {
WebTarget webResource = getBaseResource().path("/containers/{id}/changes").resolveTemplate("id", command.getContainerId());
LOGGER.trace("GET: {}", webResource);
- return webResource.request().accept(MediaType.APPLICATION_JSON).get(new GenericType>() {
+ return webResource.request().accept(MediaType.APPLICATION_JSON)
+ .get(new GenericType>() {
});
}
diff --git a/src/main/java/com/github/dockerjava/jaxrs/DockerCmdExecFactoryImpl.java b/src/main/java/com/github/dockerjava/jaxrs/DockerCmdExecFactoryImpl.java
index 862824fcc..dae172e34 100644
--- a/src/main/java/com/github/dockerjava/jaxrs/DockerCmdExecFactoryImpl.java
+++ b/src/main/java/com/github/dockerjava/jaxrs/DockerCmdExecFactoryImpl.java
@@ -1,28 +1,69 @@
package com.github.dockerjava.jaxrs;
+import java.io.IOException;
+import java.net.URI;
+import java.security.KeyStore;
+import java.security.Security;
+import java.util.logging.Logger;
+
+import javax.net.ssl.SSLContext;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+
+import org.apache.http.config.RegistryBuilder;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.conn.socket.PlainConnectionSocketFactory;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.glassfish.jersey.CommonProperties;
+import org.glassfish.jersey.SslConfigurator;
+import org.glassfish.jersey.apache.connector.ApacheClientProperties;
+import org.glassfish.jersey.apache.connector.ApacheConnectorProvider;
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.client.ClientProperties;
+
import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
import com.github.dockerjava.api.DockerClientException;
-import com.github.dockerjava.api.command.*;
+import com.github.dockerjava.api.command.AttachContainerCmd;
+import com.github.dockerjava.api.command.AuthCmd;
+import com.github.dockerjava.api.command.BuildImageCmd;
+import com.github.dockerjava.api.command.CommitCmd;
+import com.github.dockerjava.api.command.ContainerDiffCmd;
+import com.github.dockerjava.api.command.CopyFileFromContainerCmd;
+import com.github.dockerjava.api.command.CreateContainerCmd;
+import com.github.dockerjava.api.command.CreateImageCmd;
+import com.github.dockerjava.api.command.DockerCmdExecFactory;
+import com.github.dockerjava.api.command.EventsCmd;
+import com.github.dockerjava.api.command.InfoCmd;
+import com.github.dockerjava.api.command.InspectContainerCmd;
+import com.github.dockerjava.api.command.InspectImageCmd;
+import com.github.dockerjava.api.command.KillContainerCmd;
+import com.github.dockerjava.api.command.ListContainersCmd;
+import com.github.dockerjava.api.command.ListImagesCmd;
+import com.github.dockerjava.api.command.LogContainerCmd;
+import com.github.dockerjava.api.command.PauseContainerCmd;
+import com.github.dockerjava.api.command.PingCmd;
+import com.github.dockerjava.api.command.PullImageCmd;
+import com.github.dockerjava.api.command.PushImageCmd;
+import com.github.dockerjava.api.command.RemoveContainerCmd;
+import com.github.dockerjava.api.command.RemoveImageCmd;
+import com.github.dockerjava.api.command.RestartContainerCmd;
+import com.github.dockerjava.api.command.SearchImagesCmd;
+import com.github.dockerjava.api.command.StartContainerCmd;
+import com.github.dockerjava.api.command.StopContainerCmd;
+import com.github.dockerjava.api.command.TagImageCmd;
+import com.github.dockerjava.api.command.TopContainerCmd;
+import com.github.dockerjava.api.command.UnpauseContainerCmd;
+import com.github.dockerjava.api.command.VersionCmd;
+import com.github.dockerjava.api.command.WaitContainerCmd;
import com.github.dockerjava.core.CertificateUtils;
import com.github.dockerjava.core.DockerClientConfig;
import com.github.dockerjava.jaxrs.util.JsonClientFilter;
import com.github.dockerjava.jaxrs.util.ResponseStatusExceptionFilter;
import com.github.dockerjava.jaxrs.util.SelectiveLoggingFilter;
import com.google.common.base.Preconditions;
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.glassfish.jersey.CommonProperties;
-import org.glassfish.jersey.SslConfigurator;
-import org.glassfish.jersey.client.ClientConfig;
-import org.glassfish.jersey.client.ClientProperties;
-
-import javax.net.ssl.SSLContext;
-import javax.ws.rs.client.Client;
-import javax.ws.rs.client.ClientBuilder;
-import javax.ws.rs.client.WebTarget;
-import java.io.IOException;
-import java.security.KeyStore;
-import java.security.Security;
-import java.util.logging.Logger;
public class DockerCmdExecFactoryImpl implements DockerCmdExecFactory {
@@ -35,6 +76,7 @@ public void init(DockerClientConfig dockerClientConfig) {
Preconditions.checkNotNull(dockerClientConfig, "config was not specified");
ClientConfig clientConfig = new ClientConfig();
+ clientConfig.connectorProvider(new ApacheConnectorProvider());
clientConfig.property(CommonProperties.FEATURE_AUTO_DISCOVERY_DISABLE, true);
clientConfig.register(ResponseStatusExceptionFilter.class);
@@ -50,6 +92,13 @@ public void init(DockerClientConfig dockerClientConfig) {
clientConfig.property(ClientProperties.READ_TIMEOUT, readTimeout);
}
+ URI originalUri = dockerClientConfig.getUri();
+ PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(getSchemeRegistry(originalUri));
+ connManager.setMaxTotal(dockerClientConfig.getMaxTotalConnections());
+ connManager.setDefaultMaxPerRoute(dockerClientConfig.getMaxPerRoutConnections());
+ clientConfig.property(ApacheClientProperties.CONNECTION_MANAGER,
+ connManager);
+
ClientBuilder clientBuilder = ClientBuilder.newBuilder().withConfig(clientConfig);
String dockerCertPath = dockerClientConfig.getDockerCertPath();
@@ -91,6 +140,10 @@ public void init(DockerClientConfig dockerClientConfig) {
client = clientBuilder.build();
+ if (originalUri.getScheme().equals("unix")) {
+ dockerClientConfig.setUri(UnixConnectionSocketFactory.sanitizeUri(originalUri));
+ }
+
WebTarget webResource = client.target(dockerClientConfig.getUri());
if (dockerClientConfig.getVersion() == null || dockerClientConfig.getVersion().isEmpty()) {
@@ -101,6 +154,15 @@ public void init(DockerClientConfig dockerClientConfig) {
}
+ private org.apache.http.config.Registry getSchemeRegistry(final URI originalUri) {
+ return RegistryBuilder
+ .create()
+ .register("http", PlainConnectionSocketFactory.getSocketFactory())
+ .register("https", SSLConnectionSocketFactory.getSocketFactory())
+ .register("unix", new UnixConnectionSocketFactory(originalUri))
+ .build();
+ }
+
protected WebTarget getBaseResource() {
Preconditions.checkNotNull(baseResource, "Factory not initialized. You probably forgot to call init()!");
return baseResource;
diff --git a/src/main/java/com/github/dockerjava/jaxrs/EventsCmdExec.java b/src/main/java/com/github/dockerjava/jaxrs/EventsCmdExec.java
index 545d7db98..ffd757e93 100644
--- a/src/main/java/com/github/dockerjava/jaxrs/EventsCmdExec.java
+++ b/src/main/java/com/github/dockerjava/jaxrs/EventsCmdExec.java
@@ -67,7 +67,7 @@ public Void call() throws Exception {
response = webTarget.request().get(Response.class);
InputStream inputStream = response.readEntity(InputStream.class);
JsonParser jp = JSON_FACTORY.createParser(inputStream);
- while (jp.nextToken() != JsonToken.END_OBJECT && !jp.isClosed()) {
+ while (jp.nextToken() != JsonToken.END_OBJECT && !jp.isClosed() && eventCallback.isReceiving()) {
eventCallback.onEvent(OBJECT_MAPPER.readValue(jp, Event.class));
numEvents++;
}
diff --git a/src/main/java/com/github/dockerjava/jaxrs/PullImageCmdExec.java b/src/main/java/com/github/dockerjava/jaxrs/PullImageCmdExec.java
index 4ac190be4..42352170c 100644
--- a/src/main/java/com/github/dockerjava/jaxrs/PullImageCmdExec.java
+++ b/src/main/java/com/github/dockerjava/jaxrs/PullImageCmdExec.java
@@ -31,7 +31,8 @@ protected InputStream execute(PullImageCmd command) {
LOGGER.trace("POST: {}", webResource);
return webResource.request()
.accept(MediaType.APPLICATION_OCTET_STREAM_TYPE)
- .post(entity(Response.class, MediaType.APPLICATION_JSON)).readEntity(InputStream.class);
+ .post(entity(Response.class, MediaType.APPLICATION_JSON))
+ .readEntity(InputStream.class);
}
}
diff --git a/src/main/java/com/github/dockerjava/jaxrs/RemoveContainerCmdExec.java b/src/main/java/com/github/dockerjava/jaxrs/RemoveContainerCmdExec.java
index 7f45bdda2..7a479ce2f 100644
--- a/src/main/java/com/github/dockerjava/jaxrs/RemoveContainerCmdExec.java
+++ b/src/main/java/com/github/dockerjava/jaxrs/RemoveContainerCmdExec.java
@@ -23,8 +23,8 @@ protected Void execute(RemoveContainerCmd command) {
.queryParam("force", command.hasForceEnabled() ? "1" : "0");
LOGGER.trace("DELETE: {}", webResource);
- String response = webResource.request().accept(MediaType.APPLICATION_JSON).delete(String.class);
- LOGGER.trace("Response: {}", response);
+ /*String response = */webResource.request().accept(MediaType.APPLICATION_JSON).delete().close();
+// LOGGER.trace("Response: {}", response);
return null;
}
diff --git a/src/main/java/com/github/dockerjava/jaxrs/RemoveImageCmdExec.java b/src/main/java/com/github/dockerjava/jaxrs/RemoveImageCmdExec.java
index 54fb327b4..6bfeecd71 100644
--- a/src/main/java/com/github/dockerjava/jaxrs/RemoveImageCmdExec.java
+++ b/src/main/java/com/github/dockerjava/jaxrs/RemoveImageCmdExec.java
@@ -23,7 +23,7 @@ protected Void execute(RemoveImageCmd command) {
.queryParam("noprune", command.hasNoPruneEnabled() ? "1" : "0");
LOGGER.trace("DELETE: {}", webResource);
- webResource.request().delete(Response.class);
+ webResource.request().delete().close();
return null;
}
diff --git a/src/main/java/com/github/dockerjava/jaxrs/UnixConnectionSocketFactory.java b/src/main/java/com/github/dockerjava/jaxrs/UnixConnectionSocketFactory.java
new file mode 100644
index 000000000..4b6cfbba3
--- /dev/null
+++ b/src/main/java/com/github/dockerjava/jaxrs/UnixConnectionSocketFactory.java
@@ -0,0 +1,85 @@
+package com.github.dockerjava.jaxrs;
+/*
+ * Copyright (c) 2014 Spotify AB.
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+
+import org.apache.http.HttpHost;
+import org.apache.http.annotation.Immutable;
+import org.apache.http.conn.ConnectTimeoutException;
+import org.apache.http.conn.socket.ConnectionSocketFactory;
+import org.apache.http.protocol.HttpContext;
+import org.newsclub.net.unix.AFUNIXSocketAddress;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+import java.net.URI;
+
+/**
+ * Provides a ConnectionSocketFactory for connecting Apache HTTP clients to Unix sockets.
+ */
+@Immutable
+public class UnixConnectionSocketFactory implements ConnectionSocketFactory {
+
+ private File socketFile;
+
+ public UnixConnectionSocketFactory(final URI socketUri) {
+ super();
+
+ final String filename = socketUri.toString()
+ .replaceAll("^unix:///", "unix://localhost/")
+ .replaceAll("^unix://localhost", "");
+
+ this.socketFile = new File(filename);
+ }
+
+ public static URI sanitizeUri(final URI uri) {
+ if (uri.getScheme().equals("unix")) {
+ return URI.create("unix://localhost:80");
+ } else {
+ return uri;
+ }
+ }
+
+ @Override
+ public Socket createSocket(final HttpContext context) throws IOException {
+ return new ApacheUnixSocket();
+ }
+
+ @Override
+ public Socket connectSocket(final int connectTimeout,
+ final Socket socket,
+ final HttpHost host,
+ final InetSocketAddress remoteAddress,
+ final InetSocketAddress localAddress,
+ final HttpContext context) throws IOException {
+ try {
+ socket.connect(new AFUNIXSocketAddress(socketFile), connectTimeout);
+ } catch (SocketTimeoutException e) {
+ throw new ConnectTimeoutException(e, null, remoteAddress.getAddress());
+ }
+
+ return socket;
+ }
+}
diff --git a/src/main/java/com/github/dockerjava/jaxrs/util/ResponseStatusExceptionFilter.java b/src/main/java/com/github/dockerjava/jaxrs/util/ResponseStatusExceptionFilter.java
index 61d97f93b..0e474df59 100644
--- a/src/main/java/com/github/dockerjava/jaxrs/util/ResponseStatusExceptionFilter.java
+++ b/src/main/java/com/github/dockerjava/jaxrs/util/ResponseStatusExceptionFilter.java
@@ -2,6 +2,7 @@
import java.io.EOFException;
import java.io.IOException;
+import java.io.InputStream;
import java.nio.charset.Charset;
import javax.ws.rs.client.ClientRequestContext;
@@ -63,7 +64,9 @@ public String getBodyAsMessage(ClientResponseContext responseContext)
if (contentLength != -1) {
byte[] buffer = new byte[contentLength];
try {
- IOUtils.readFully(responseContext.getEntityStream(), buffer);
+ InputStream entityStream = responseContext.getEntityStream();
+ IOUtils.readFully(entityStream, buffer);
+ entityStream.close();
}
catch (EOFException e) {
return null;
diff --git a/src/test/java/com/github/dockerjava/api/model/PortsTest.java b/src/test/java/com/github/dockerjava/api/model/PortsTest.java
index 6a6c5e44e..f60be4354 100644
--- a/src/test/java/com/github/dockerjava/api/model/PortsTest.java
+++ b/src/test/java/com/github/dockerjava/api/model/PortsTest.java
@@ -1,37 +1,37 @@
-package com.github.dockerjava.api.model;
-
-import static org.testng.Assert.assertEquals;
-
-import java.util.Map;
-
-import org.testng.annotations.Test;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.github.dockerjava.api.model.Ports.Binding;
-
-public class PortsTest {
- private final ObjectMapper objectMapper = new ObjectMapper();
- private final String jsonWithDoubleBindingForOnePort =
- "{\"80/tcp\":[{\"HostIp\":\"10.0.0.1\",\"HostPort\":\"80\"},{\"HostIp\":\"10.0.0.2\",\"HostPort\":\"80\"}]}";
-
- @Test
- public void deserializingPortWithMultipleBindings() throws Exception {
- Ports ports = objectMapper.readValue(jsonWithDoubleBindingForOnePort, Ports.class);
- Map map = ports.getBindings();
- assertEquals(map.size(), 1);
-
- Binding[] bindings = map.get(ExposedPort.tcp(80));
- assertEquals(bindings.length, 2);
- assertEquals(bindings[0], new Binding("10.0.0.1", 80));
- assertEquals(bindings[1], new Binding("10.0.0.2", 80));
- }
-
- @Test
- public void serializingPortWithMultipleBindings() throws Exception {
- Ports ports = new Ports();
- ports.bind(ExposedPort.tcp(80), new Binding("10.0.0.1", 80));
- ports.bind(ExposedPort.tcp(80), new Binding("10.0.0.2", 80));
- assertEquals(objectMapper.writeValueAsString(ports), jsonWithDoubleBindingForOnePort);
- }
-
-}
+package com.github.dockerjava.api.model;
+
+import static org.testng.Assert.assertEquals;
+
+import java.util.Map;
+
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.github.dockerjava.api.model.Ports.Binding;
+
+public class PortsTest {
+ private final ObjectMapper objectMapper = new ObjectMapper();
+ private final String jsonWithDoubleBindingForOnePort =
+ "{\"80/tcp\":[{\"HostIp\":\"10.0.0.1\",\"HostPort\":\"80\"},{\"HostIp\":\"10.0.0.2\",\"HostPort\":\"80\"}]}";
+
+ @Test
+ public void deserializingPortWithMultipleBindings() throws Exception {
+ Ports ports = objectMapper.readValue(jsonWithDoubleBindingForOnePort, Ports.class);
+ Map map = ports.getBindings();
+ assertEquals(map.size(), 1);
+
+ Binding[] bindings = map.get(ExposedPort.tcp(80));
+ assertEquals(bindings.length, 2);
+ assertEquals(bindings[0], new Binding("10.0.0.1", 80));
+ assertEquals(bindings[1], new Binding("10.0.0.2", 80));
+ }
+
+ @Test
+ public void serializingPortWithMultipleBindings() throws Exception {
+ Ports ports = new Ports();
+ ports.bind(ExposedPort.tcp(80), new Binding("10.0.0.1", 80));
+ ports.bind(ExposedPort.tcp(80), new Binding("10.0.0.2", 80));
+ assertEquals(objectMapper.writeValueAsString(ports), jsonWithDoubleBindingForOnePort);
+ }
+
+}
diff --git a/src/test/java/com/github/dockerjava/client/AbstractDockerClientTest.java b/src/test/java/com/github/dockerjava/client/AbstractDockerClientTest.java
index 385508b68..ed066b5fb 100644
--- a/src/test/java/com/github/dockerjava/client/AbstractDockerClientTest.java
+++ b/src/test/java/com/github/dockerjava/client/AbstractDockerClientTest.java
@@ -95,6 +95,7 @@ protected String asString(InputStream response) {
logwriter.write(line + (itr.hasNext() ? "\n" : ""));
//LOG.info("line: "+line);
}
+ response.close();
return logwriter.toString();
} catch (IOException e) {
diff --git a/src/test/java/com/github/dockerjava/core/DockerClientConfigTest.java b/src/test/java/com/github/dockerjava/core/DockerClientConfigTest.java
index b0747e68e..366cca319 100644
--- a/src/test/java/com/github/dockerjava/core/DockerClientConfigTest.java
+++ b/src/test/java/com/github/dockerjava/core/DockerClientConfigTest.java
@@ -15,7 +15,7 @@ public class DockerClientConfigTest {
public static final DockerClientConfig EXAMPLE_CONFIG = newExampleConfig();
private static DockerClientConfig newExampleConfig() {
- return new DockerClientConfig(URI.create("http://foo"), "bar", "baz", "qux", "blam", "flim", 877, false);
+ return new DockerClientConfig(URI.create("http://foo"), "bar", "baz", "qux", "blam", "flim", 877, false, 20, 2);
}
@Test
diff --git a/src/test/java/com/github/dockerjava/core/command/CopyFileFromContainerCmdImplTest.java b/src/test/java/com/github/dockerjava/core/command/CopyFileFromContainerCmdImplTest.java
index 945b61983..a4d81e4e3 100644
--- a/src/test/java/com/github/dockerjava/core/command/CopyFileFromContainerCmdImplTest.java
+++ b/src/test/java/com/github/dockerjava/core/command/CopyFileFromContainerCmdImplTest.java
@@ -4,6 +4,7 @@
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.client.AbstractDockerClientTest;
+import org.apache.commons.io.IOUtils;
import org.testng.ITestResult;
import org.testng.annotations.*;
@@ -49,8 +50,21 @@ public void copyFromContainer() throws Exception {
dockerClient.startContainerCmd(container.getId()).exec();
- InputStream response = dockerClient.copyFileFromContainerCmd(container.getId(), "/test").exec();
- assertTrue(response.available() > 0, "The file was not copied from the container.");
+ InputStream response = null;
+ try {
+ response = dockerClient.copyFileFromContainerCmd(container.getId(), "/test").exec();
+ boolean condition = response.available() > 0;
+
+ // read the stream fully. Otherwise, the underlying stream will not be closed.
+ for(String line:IOUtils.readLines(response)) {
+ assertThat("", null != line);
+ }
+
+ assertTrue(condition, "The file was not copied from the container.");
+ } finally {
+ System.out.println("Close copyFromContainer stream.");
+ response.close();
+ }
}
@Test
diff --git a/src/test/java/com/github/dockerjava/core/command/EventsCmdImplTest.java b/src/test/java/com/github/dockerjava/core/command/EventsCmdImplTest.java
index 6935227bc..8c068b6df 100644
--- a/src/test/java/com/github/dockerjava/core/command/EventsCmdImplTest.java
+++ b/src/test/java/com/github/dockerjava/core/command/EventsCmdImplTest.java
@@ -19,6 +19,7 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
@Test(groups = "integration")
public class EventsCmdImplTest extends AbstractDockerClientTest {
@@ -78,7 +79,7 @@ public void testEventStreaming() throws InterruptedException, IOException {
TimeUnit.SECONDS.sleep(1);
CountDownLatch countDownLatch = new CountDownLatch(KNOWN_NUM_EVENTS);
- EventCallback eventCallback = new EventCallbackTest(countDownLatch);
+ EventCallbackTest eventCallback = new EventCallbackTest(countDownLatch);
EventsCmd eventsCmd = dockerClient.eventsCmd(eventCallback).withSince(getEpochTime());
ExecutorService executorService = eventsCmd.exec();
@@ -87,6 +88,7 @@ public void testEventStreaming() throws InterruptedException, IOException {
boolean zeroCount = countDownLatch.await(5, TimeUnit.SECONDS);
executorService.shutdown();
+ eventCallback.close();
assertTrue(zeroCount, "Expected 4 events, [create, start, die, stop]");
}
@@ -105,12 +107,17 @@ private int generateEvents() {
private class EventCallbackTest implements EventCallback {
private final CountDownLatch countDownLatch;
+ private final AtomicBoolean isReceiving = new AtomicBoolean(true);
public EventCallbackTest(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
- @Override
+ public void close() {
+ isReceiving.set(false);
+ }
+
+ @Override
public void onEvent(Event event) {
LOG.info("Received event #{}: {}", countDownLatch.getCount(), event);
countDownLatch.countDown();
@@ -125,5 +132,10 @@ public void onException(Throwable throwable) {
public void onCompletion(int numEvents) {
LOG.info("Number of events received: {}", numEvents);
}
+
+ @Override
+ public boolean isReceiving() {
+ return isReceiving.get();
+ }
}
}
diff --git a/src/test/java/com/github/dockerjava/core/command/PullImageCmdImplTest.java b/src/test/java/com/github/dockerjava/core/command/PullImageCmdImplTest.java
index 84af9e72d..7f0e146c1 100644
--- a/src/test/java/com/github/dockerjava/core/command/PullImageCmdImplTest.java
+++ b/src/test/java/com/github/dockerjava/core/command/PullImageCmdImplTest.java
@@ -97,7 +97,9 @@ public void testPullImage() throws DockerException, IOException {
public void testPullNonExistingImage() throws DockerException, IOException {
// does not throw an exception
- dockerClient.pullImageCmd("nonexisting/foo").exec();
+ InputStream is = dockerClient.pullImageCmd("nonexisting/foo").exec();
+ // stream needs to be fully read in order to close the underlying connection
+ this.asString(is);
try {
dockerClient.pullImageCmd("non-existing/foo").exec();
diff --git a/src/test/java/com/github/dockerjava/core/command/PushImageCmdImplTest.java b/src/test/java/com/github/dockerjava/core/command/PushImageCmdImplTest.java
index b12ac4ec2..ecafbb78e 100644
--- a/src/test/java/com/github/dockerjava/core/command/PushImageCmdImplTest.java
+++ b/src/test/java/com/github/dockerjava/core/command/PushImageCmdImplTest.java
@@ -77,6 +77,5 @@ public void pushExistentImage() throws Exception {
assertThat(asString(dockerClient.pushImageCmd(username + "/xxx").exec()), containsString("error"));
}
-
}