Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
a3d2b7c
Draft DockerHttpClient abstraction
bsideup Mar 19, 2020
03e8469
fix checkstyle issues
bsideup Mar 19, 2020
d018fe4
fix checkstyle issues (2)
bsideup Mar 19, 2020
c3ab8e0
Fix CmdIT
bsideup Mar 19, 2020
e562fec
Introduce `OkDockerHttpClient.Factory` and restore backward compatibi…
bsideup Mar 20, 2020
3223d54
fix FramedInputStreamConsumer
bsideup Mar 20, 2020
c584ac2
Add `Response#getHeader(String)`
bsideup Mar 20, 2020
a8c38da
test improvements
bsideup Mar 20, 2020
7dfab44
restore `withConnectTimeout`
bsideup Mar 20, 2020
b08ba85
fix code style in `FramedInputStreamConsumer`
bsideup Mar 20, 2020
e9fdc65
Add JerseyDockerHttpClient
bsideup Mar 20, 2020
30cfcac
checkstyle...
bsideup Mar 20, 2020
1d6b691
Add Apache HttpClient5-based transport
bsideup Apr 3, 2020
95d872a
Merge branch 'master' into httpclient5_transport
bsideup Apr 4, 2020
6ce0ae4
Merge branch 'master' into httpclient5_transport
bsideup Apr 4, 2020
d5773b3
fix japicmp
bsideup Apr 4, 2020
6df9e45
avoid double closing
bsideup Apr 4, 2020
440c455
checkstyle...
bsideup Apr 4, 2020
55e8222
close but catch exceptions
bsideup Apr 4, 2020
8cb26c8
checkstyle.........
bsideup Apr 4, 2020
c67e8fa
temporarily run only HttpClient's tests
bsideup Apr 4, 2020
229fabb
httpclient5 + okhttp
bsideup Apr 4, 2020
563e8a6
all but okhttp
bsideup Apr 4, 2020
6558a6f
fix OkHttp transport
bsideup Apr 4, 2020
3c91c92
revert okhttp changes
bsideup Apr 4, 2020
31d3f40
Debug OkHttp
bsideup Apr 5, 2020
338026f
Remove CLOSING
bsideup Apr 5, 2020
6f0be01
fix compilation
bsideup Apr 5, 2020
bc25896
checkstyle..........
bsideup Apr 5, 2020
04ff5c9
do not treat 0 as -1
bsideup Apr 5, 2020
fbb35e7
log the error code
bsideup Apr 5, 2020
d61e1c0
[debug] gracefully handle "Connection reset by peer"
bsideup Apr 5, 2020
f9589d5
Add EOF check
bsideup Apr 5, 2020
f1618f3
Revert OkHttp changes
bsideup Apr 5, 2020
c9d6171
Revert more OkHttp changes
bsideup Apr 5, 2020
540a58e
revert more...
bsideup Apr 5, 2020
72af8c6
full revert
bsideup Apr 5, 2020
7e15cb4
remove docker-java-transport-common for now
bsideup Apr 5, 2020
0242b81
checkstyle...
bsideup Apr 5, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions docker-java-transport-httpclient5/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.github.docker-java</groupId>
<artifactId>docker-java-parent</artifactId>
<version>3.2.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

<artifactId>docker-java-transport-httpclient5</artifactId>
<packaging>jar</packaging>

<name>docker-java-transport-httpclient5</name>
<url>https://github.com/docker-java/docker-java</url>
<description>Java API Client for Docker</description>

<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>docker-java-core</artifactId>
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.httpcomponents.core5</groupId>
<artifactId>httpcore5-h2</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>5.5.0</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>com.github.siom79.japicmp</groupId>
<artifactId>japicmp-maven-plugin</artifactId>
<configuration>
<!-- TODO remove once this module is released -->
<skip>true</skip>
</configuration>
</plugin>

<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<Export-Package>com.github.dockerjava.httpclient5.*</Export-Package>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
package com.github.dockerjava.httpclient5;

