Skip to content

Commit 217c14b

Browse files
authored
Add OkHttp transport (#1284)
This is a backport of the OkHttp transport from http://github.com/testcontainers/testcontainers-java OkHttp provides a lightweight API, supports the same feature set as Netty does but also has Windows Npipe support since it can use Java's Socket abstraction.
1 parent 212b072 commit 217c14b

24 files changed

+1422
-65
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<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">
2+
<modelVersion>4.0.0</modelVersion>
3+
4+
<parent>
5+
<groupId>com.github.docker-java</groupId>
6+
<artifactId>docker-java-parent</artifactId>
7+
<version>3.2.0-SNAPSHOT</version>
8+
<relativePath>../pom.xml</relativePath>
9+
</parent>
10+
11+
<artifactId>docker-java-transport-okhttp</artifactId>
12+
<packaging>bundle</packaging>
13+
14+
<name>docker-java-transport-okhttp</name>
15+
<url>https://github.com/docker-java/docker-java</url>
16+
<description>Java API Client for Docker</description>
17+
18+
<dependencies>
19+
<dependency>
20+
<groupId>${groupId}</groupId>
21+
<artifactId>docker-java-core</artifactId>
22+
<version>${version}</version>
23+
</dependency>
24+
25+
<dependency>
26+
<groupId>com.squareup.okhttp3</groupId>
27+
<artifactId>okhttp</artifactId>
28+
<version>3.14.4</version>
29+
</dependency>
30+
31+
<dependency>
32+
<groupId>net.java.dev.jna</groupId>
33+
<artifactId>jna-platform</artifactId>
34+
<version>5.4.0</version>
35+
</dependency>
36+
</dependencies>
37+
38+
<build>
39+
<plugins>
40+
<plugin>
41+
<groupId>org.apache.felix</groupId>
42+
<artifactId>maven-bundle-plugin</artifactId>
43+
<extensions>true</extensions>
44+
<configuration>
45+
<instructions>
46+
<Export-Package>com.github.dockerjava.okhttp.*</Export-Package>
47+
</instructions>
48+
</configuration>
49+
</plugin>
50+
</plugins>
51+
</build>
52+
</project>
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package com.github.dockerjava.okhttp;
2+
3+
import com.github.dockerjava.api.async.ResultCallback;
4+
import com.github.dockerjava.api.model.Frame;
5+
import com.github.dockerjava.api.model.StreamType;
6+
import okio.BufferedSource;
7+
8+
import java.nio.ByteBuffer;
9+
import java.util.Arrays;
10+
import java.util.function.Consumer;
11+
12+
class FramedSink implements Consumer<BufferedSource> {
13+
14+
private static final int HEADER_SIZE = 8;
15+
16+
private final ResultCallback<Frame> resultCallback;
17+
18+
FramedSink(ResultCallback<Frame> resultCallback) {
19+
this.resultCallback = resultCallback;
20+
}
21+
22+
@Override
23+
public void accept(BufferedSource source) {
24+
try {
25+
while (!source.exhausted()) {
26+
// See https://docs.docker.com/engine/api/v1.37/#operation/ContainerAttach
27+
// [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4}[]byte{OUTPUT}
28+
29+
if (!source.request(HEADER_SIZE)) {
30+
return;
31+
}
32+
byte[] bytes = source.readByteArray(HEADER_SIZE);
33+
34+
StreamType streamType = streamType(bytes[0]);
35+
36+
if (streamType == StreamType.RAW) {
37+
resultCallback.onNext(new Frame(StreamType.RAW, bytes));
38+
byte[] buffer = new byte[1024];
39+
while (!source.exhausted()) {
40+
int readBytes = source.read(buffer);
41+
if (readBytes != -1) {
42+
resultCallback.onNext(new Frame(StreamType.RAW, Arrays.copyOf(buffer, readBytes)));
43+
}
44+
}
45+
return;
46+
}
47+
48+
int payloadSize = ByteBuffer.wrap(bytes, 4, 4).getInt();
49+
if (!source.request(payloadSize)) {
50+
return;
51+
}
52+
byte[] payload = source.readByteArray(payloadSize);
53+
54+
resultCallback.onNext(new Frame(streamType, payload));
55+
}
56+
} catch (Exception e) {
57+
resultCallback.onError(e);
58+
}
59+
}
60+
61+
private static StreamType streamType(byte streamType) {
62+
switch (streamType) {
63+
case 0:
64+
return StreamType.STDIN;
65+
case 1:
66+
return StreamType.STDOUT;
67+
case 2:
68+
return StreamType.STDERR;
69+
default:
70+
return StreamType.RAW;
71+
}
72+
}
73+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package com.github.dockerjava.okhttp;
2+
3+
import com.sun.jna.platform.win32.Kernel32;
4+
5+
import javax.net.SocketFactory;
6+
import java.io.FileNotFoundException;
7+
import java.io.IOException;
8+
import java.io.InputStream;
9+
import java.io.OutputStream;
10+
import java.io.RandomAccessFile;
11+
import java.net.InetAddress;
12+
import java.net.Socket;
13+
import java.net.SocketAddress;
14+
15+
class NamedPipeSocketFactory extends SocketFactory {
16+
17+
final String socketFileName;
18+
19+
NamedPipeSocketFactory(String socketFileName) {
20+
this.socketFileName = socketFileName;
21+
}
22+
23+
@Override
24+
public Socket createSocket() {
25+
return new Socket() {
26+
27+
RandomAccessFile file;
28+
InputStream is;
29+
OutputStream os;
30+
31+
@Override
32+
public void close() throws IOException {
33+
if (file != null) {
34+
file.close();
35+
file = null;
36+
}
37+
}
38+
39+
@Override
40+
public void connect(SocketAddress endpoint) {
41+
connect(endpoint, 0);
42+
}
43+
44+
@Override
45+
public void connect(SocketAddress endpoint, int timeout) {
46+
long startedAt = System.currentTimeMillis();
47+
timeout = Math.max(timeout, 10_000);
48+
while (true) {
49+
try {
50+
file = new RandomAccessFile(socketFileName, "rw");
51+
break;
52+
} catch (FileNotFoundException e) {
53+
if (System.currentTimeMillis() - startedAt >= timeout) {
54+
throw new RuntimeException(e);
55+
} else {
56+
Kernel32.INSTANCE.WaitNamedPipe(socketFileName, 100);
57+
}
58+
}
59+
}
60+
61+
is = new InputStream() {
62+
@Override
63+
public int read(byte[] bytes, int off, int len) throws IOException {
64+
if (OkHttpInvocationBuilder.CLOSING.get()) {
65+
return 0;
66+
}
67+
return file.read(bytes, off, len);
68+
}
69+
70+
@Override
71+
public int read() throws IOException {
72+
if (OkHttpInvocationBuilder.CLOSING.get()) {
73+
return 0;
74+
}
75+
return file.read();
76+
}
77+
78+
@Override
79+
public int read(byte[] bytes) throws IOException {
80+
if (OkHttpInvocationBuilder.CLOSING.get()) {
81+
return 0;
82+
}
83+
return file.read(bytes);
84+
}
85+
};
86+
87+
os = new OutputStream() {
88+
@Override
89+
public void write(byte[] bytes, int off, int len) throws IOException {
90+
file.write(bytes, off, len);
91+
}
92+
93+
@Override
94+
public void write(int value) throws IOException {
95+
file.write(value);
96+
}
97+
98+
@Override
99+
public void write(byte[] bytes) throws IOException {
100+
file.write(bytes);
101+
}
102+
};
103+
}
104+
105+
@Override
106+
public InputStream getInputStream() {
107+
return is;
108+
}
109+
110+
@Override
111+
public OutputStream getOutputStream() {
112+
return os;
113+
}
114+
};
115+
}
116+
117+
@Override
118+
public Socket createSocket(String s, int i) {
119+
throw new UnsupportedOperationException();
120+
}
121+
122+
@Override
123+
public Socket createSocket(String s, int i, InetAddress inetAddress, int i1) {
124+
throw new UnsupportedOperationException();
125+
}
126+
127+
@Override
128+
public Socket createSocket(InetAddress inetAddress, int i) {
129+
throw new UnsupportedOperationException();
130+
}
131+
132+
@Override
133+
public Socket createSocket(InetAddress inetAddress, int i, InetAddress inetAddress1, int i1) {
134+
throw new UnsupportedOperationException();
135+
}
136+
}

0 commit comments

Comments
 (0)