Skip to content

Commit 9d17a47

Browse files
committed
Merge branch '__rultor'
2 parents 3626a97 + 6811bb4 commit 9d17a47

File tree

4 files changed

+269
-24
lines changed

4 files changed

+269
-24
lines changed
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
/**
2+
* Copyright (c) 2018-2019, Mihai Emil Andronache
3+
* All rights reserved.
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are met:
6+
* 1)Redistributions of source code must retain the above copyright notice,
7+
* this list of conditions and the following disclaimer.
8+
* 2)Redistributions in binary form must reproduce the above copyright notice,
9+
* this list of conditions and the following disclaimer in the documentation
10+
* and/or other materials provided with the distribution.
11+
* 3)Neither the name of docker-java-api nor the names of its
12+
* contributors may be used to endorse or promote products derived from
13+
* this software without specific prior written permission.
14+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
18+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24+
* POSSIBILITY OF SUCH DAMAGE.
25+
*/
26+
package com.amihaiemil.docker;
27+
28+
import org.apache.http.HttpEntity;
29+
import org.apache.http.HttpResponse;
30+
import org.apache.http.client.ResponseHandler;
31+
import org.apache.http.entity.ContentType;
32+
import org.apache.http.protocol.HTTP;
33+
import org.apache.http.util.Args;
34+
import org.apache.http.util.CharArrayBuffer;
35+
36+
import java.io.IOException;
37+
import java.io.InputStream;
38+
import java.io.InputStreamReader;
39+
import java.io.Reader;
40+
import java.nio.charset.Charset;
41+
42+
/**
43+
* Handler that returns the response content as a String.
44+
* @author Morozov Evgeniy ([email protected])
45+
* @version $Id$
46+
* @since 0.0.2
47+
*/
48+
final class ReadLogString implements ResponseHandler<String> {
49+
50+
/**
51+
* Handlers to be executed before actually reading the array.
52+
*/
53+
private final ResponseHandler<HttpResponse> other;
54+
55+
/**
56+
* Ctor.
57+
* @param other Handlers to be executed before actually reading the array.
58+
*/
59+
ReadLogString(final ResponseHandler<HttpResponse> other) {
60+
this.other = other;
61+
}
62+
63+
@Override
64+
public String handleResponse(final HttpResponse httpResponse)
65+
throws IOException {
66+
final HttpResponse resp = this.other.handleResponse(httpResponse);
67+
return this.toString(resp.getEntity());
68+
}
69+
70+
/**
71+
* Docker logs contains header
72+
* [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4}
73+
* STREAM_TYPE
74+
* 0: stdin (is written on stdout)
75+
* 1: stdout
76+
* 2: stderr
77+
*
78+
* SIZE1, SIZE2, SIZE3, SIZE4 are the four bytes of the uint32 size
79+
* encoded as big endian.
80+
*
81+
* This method do:
82+
*
83+
* 1) Read 8 bytes.
84+
* 2) Choose stdout or stderr depending on the first byte.
85+
* 3) Extract the frame size from the last four bytes.
86+
* 4) Read the extracted size and output it on the correct output.
87+
* 5) Goto 1.
88+
*
89+
* @param entity HttpEntity for read message.
90+
* @return Logs from container in String.
91+
* @throws IOException if the entity cannot be read
92+
*/
93+
private String toString(final HttpEntity entity) throws IOException {
94+
final InputStream instream = entity.getContent();
95+
final CharArrayBuffer buffer = new CharArrayBuffer(
96+
this.getCapacity(entity)
97+
);
98+
if (instream != null) {
99+
try {
100+
final Reader reader = new InputStreamReader(
101+
instream,
102+
this.getCharset(ContentType.get(entity))
103+
);
104+
this.read(buffer, reader);
105+
} finally {
106+
instream.close();
107+
}
108+
}
109+
return buffer.toString();
110+
}
111+
112+
/**
113+
* Docker logs contains header
114+
* [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4}
115+
* STREAM_TYPE
116+
* 0: stdin (is written on stdout)
117+
* 1: stdout
118+
* 2: stderr
119+
*
120+
* SIZE1, SIZE2, SIZE3, SIZE4 are the four bytes of the uint32 size
121+
* encoded as big endian.
122+
*
123+
* 1) Read 8 bytes from reader.
124+
* 2) Choose not stdin(0) depending on the first byte.
125+
* 3) Extract the frame size from the last four bytes.
126+
* 4) Read the extracted size from reader and save it in buffer in circle.
127+
* 5) Goto 1.
128+
*
129+
* @param buffer Buffer for save message.
130+
* @param reader Reader for read message.
131+
* @throws IOException if the entity cannot be read
132+
*/
133+
private void read(final CharArrayBuffer buffer,
134+
final Reader reader) throws IOException {
135+
char[] controlChars = new char[8];
136+
int len;
137+
while (reader.read(controlChars) != -1) {
138+
if (controlChars[0] != 0) {
139+
long byteInLine = this.getUInt(controlChars);
140+
char[] stdout;
141+
if (byteInLine > 1024) {
142+
stdout = new char[1024];
143+
} else {
144+
stdout = new char[(int) byteInLine];
145+
}
146+
while (byteInLine > 0) {
147+
len = reader.read(stdout);
148+
byteInLine -= len;
149+
if (len != -1) {
150+
buffer.append(stdout, 0, len);
151+
}
152+
}
153+
}
154+
}
155+
}
156+
157+
/**
158+
* Check that content length less then Integer.MAX_VALUE.
159+
* Try to get content length from entity
160+
* If length less then zero return 4096
161+
*
162+
* @param entity HttpEntity for get capacity.
163+
* @return Capacity.
164+
*/
165+
private int getCapacity(final HttpEntity entity) {
166+
Args.check(entity.getContentLength() <= Integer.MAX_VALUE,
167+
"HTTP entity too large to be buffered in memory");
168+
int capacity = (int) entity.getContentLength();
169+
if (capacity < 0) {
170+
capacity = 4096;
171+
}
172+
return capacity;
173+
}
174+
175+
/**
176+
* Try to get charset from content type.
177+
* If charset not set, try get default charset by mime type
178+
* If not set return ISO-8859-1
179+
*
180+
* @param contentType Content type.
181+
* @return Charset.
182+
*/
183+
private Charset getCharset(final ContentType contentType) {
184+
Charset charset = null;
185+
if (contentType != null) {
186+
charset = contentType.getCharset();
187+
if (charset == null) {
188+
ContentType defaultContentType =
189+
ContentType.getByMimeType(contentType.getMimeType());
190+
if (defaultContentType != null) {
191+
charset = defaultContentType.getCharset();
192+
} else {
193+
charset = null;
194+
}
195+
}
196+
}
197+
198+
if (charset == null) {
199+
charset = HTTP.DEF_CONTENT_CHARSET;
200+
}
201+
return charset;
202+
}
203+
204+
/**
205+
* Convert byte to long.
206+
*
207+
* @param byteForConvert Byte for convert.
208+
* @return Long Converted byte.
209+
*/
210+
private long byteAsULong(final char byteForConvert) {
211+
return ((long) byteForConvert) & 0x00000000000000FFL;
212+
}
213+
214+
/**
215+
* Convert byte from control byte to uint32.
216+
*
217+
* @param bytes Array of byte.
218+
* @return Long uint32.
219+
*/
220+
private long getUInt(final char[] bytes) {
221+
return this.byteAsULong(bytes[7])
222+
| (this.byteAsULong(bytes[6]) << 8)
223+
| (this.byteAsULong(bytes[5]) << 16)
224+
| (this.byteAsULong(bytes[4]) << 24);
225+
}
226+
227+
228+
}

