Skip to content

Commit 7b7f1bd

Browse files
committed
Change all response body calls to default to streamed output
1 parent 9c06af5 commit 7b7f1bd

File tree

8 files changed

+110
-120
lines changed

8 files changed

+110
-120
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -611,7 +611,7 @@
611611
</configuration>
612612
</execution>
613613
<execution>
614-
<id>httpclient-test</id>
614+
<id>httpclient-test-tracing</id>
615615
<phase>integration-test</phase>
616616
<goals>
617617
<goal>test</goal>

src/main/java/org/kohsuke/github/GitHubClient.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -629,11 +629,8 @@ private void logResponseBody(@Nonnull final GitHubConnectorResponse response) {
629629
LOGGER.log(FINEST, () -> {
630630
String body;
631631
try {
632-
if (response.request().avoidBufferedResponseStream()) {
633-
body = "Stream-once body not logged.";
634-
} else {
635-
body = GitHubResponse.getBodyAsString(response);
636-
}
632+
response.forceBufferedBodyStream();
633+
body = GitHubResponse.getBodyAsString(response);
637634
} catch (Throwable e) {
638635
body = "Error reading response body";
639636
}

src/main/java/org/kohsuke/github/GitHubRequest.java

Lines changed: 4 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ public class GitHubRequest implements GitHubConnectorRequest {
5151
private final RateLimitTarget rateLimitTarget;
5252
private final byte[] body;
5353
private final boolean forceBody;
54-
private final boolean avoidBufferedResponseStream;
5554

5655
private final URL url;
5756

@@ -64,8 +63,7 @@ private GitHubRequest(@Nonnull List<Entry> args,
6463
@Nonnull String method,
6564
@Nonnull RateLimitTarget rateLimitTarget,
6665
@CheckForNull byte[] body,
67-
boolean forceBody,
68-
boolean avoidBufferedResponseStream) {
66+
boolean forceBody) {
6967
this.args = Collections.unmodifiableList(new ArrayList<>(args));
7068
TreeMap<String, List<String>> caseInsensitiveMap = new TreeMap<>(nullableCaseInsensitiveComparator);
7169
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
@@ -79,7 +77,6 @@ private GitHubRequest(@Nonnull List<Entry> args,
7977
this.rateLimitTarget = rateLimitTarget;
8078
this.body = body;
8179
this.forceBody = forceBody;
82-
this.avoidBufferedResponseStream = avoidBufferedResponseStream;
8380
String tailApiUrl = buildTailApiUrl();
8481
url = getApiURL(apiUrl, tailApiUrl);
8582
}
@@ -272,14 +269,6 @@ public boolean hasBody() {
272269
return forceBody || !METHODS_WITHOUT_BODY.contains(method);
273270
}
274271

275-
/**
276-
* Whether the response to this request should avoid buffered stream or not.
277-
*/
278-
@Override
279-
public boolean avoidBufferedResponseStream() {
280-
return avoidBufferedResponseStream;
281-
}
282-
283272
/**
284273
* Create a {@link Builder} from this request. Initial values of the builder will be the same as this
285274
* {@link GitHubRequest}.
@@ -295,8 +284,7 @@ Builder<?> toBuilder() {
295284
method,
296285
rateLimitTarget,
297286
body,
298-
forceBody,
299-
avoidBufferedResponseStream);
287+
forceBody);
300288
}
301289

302290
private String buildTailApiUrl() {
@@ -366,7 +354,6 @@ static class Builder<B extends Builder<B>> {
366354

367355
private byte[] body;
368356
private boolean forceBody;
369-
private boolean avoidBufferedResponseStream;
370357

371358
/**
372359
* Create a new {@link GitHubRequest.Builder}
@@ -380,7 +367,6 @@ protected Builder() {
380367
"GET",
381368
RateLimitTarget.CORE,
382369
null,
383-
false,
384370
false);
385371
}
386372

@@ -392,8 +378,7 @@ private Builder(@Nonnull List<Entry> args,
392378
@Nonnull String method,
393379
@Nonnull RateLimitTarget rateLimitTarget,
394380
@CheckForNull byte[] body,
395-
boolean forceBody,
396-
boolean avoidBufferedResponseStream) {
381+
boolean forceBody) {
397382
this.args = new ArrayList<>(args);
398383
TreeMap<String, List<String>> caseInsensitiveMap = new TreeMap<>(nullableCaseInsensitiveComparator);
399384
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
@@ -407,7 +392,6 @@ private Builder(@Nonnull List<Entry> args,
407392
this.rateLimitTarget = rateLimitTarget;
408393
this.body = body;
409394
this.forceBody = forceBody;
410-
this.avoidBufferedResponseStream = avoidBufferedResponseStream;
411395
}
412396

413397
/**
@@ -426,8 +410,7 @@ public GitHubRequest build() {
426410
method,
427411
rateLimitTarget,
428412
body,
429-
forceBody,
430-
avoidBufferedResponseStream);
413+
forceBody);
431414
}
432415

433416
/**
@@ -817,17 +800,6 @@ public B inBody() {
817800
forceBody = true;
818801
return (B) this;
819802
}
820-
821-
/**
822-
* We cache response stream into buffer by default, but some responses can be huge so we should avoid that.
823-
* Setting this flag indicates that we should avoid buffered stream response.
824-
*
825-
* @return the request builder
826-
*/
827-
public B avoidBufferedResponseStream() {
828-
avoidBufferedResponseStream = true;
829-
return (B) this;
830-
}
831803
}
832804

833805
/**

src/main/java/org/kohsuke/github/Requester.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,7 @@ public int fetchHttpStatusCode() throws IOException {
128128
* the io exception
129129
*/
130130
public <T> T fetchStream(@Nonnull InputStreamFunction<T> handler) throws IOException {
131-
return client
132-
.sendRequest(this.avoidBufferedResponseStream(),
133-
(connectorResponse) -> handler.apply(connectorResponse.bodyStream()))
134-
.body();
131+
return client.sendRequest(this, (connectorResponse) -> handler.apply(connectorResponse.bodyStream())).body();
135132
}
136133

137134
/**

src/main/java/org/kohsuke/github/connector/GitHubConnectorRequest.java

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -81,13 +81,4 @@ public interface GitHubConnectorRequest {
8181
* @return true, if the body is not null. Otherwise, false.
8282
*/
8383
boolean hasBody();
84-
85-
/**
86-
* Whether the response stream to this request is not buffered. It is used to avoid huge response caching.
87-
*
88-
* @return true, if the response stream is not buffered.
89-
*/
90-
default boolean avoidBufferedResponseStream() {
91-
return false;
92-
}
9384
}

src/main/java/org/kohsuke/github/connector/GitHubConnectorResponse.java

Lines changed: 88 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@
77
import java.io.Closeable;
88
import java.io.IOException;
99
import java.io.InputStream;
10-
import java.util.*;
10+
import java.util.ArrayList;
11+
import java.util.Collections;
12+
import java.util.Comparator;
13+
import java.util.List;
14+
import java.util.Map;
15+
import java.util.TreeMap;
1116
import java.util.zip.GZIPInputStream;
1217

1318
import javax.annotation.CheckForNull;
@@ -37,6 +42,12 @@ public abstract class GitHubConnectorResponse implements Closeable {
3742
private final GitHubConnectorRequest request;
3843
@Nonnull
3944
private final Map<String, List<String>> headers;
45+
private boolean bodyStreamCalled = false;
46+
private boolean bodyBytesLoaded = false;
47+
private InputStream bodyStream = null;
48+
private byte[] bodyBytes = null;
49+
private boolean isClosed = false;
50+
private boolean forceBufferedBodyStream;
4051

4152
/**
4253
* GitHubConnectorResponse constructor
@@ -86,7 +97,55 @@ public String header(String name) {
8697
* if response stream is null or an I/O Exception occurs.
8798
*/
8899
@Nonnull
89-
public abstract InputStream bodyStream() throws IOException;
100+
public InputStream bodyStream() throws IOException {
101+
InputStream body = null;
102+
synchronized (this) {
103+
if (isClosed) {
104+
throw new IOException("Response is closed");
105+
}
106+
107+
if (bodyStreamCalled) {
108+
if (!bodyBytesLoaded) {
109+
throw new IOException("Response is already consumed");
110+
}
111+
} else {
112+
body = wrapStream(rawBodyStream());
113+
bodyStreamCalled = true;
114+
bodyStream = body;
115+
if (useBufferedBodyStream()) {
116+
bodyBytesLoaded = true;
117+
try (InputStream stream = body) {
118+
if (stream != null) {
119+
bodyBytes = IOUtils.toByteArray(stream);
120+
}
121+
}
122+
bodyStream = null;
123+
}
124+
}
125+
126+
if (bodyBytesLoaded) {
127+
body = bodyBytes == null ? null : new ByteArrayInputStream(bodyBytes);
128+
}
129+
}
130+
131+
if (body == null) {
132+
throw new IOException("Response body missing, stream null");
133+
}
134+
135+
return body;
136+
}
137+
138+
/**
139+
* Get the raw implementation specific body stream for this response.
140+
*
141+
* This method will only be called once to completion. If an exception is thrown, it may be called multiple times.
142+
*
143+
* @return the stream for the raw response
144+
* @throws IOException
145+
* if an I/O Exception occurs.
146+
*/
147+
@CheckForNull
148+
protected abstract InputStream rawBodyStream() throws IOException;
90149

91150
/**
92151
* Gets the {@link GitHubConnector} for this response.
@@ -123,8 +182,33 @@ public Map<String, List<String>> allHeaders() {
123182
*
124183
* @return true when unbuffered body stream can should be used.
125184
*/
126-
boolean useUnbufferedBodyStream() {
127-
return statusCode() == HTTP_OK && request().avoidBufferedResponseStream();
185+
boolean useBufferedBodyStream() {
186+
synchronized (this) {
187+
return forceBufferedBodyStream || statusCode() != HTTP_OK;
188+
}
189+
}
190+
191+
/**
192+
* Use unbufferred body stream.
193+
*
194+
* @return true when unbuffered body stream can should be used.
195+
*/
196+
public void forceBufferedBodyStream() {
197+
synchronized (this) {
198+
this.forceBufferedBodyStream = true;
199+
}
200+
}
201+
202+
/**
203+
* {@inheritDoc}
204+
*/
205+
@Override
206+
public void close() throws IOException {
207+
synchronized (this) {
208+
IOUtils.closeQuietly(bodyStream);
209+
isClosed = true;
210+
this.bodyBytes = null;
211+
}
128212
}
129213

130214
/**
@@ -169,10 +253,6 @@ public final int parseInt(String name) throws NumberFormatException {
169253
*/
170254
public abstract static class ByteArrayResponse extends GitHubConnectorResponse {
171255

172-
private boolean inputStreamRead = false;
173-
private byte[] inputBytes = null;
174-
private boolean isClosed = false;
175-
176256
/**
177257
* Constructor for ByteArray Response
178258
*
@@ -188,61 +268,5 @@ protected ByteArrayResponse(@Nonnull GitHubConnectorRequest request,
188268
@Nonnull Map<String, List<String>> headers) {
189269
super(request, statusCode, headers);
190270
}
191-
192-
/**
193-
* {@inheritDoc}
194-
*/
195-
@Override
196-
@Nonnull
197-
public InputStream bodyStream() throws IOException {
198-
if (isClosed) {
199-
throw new IOException("Response is closed");
200-
}
201-
202-
synchronized (this) {
203-
InputStream body;
204-
if (!inputStreamRead) {
205-
body = wrapStream(rawBodyStream());
206-
if (!useUnbufferedBodyStream()) {
207-
try (InputStream stream = body) {
208-
if (stream != null) {
209-
inputBytes = IOUtils.toByteArray(stream);
210-
}
211-
}
212-
}
213-
inputStreamRead = true;
214-
if (useUnbufferedBodyStream()) {
215-
return body;
216-
}
217-
}
218-
}
219-
220-
if (useUnbufferedBodyStream()) {
221-
throw new IOException("Response is already consumed");
222-
} else if (inputBytes == null) {
223-
throw new IOException("Response body missing, stream null");
224-
}
225-
226-
return new ByteArrayInputStream(inputBytes);
227-
}
228-
229-
/**
230-
* Get the raw implementation specific body stream for this response.
231-
*
232-
* This method will only be called once to completion. If an exception is thrown, it may be called multiple
233-
* times.
234-
*
235-
* @return the stream for the raw response
236-
* @throws IOException
237-
* if an I/O Exception occurs.
238-
*/
239-
@CheckForNull
240-
protected abstract InputStream rawBodyStream() throws IOException;
241-
242-
@Override
243-
public void close() throws IOException {
244-
isClosed = true;
245-
this.inputBytes = null;
246-
}
247271
}
248272
}

src/test/java/org/kohsuke/github/GHWorkflowRunTest.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import java.util.Optional;
2020
import java.util.Scanner;
2121
import java.util.function.Function;
22+
import java.util.logging.Level;
23+
import java.util.logging.Logger;
2224
import java.util.stream.Collectors;
2325
import java.util.zip.ZipEntry;
2426
import java.util.zip.ZipInputStream;
@@ -394,10 +396,14 @@ public void testArtifacts() throws IOException {
394396
checkArtifactProperties(artifacts.get(0), "artifact1");
395397
checkArtifactProperties(artifacts.get(1), "artifact2");
396398

399+
Logger clientLogger = Logger.getLogger(GitHubClient.class.getName());
400+
397401
// Test download from upload-artifact@v3 infrastructure
398402
String artifactContent = artifacts.get(0).download((is) -> {
399-
assertThat(is, not(isA(ByteArrayInputStream.class)));
400-
403+
// At finest log level, all body responses are byte arrays.
404+
if (clientLogger.getLevel() != Level.FINEST) {
405+
assertThat(is, not(isA(ByteArrayInputStream.class)));
406+
}
401407
try (ZipInputStream zis = new ZipInputStream(is)) {
402408
StringBuilder sb = new StringBuilder();
403409

0 commit comments

Comments
 (0)