Skip to content

Commit a356f66

Browse files
committed
Shutdown hijacked stdin
Fixes #1448
1 parent 5f416a7 commit a356f66

File tree

4 files changed

+84
-0
lines changed

4 files changed

+84
-0
lines changed

docker-java-transport-httpclient5/src/main/java/com/github/dockerjava/httpclient5/HijackingHttpRequestExecutor.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.github.dockerjava.httpclient5;
22

3+
import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
34
import org.apache.hc.core5.http.ClassicHttpRequest;
45
import org.apache.hc.core5.http.ClassicHttpResponse;
56
import org.apache.hc.core5.http.ConnectionReuseStrategy;
@@ -80,6 +81,9 @@ private ClassicHttpResponse executeHijacked(
8081
fakeRequest.setHeader(HttpHeaders.CONTENT_LENGTH, Long.MAX_VALUE);
8182
fakeRequest.setEntity(new HijackedEntity(hijackedInput));
8283
conn.sendRequestEntity(fakeRequest);
84+
if (conn instanceof ManagedHttpClientConnection) {
85+
((ManagedHttpClientConnection) conn).getSocket().shutdownOutput();
86+
}
8387
} catch (Exception e) {
8488
throw new RuntimeException(e);
8589
}

docker-java-transport-okhttp/src/main/java/com/github/dockerjava/okhttp/HijackingInterceptor.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public Response intercept(Chain chain) throws IOException {
4949
sink.writeByte(aByte);
5050
sink.emit();
5151
}
52+
exchange.connection().socket().shutdownOutput();
5253
} catch (Exception e) {
5354
throw new RuntimeException(e);
5455
}

docker-java-transport/src/main/java/com/github/dockerjava/transport/DomainSocket.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,32 @@ public OutputStream getOutputStream() {
105105
return this.outputStream;
106106
}
107107

108+
@Override
109+
public void shutdownInput() throws IOException {
110+
try (Handle handle = this.fileDescriptor.acquire()) {
111+
if (!handle.isClosed()) {
112+
try {
113+
shutdown(handle.intValue(), SHUT_RD);
114+
} catch (LastErrorException ex) {
115+
throw new IOException(ex);
116+
}
117+
}
118+
}
119+
}
120+
121+
@Override
122+
public void shutdownOutput() throws IOException {
123+
try (Handle handle = this.fileDescriptor.acquire()) {
124+
if (!handle.isClosed()) {
125+
try {
126+
shutdown(handle.intValue(), SHUT_WR);
127+
} catch (LastErrorException ex) {
128+
throw new IOException(ex);
129+
}
130+
}
131+
}
132+
}
133+
108134
@Override
109135
public void close() throws IOException {
110136
super.close();
@@ -123,6 +149,8 @@ public void close() throws IOException {
123149

124150
private native int write(int fd, ByteBuffer buffer, int count) throws LastErrorException;
125151

152+
private native int shutdown(int fd, int how) throws LastErrorException;
153+
126154
private native int close(int fd) throws LastErrorException;
127155

128156
/**

docker-java/src/test/java/com/github/dockerjava/cmd/AttachContainerCmdIT.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import java.io.InputStream;
1818
import java.io.PipedInputStream;
1919
import java.io.PipedOutputStream;
20+
import java.nio.charset.StandardCharsets;
2021
import java.util.concurrent.CountDownLatch;
2122
import java.util.concurrent.TimeUnit;
2223

@@ -281,6 +282,56 @@ public void onComplete() {
281282
}
282283
}
283284

285+
/**
286+
* The process in Docker container should receive EOF when reading its
287+
* stdin after the corresponding stream is closed on docker-java side in
288+
* case when the container has been created with StdinOnce flag set to
289+
* true.
290+
*/
291+
@Test
292+
public void closeStdinWithStdinOnce() throws Exception {
293+
Assume.assumeTrue("supports stdin attach", getFactoryType().supportsStdinAttach());
294+
295+
DockerClient dockerClient = dockerRule.getClient();
296+
297+
CreateContainerResponse container = dockerClient.createContainerCmd(DEFAULT_IMAGE)
298+
.withStdinOpen(true)
299+
.withStdInOnce(true)
300+
.withCmd("sh", "-c", "read -r line; echo Done")
301+
.exec();
302+
303+
CountDownLatch done = new CountDownLatch(1);
304+
PipedOutputStream stdin = new PipedOutputStream();
305+
306+
try (
307+
ResultCallback.Adapter<Frame> resultCallback = dockerClient.attachContainerCmd(container.getId())
308+
.withStdIn(new PipedInputStream(stdin))
309+
.withStdOut(true)
310+
.withFollowStream(true)
311+
.exec(new ResultCallback.Adapter<Frame>() {
312+
@Override
313+
public void onNext(Frame frame) {
314+
if (frame.toString().contains("Done")) {
315+
done.countDown();
316+
}
317+
}
318+
})
319+
) {
320+
resultCallback.awaitStarted(5, SECONDS);
321+
dockerClient.startContainerCmd(container.getId()).exec();
322+
323+
stdin.write("Hello".getBytes(StandardCharsets.UTF_8));
324+
stdin.close();
325+
326+
assertTrue("Should detect closed stdin", done.await(5, SECONDS));
327+
328+
assertTrue(
329+
"The script should finish quickly after stdin is closed on docker-java side",
330+
resultCallback.awaitCompletion(5, SECONDS)
331+
);
332+
}
333+
}
334+
284335
public static class AttachContainerTestCallback extends ResultCallback.Adapter<Frame> {
285336
private final StringBuffer log = new StringBuffer();
286337

0 commit comments

Comments
 (0)