-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Apache HttpClient 5 transport (#1358)
Adds a new, Apache HttpClient 5 based transport (without Jersey) that implements `DockerHttpClient` and supports input hijacking
- Loading branch information
Showing
8 changed files
with
925 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
This comment has been minimized.
Sorry, something went wrong. |
||
<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> |
240 changes: 240 additions & 0 deletions
240
...t-httpclient5/src/main/java/com/github/dockerjava/httpclient5/ApacheDockerHttpClient.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.
What is this exclusion for? I'm getting