23
23
/**
24
24
* Response information supplied when a response is received and before the body is processed.
25
25
* <p>
26
+ * During a request to GitHub, {@link GitHubConnector#send(GitHubConnectorRequest)} returns a
27
+ * {@link GitHubConnectorResponse}. This is processed to create a GitHubResponse.
28
+ * <p>
26
29
* Instances of this class are closed once the response is done being processed. This means that {@link #bodyStream()}
27
30
* will not be readable after a call is completed.
28
31
*
@@ -43,11 +46,10 @@ public abstract class GitHubConnectorResponse implements Closeable {
43
46
@ Nonnull
44
47
private final Map <String , List <String >> headers ;
45
48
private boolean bodyStreamCalled = false ;
46
- private boolean bodyBytesLoaded = false ;
47
49
private InputStream bodyStream = null ;
48
50
private byte [] bodyBytes = null ;
49
51
private boolean isClosed = false ;
50
- private boolean forceBufferedBodyStream ;
52
+ private boolean isBodyStreamRereadable ;
51
53
52
54
/**
53
55
* GitHubConnectorResponse constructor
@@ -71,6 +73,7 @@ protected GitHubConnectorResponse(@Nonnull GitHubConnectorRequest request,
71
73
caseInsensitiveMap .put (entry .getKey (), Collections .unmodifiableList (new ArrayList <>(entry .getValue ())));
72
74
}
73
75
this .headers = Collections .unmodifiableMap (caseInsensitiveMap );
76
+ this .isBodyStreamRereadable = false ;
74
77
}
75
78
76
79
/**
@@ -92,53 +95,60 @@ public String header(String name) {
92
95
/**
93
96
* The response body as an {@link InputStream}.
94
97
*
98
+ * When {@link #isBodyStreamRereadable} is false, {@link #bodyStream()} can only be called once and the returned
99
+ * stream should be assumed to be read-once and not resetable. This is the default behavior for HTTP_OK responses
100
+ * and significantly reduces memory usage.
101
+ *
102
+ * When {@link #isBodyStreamRereadable} is true, {@link #bodyStream()} can be called be called multiple times. The
103
+ * full stream data is read into a byte array during the first call. Each call returns a new stream backed by the
104
+ * same byte array. This uses more memory, but is required to enable rereading the body stream during trace logging,
105
+ * debugging, and error responses.
106
+ *
95
107
* @return the response body
96
108
* @throws IOException
97
109
* if response stream is null or an I/O Exception occurs.
98
110
*/
99
111
@ Nonnull
100
112
public InputStream bodyStream () throws IOException {
101
- InputStream body = null ;
102
113
synchronized (this ) {
103
114
if (isClosed ) {
104
115
throw new IOException ("Response is closed" );
105
116
}
106
117
107
118
if (bodyStreamCalled ) {
108
- if (!bodyBytesLoaded ) {
109
- throw new IOException ("Response is already consumed " );
119
+ if (!isBodyStreamRereadable () ) {
120
+ throw new IOException ("Response body not rereadable " );
110
121
}
111
122
} else {
112
- body = wrapStream (rawBodyStream ());
123
+ bodyStream = wrapStream (rawBodyStream ());
113
124
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
}
125
126
126
- if (bodyBytesLoaded ) {
127
- body = bodyBytes == null ? null : new ByteArrayInputStream (bodyBytes );
127
+ if (bodyStream == null ) {
128
+ throw new IOException ("Response body missing, stream null" );
129
+ } else if (!isBodyStreamRereadable ()) {
130
+ return bodyStream ;
128
131
}
129
- }
130
132
131
- if (body == null ) {
132
- throw new IOException ("Response body missing, stream null" );
133
- }
133
+ // Load rereadable byte array
134
+ if (bodyBytes == null ) {
135
+ bodyBytes = IOUtils .toByteArray (bodyStream );
136
+ // Close the raw body stream after successfully reading
137
+ IOUtils .closeQuietly (bodyStream );
138
+ }
134
139
135
- return body ;
140
+ return new ByteArrayInputStream (bodyBytes );
141
+ }
136
142
}
137
143
138
144
/**
139
145
* Get the raw implementation specific body stream for this response.
140
146
*
141
- * This method will only be called once to completion. If an exception is thrown, it may be called multiple times.
147
+ * This method will only be called once to completion. If an exception is thrown by this method, it may be called
148
+ * multiple times.
149
+ *
150
+ * The stream returned from this method will be closed when the response is closed or sooner. Inheriting classes do
151
+ * not need to close it.
142
152
*
143
153
* @return the stream for the raw response
144
154
* @throws IOException
@@ -178,24 +188,40 @@ public Map<String, List<String>> allHeaders() {
178
188
}
179
189
180
190
/**
181
- * Use unbufferred body stream.
191
+ * The body stream rereadable state.
192
+ *
193
+ * Body stream defaults to read once for HTTP_OK responses (to reduce memory usage). For non-HTTP_OK responses, body
194
+ * stream is switched to rereadable (in-memory byte array) for error processing.
182
195
*
183
- * @return true when unbuffered body stream can should be used.
196
+ * Calling {@link #setBodyStreamRereadable()} will force {@link #isBodyStreamRereadable} to be true for this
197
+ * response regardless of {@link #statusCode} value.
198
+ *
199
+ * @return true when body stream is rereadable.
184
200
*/
185
- boolean useBufferedBodyStream () {
201
+ public boolean isBodyStreamRereadable () {
186
202
synchronized (this ) {
187
- return forceBufferedBodyStream || statusCode () != HTTP_OK ;
203
+ return isBodyStreamRereadable || statusCode != HTTP_OK ;
188
204
}
189
205
}
190
206
191
207
/**
192
- * Use unbufferred body stream.
208
+ * Force body stream to rereadable regardless of status code.
209
+ *
210
+ * Calling {@link #setBodyStreamRereadable()} will force {@link #isBodyStreamRereadable} to be true for this
211
+ * response regardless of {@link #statusCode} value.
193
212
*
194
- * @return true when unbuffered body stream can should be used.
213
+ * This is required to support body value logging during low-level tracing but should be avoided in general since it
214
+ * consumes significantly more memory.
215
+ *
216
+ * Will throw runtime exception if a non-rereadable body stream has already been returned from
217
+ * {@link #bodyStream()}.
195
218
*/
196
- public void forceBufferedBodyStream () {
219
+ public void setBodyStreamRereadable () {
197
220
synchronized (this ) {
198
- this .forceBufferedBodyStream = true ;
221
+ if (bodyStreamCalled && !isBodyStreamRereadable ()) {
222
+ throw new RuntimeException ("bodyStream() already called in read-once mode" );
223
+ }
224
+ isBodyStreamRereadable = true ;
199
225
}
200
226
}
201
227
@@ -250,7 +276,10 @@ public final int parseInt(String name) throws NumberFormatException {
250
276
251
277
/**
252
278
* A ByteArrayResponse class
279
+ *
280
+ * @deprecated Inherit directly from {@link GitHubConnectorResponse}.
253
281
*/
282
+ @ Deprecated
254
283
public abstract static class ByteArrayResponse extends GitHubConnectorResponse {
255
284
256
285
/**
0 commit comments