import com.github.dockerjava.core.DockerClientConfig;
import com.github.dockerjava.core.DockerHttpClient;
import com.github.dockerjava.core.SSLConfig;
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.ManagedHttpClientConnectionFactory;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
import org.apache.hc.core5.http.ConnectionClosedException;
import org.apache.hc.core5.http.ContentLengthStrategy;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.NameValuePair;
import org.apache.hc.core5.http.config.Registry;
import org.apache.hc.core5.http.config.RegistryBuilder;
import org.apache.hc.core5.http.impl.DefaultContentLengthStrategy;
import org.apache.hc.core5.http.impl.io.EmptyInputStream;
import org.apache.hc.core5.http.io.entity.InputStreamEntity;
import org.apache.hc.core5.http.protocol.BasicHttpContext;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.net.URIAuthority;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.net.URI;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public final class ApacheDockerHttpClient implements DockerHttpClient {

public static final class Factory {

private DockerClientConfig dockerClientConfig = null;

public Factory dockerClientConfig(DockerClientConfig value) {
this.dockerClientConfig = value;
return this;
}

public ApacheDockerHttpClient build() {
Objects.requireNonNull(dockerClientConfig, "dockerClientConfig");
return new ApacheDockerHttpClient(dockerClientConfig);
}
}

private final CloseableHttpClient httpClient;

private final HttpHost host;

private ApacheDockerHttpClient(DockerClientConfig dockerClientConfig) {
Registry<ConnectionSocketFactory> socketFactoryRegistry = createConnectionSocketFactoryRegistry(dockerClientConfig);

URI dockerHost = dockerClientConfig.getDockerHost();

switch (dockerHost.getScheme()) {
case "unix":
case "npipe":
host = new HttpHost(dockerHost.getScheme(), "localhost", 2375);
break;
case "tcp":
host = new HttpHost(
socketFactoryRegistry.lookup("https") != null ? "https" : "http",
dockerHost.getHost(),
dockerHost.getPort()
);
break;
default:
host = HttpHost.create(dockerHost);
}

httpClient = HttpClients.custom()
.setRequestExecutor(new HijackingHttpRequestExecutor(null))
.setConnectionManager(new PoolingHttpClientConnectionManager(
socketFactoryRegistry,
new ManagedHttpClientConnectionFactory(
null,
null,
null,
null,
message -> {
Header transferEncodingHeader = message.getFirstHeader(HttpHeaders.TRANSFER_ENCODING);
if (transferEncodingHeader != null) {
if ("identity".equalsIgnoreCase(transferEncodingHeader.getValue())) {
return ContentLengthStrategy.UNDEFINED;
}
}
return DefaultContentLengthStrategy.INSTANCE.determineLength(message);
},
null
)
))
.build();
}

private Registry<ConnectionSocketFactory> createConnectionSocketFactoryRegistry(DockerClientConfig dockerClientConfig) {
RegistryBuilder<ConnectionSocketFactory> socketFactoryRegistryBuilder = RegistryBuilder.create();

SSLConfig sslConfig = dockerClientConfig.getSSLConfig();
if (sslConfig != null) {
try {
SSLContext sslContext = sslConfig.getSSLContext();
if (sslContext != null) {
socketFactoryRegistryBuilder.register("https", new SSLConnectionSocketFactory(sslContext));
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}

return socketFactoryRegistryBuilder
.register("tcp", PlainConnectionSocketFactory.INSTANCE)
.register("http", PlainConnectionSocketFactory.INSTANCE)
.register("unix", new PlainConnectionSocketFactory() {
@Override
public Socket createSocket(HttpContext context) throws IOException {
URI dockerHost = dockerClientConfig.getDockerHost();

return new UnixDomainSocket(dockerHost.getPath());
}
})
.register("npipe", new PlainConnectionSocketFactory() {
@Override
public Socket createSocket(HttpContext context) {
URI dockerHost = dockerClientConfig.getDockerHost();

return new NamedPipeSocket(dockerHost.getPath());
}
})
.build();
}

@Override
public Response execute(Request request) {
HttpContext context = new BasicHttpContext();
HttpUriRequestBase httpUriRequest = new HttpUriRequestBase(request.method(), URI.create(request.path()));
httpUriRequest.setScheme(host.getSchemeName());
httpUriRequest.setAuthority(new URIAuthority(host.getHostName(), host.getPort()));

request.headers().forEach(httpUriRequest::addHeader);

InputStream body = request.body();
if (body != null) {
httpUriRequest.setEntity(new InputStreamEntity(body, null));
}

if (request.hijackedInput() != null) {
context.setAttribute(HijackingHttpRequestExecutor.HIJACKED_INPUT_ATTRIBUTE, request.hijackedInput());
httpUriRequest.setHeader("Upgrade", "tcp");
httpUriRequest.setHeader("Connection", "Upgrade");
}

try {
CloseableHttpResponse response = httpClient.execute(host, httpUriRequest, context);

return new ApacheResponse(httpUriRequest, response);
} catch (IOException e) {
throw new RuntimeException(e);
}
}

@Override
public void close() throws IOException {
httpClient.close();
}

static class ApacheResponse implements Response {

private static final Logger LOGGER = LoggerFactory.getLogger(ApacheResponse.class);

private final HttpUriRequestBase request;

private final CloseableHttpResponse response;

ApacheResponse(HttpUriRequestBase httpUriRequest, CloseableHttpResponse response) {
this.request = httpUriRequest;
this.response = response;
}

@Override
public int getStatusCode() {
return response.getCode();
}

@Override
public Map<String, List<String>> getHeaders() {
return Stream.of(response.getHeaders()).collect(Collectors.groupingBy(
NameValuePair::getName,
Collectors.mapping(NameValuePair::getValue, Collectors.toList())
));
}

@Override
public String getHeader(String name) {
Header firstHeader = response.getFirstHeader(name);
return firstHeader != null ? firstHeader.getValue() : null;
}

@Override
public InputStream getBody() {
try {
return response.getEntity() != null
? response.getEntity().getContent()
: EmptyInputStream.INSTANCE;
} catch (IOException e) {
throw new RuntimeException(e);
}
}

@Override
public void close() {
try {
request.abort();
} catch (Exception e) {
LOGGER.debug("Failed to abort the request", e);
}

try {
response.close();
} catch (ConnectionClosedException e) {
LOGGER.trace("Failed to close the response", e);
} catch (Exception e) {
LOGGER.debug("Failed to close the response", e);
}
}
}
}
Loading