src/main/java/com/amihaiemil/docker/RtLogs.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public String fetch() throws IOException, UnexpectedResponseException {
110110
try {
111111
return this.client.execute(
112112
fetch,
113-
new ReadString(
113+
new ReadLogString(
114114
new MatchStatus(
115115
fetch.getURI(),
116116
HttpStatus.SC_OK

src/test/java/com/amihaiemil/docker/RtLogsITCase.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ public void fetchesLogs() throws Exception {
5656
).images().pull("hello-world", "latest").run();
5757
final String logs = container.logs().fetch();
5858
MatcherAssert.assertThat(
59-
logs.trim(),
60-
new StringStartsWith("Hello from Docker!")
59+
logs,
60+
new StringStartsWith("\nHello from Docker!")
6161
);
6262
}
6363

src/test/java/com/amihaiemil/docker/RtLogsTestCase.java

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public void followsLogs() throws Exception {
9999
);
100100
}
101101
}
102-
102+
103103
/**
104104
* RtLogs can fetch the Container's logs (return them as a String).
105105
* @throws Exception If something goes wrong.
@@ -109,12 +109,8 @@ public void fetchesLogs() throws Exception {
109109
final Logs logs = new RtLogs(
110110
Mockito.mock(Container.class),
111111
new AssertRequest(
112-
new Response(
113-
HttpStatus.SC_OK,
114-
Json.createObjectBuilder()
115-
.add("logs", "...fetched logs...")
116-
.build().toString()
117-
),
112+
new Response(HttpStatus.SC_OK,
113+
this.prepareMessage("...fetched logs...")),
118114
new Condition(
119115
"Method should be a GET",
120116
req -> req.getRequestLine().getMethod().equals("GET")
@@ -130,10 +126,37 @@ public void fetchesLogs() throws Exception {
130126
);
131127
MatcherAssert.assertThat(
132128
logs.fetch(),
133-
Matchers.equalTo("{\"logs\":\"...fetched logs...\"}")
129+
Matchers.equalTo("...fetched logs...")
134130
);
135131
}
136-
132+
133+
/**
134+
* Docker logs contains header -
135+
* header := [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4}
136+
* STREAM_TYPE
137+
* 0: stdin (is written on stdout)
138+
* 1: stdout
139+
* 2: stderr
140+
*
141+
* SIZE1, SIZE2, SIZE3, SIZE4 are the four bytes of the uint32 size
142+
* encoded as big endian.
143+
*
144+
* @param message Message from container
145+
* @return String with header.
146+
*/
147+
private String prepareMessage(final String message) {
148+
char[] chars = new char[8];
149+
chars[0] = 1;
150+
chars[1] = 0;
151+
chars[2] = 0;
152+
chars[3] = 0;
153+
chars[4] = 0;
154+
chars[5] = 0;
155+
chars[6] = 0;
156+
chars[7] = (char) message.length();
157+
return new String(chars) + message;
158+
}
159+
137160
/**
138161
* RtLogs.toString() fetches the logs as String.
139162
*/
@@ -144,9 +167,7 @@ public void toStringFetch() {
144167
new AssertRequest(
145168
new Response(
146169
HttpStatus.SC_OK,
147-
Json.createObjectBuilder()
148-
.add("logs", "toString logs")
149-
.build().toString()
170+
this.prepareMessage("toString logs")
150171
),
151172
new Condition(
152173
"Method should be a GET",
@@ -163,7 +184,7 @@ public void toStringFetch() {
163184
);
164185
MatcherAssert.assertThat(
165186
logs.toString(),
166-
Matchers.equalTo("{\"logs\":\"toString logs\"}")
187+
Matchers.equalTo("toString logs")
167188
);
168189
}
169190

@@ -178,9 +199,7 @@ public void logStdout() throws Exception {
178199
new AssertRequest(
179200
new Response(
180201
HttpStatus.SC_OK,
181-
Json.createObjectBuilder()
182-
.add("logs", "stdout logs")
183-
.build().toString()
202+
this.prepareMessage("stdout logs")
184203
),
185204
new Condition(
186205
"Method should be a GET",
@@ -197,7 +216,7 @@ public void logStdout() throws Exception {
197216
);
198217
MatcherAssert.assertThat(
199218
logs.stdout().fetch(),
200-
Matchers.equalTo("{\"logs\":\"stdout logs\"}")
219+
Matchers.equalTo("stdout logs")
201220
);
202221
}
203222

@@ -212,9 +231,7 @@ public void logStderr() throws Exception {
212231
new AssertRequest(
213232
new Response(
214233
HttpStatus.SC_OK,
215-
Json.createObjectBuilder()
216-
.add("logs", "stderr logs")
217-
.build().toString()
234+
this.prepareMessage("stderr logs")
218235
),
219236
new Condition(
220237
"Method should be a GET",
@@ -231,7 +248,7 @@ public void logStderr() throws Exception {
231248
);
232249
MatcherAssert.assertThat(
233250
logs.stderr().fetch(),
234-
Matchers.equalTo("{\"logs\":\"stderr logs\"}")
251+
Matchers.equalTo("stderr logs")
235252
);
236253
}
237254

0 commit comments

Comments
 (0)