Skip to content

Commit d077404

Browse files
committed
Uncompress compressed body before return
1 parent 8af8991 commit d077404

File tree

4 files changed

+145
-7
lines changed

4 files changed

+145
-7
lines changed

logbook-httpclient5/src/main/java/org/zalando/logbook/httpclient5/LogbookHttpResponseInterceptor.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@
2424
@Slf4j
2525
public final class LogbookHttpResponseInterceptor implements HttpResponseInterceptor {
2626

27+
private final boolean decompressResponse;
28+
29+
public LogbookHttpResponseInterceptor() {
30+
this(false);
31+
}
32+
33+
public LogbookHttpResponseInterceptor(boolean decompressResponse) {
34+
this.decompressResponse = decompressResponse;
35+
}
36+
2737
@Override
2838
public void process(HttpResponse original, EntityDetails entity, HttpContext context) throws IOException {
2939
try {

logbook-httpclient5/src/main/java/org/zalando/logbook/httpclient5/RemoteResponse.java

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,20 @@
1414
import org.zalando.logbook.HttpHeaders;
1515
import org.zalando.logbook.Origin;
1616

17+
import java.io.ByteArrayInputStream;
18+
import java.io.ByteArrayOutputStream;
1719
import java.io.IOException;
1820
import java.nio.ByteBuffer;
1921
import java.nio.charset.Charset;
22+
import java.util.Arrays;
2023
import java.util.List;
2124
import java.util.Map;
2225
import java.util.Objects;
2326
import java.util.Optional;
2427
import java.util.Set;
2528
import java.util.concurrent.atomic.AtomicReference;
2629
import java.util.stream.Stream;
30+
import java.util.zip.GZIPInputStream;
2731

2832
import static java.nio.charset.StandardCharsets.UTF_8;
2933
import static java.util.stream.Collectors.groupingBy;
@@ -39,11 +43,21 @@ final class RemoteResponse implements org.zalando.logbook.HttpResponse {
3943
private final HttpResponse response;
4044
private final EntityDetails entityDetails;
4145
private final ByteBuffer body;
46+
private final boolean decompressResponse;
4247

43-
RemoteResponse(HttpResponse response) {
48+
RemoteResponse(HttpResponse response, boolean decompressResponse) {
4449
this.response = response;
4550
this.body = null;
4651
this.entityDetails = null;
52+
this.decompressResponse = decompressResponse;
53+
}
54+
55+
RemoteResponse(HttpResponse response) {
56+
this(response, false);
57+
}
58+
59+
public RemoteResponse(HttpResponse response, EntityDetails entityDetails, ByteBuffer body) {
60+
this(response, entityDetails, body, false);
4761
}
4862

4963
private interface State {
@@ -229,8 +243,41 @@ public RemoteResponse withoutBody() {
229243
}
230244

231245
@Override
232-
public byte[] getBody() {
233-
return state.updateAndGet(throwingUnaryOperator(state -> (body != null) ? state.buffer(Objects.requireNonNull(entityDetails), body) : state.buffer(response))).getBody();
246+
public byte[] getBody() throws IOException {
247+
byte[] originalBody = state.updateAndGet(throwingUnaryOperator(state -> (body != null) ? state.buffer(Objects.requireNonNull(entityDetails), body) : state.buffer(response))).getBody();
248+
249+
if (decompressResponse && isGzip()) {
250+
return getDecompressedBytes(originalBody);
251+
}
252+
return originalBody;
253+
}
254+
255+
private boolean isGzip() {
256+
if (response.containsHeader("Content-Encoding")) {
257+
Header[] headers = response.getHeaders("Content-Encoding");
258+
return Arrays.stream(headers).anyMatch(this::isGzipHeaderRepresentation);
259+
}
260+
return false;
261+
}
262+
263+
private boolean isGzipHeaderRepresentation(Header header) {
264+
if ("gzip".equalsIgnoreCase(header.getValue())) {
265+
return true;
266+
} else {
267+
return "x-gzip".equalsIgnoreCase(header.getValue());
268+
}
269+
}
270+
271+
private static byte[] getDecompressedBytes(byte[] body) throws IOException {
272+
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(body); ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
273+
GZIPInputStream gzipInputStream = new GZIPInputStream(inputStream);
274+
byte[] buffer = new byte[1024];
275+
int bytesRead;
276+
while ((bytesRead = gzipInputStream.read(buffer)) != -1) {
277+
outputStream.write(buffer, 0, bytesRead);
278+
}
279+
return outputStream.toByteArray();
280+
}
234281
}
235282

236283
}

logbook-httpclient5/src/test/java/org/zalando/logbook/httpclient5/RemoteResponseTest.java

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@
1010
import org.junit.jupiter.api.Test;
1111

1212
import java.io.ByteArrayInputStream;
13+
import java.io.ByteArrayOutputStream;
1314
import java.io.IOException;
1415
import java.nio.ByteBuffer;
16+
import java.nio.charset.StandardCharsets;
17+
import java.util.zip.GZIPOutputStream;
1518

1619
import static java.nio.charset.StandardCharsets.ISO_8859_1;
1720
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -20,14 +23,20 @@
2023
final class RemoteResponseTest {
2124

2225
private final BasicHttpEntity entity = new BasicHttpEntity(new ByteArrayInputStream("Hello, world!".getBytes(UTF_8)), -1, ContentType.TEXT_PLAIN, "gzip", true);
23-
private final BasicClassicHttpResponse delegate = new BasicClassicHttpResponse( 200, "Ok");
26+
private final BasicClassicHttpResponse delegate = new BasicClassicHttpResponse(200, "Ok");
2427
private final RemoteResponse unit = new RemoteResponse(delegate);
2528

2629
@BeforeEach
2730
void setUpResponseBody() {
2831
delegate.setEntity(entity);
2932
}
3033

34+
35+
@Test
36+
void decompresed() throws IOException {
37+
38+
}
39+
3140
@Test
3241
void shouldReturnContentTypesCharsetIfGiven() {
3342
delegate.addHeader("Content-Type", "text/plain;charset=ISO-8859-1");
@@ -103,4 +112,77 @@ void shouldResolveProtocolVersion() throws IOException {
103112
void shouldPreserveCaseForReasonPhrase() {
104113
assertThat(unit.getReasonPhrase()).isEqualTo("Ok");
105114
}
115+
116+
@Test
117+
void shouldDecompressCompressedGzipBodyBeforeReturn() throws IOException {
118+
String json = "{\"data\": \"data\"}";
119+
byte[] compressed = compress(json.getBytes(StandardCharsets.UTF_8));
120+
121+
BasicHttpEntity basicEntity = new BasicHttpEntity(
122+
new ByteArrayInputStream(compressed),
123+
-1,
124+
ContentType.APPLICATION_JSON,
125+
"gzip",
126+
true
127+
);
128+
129+
BasicClassicHttpResponse underTest = new BasicClassicHttpResponse(200, "Ok");
130+
RemoteResponse response = new RemoteResponse(underTest, true);
131+
underTest.setEntity(basicEntity);
132+
underTest.addHeader("Content-Type", "application/json;charset=utf-8");
133+
underTest.addHeader("Content-Encoding", "gzip");
134+
135+
assertThat(new String(response.withBody().getBody())).isEqualTo(json);
136+
}
137+
138+
@Test
139+
void shouldDecompressCompressedXGzipBodyBeforeReturn() throws IOException {
140+
String json = "{\"data\": \"data\"}";
141+
byte[] compressed = compress(json.getBytes(StandardCharsets.UTF_8));
142+
143+
BasicHttpEntity basicEntity = new BasicHttpEntity(
144+
new ByteArrayInputStream(compressed),
145+
-1,
146+
ContentType.APPLICATION_JSON,
147+
"gzip",
148+
true
149+
);
150+
151+
BasicClassicHttpResponse underTest = new BasicClassicHttpResponse(200, "Ok");
152+
RemoteResponse response = new RemoteResponse(underTest, true);
153+
underTest.setEntity(basicEntity);
154+
underTest.addHeader("Content-Type", "application/json;charset=utf-8");
155+
underTest.addHeader("Content-Encoding", "x-gzip");
156+
157+
assertThat(new String(response.withBody().getBody())).isEqualTo(json);
158+
}
159+
160+
@Test
161+
void shouldNotDecompressCompressedBodyContentEncodingHeaderIsNotPresent() throws IOException {
162+
String json = "{\"data\": \"data\"}";
163+
byte[] compressed = compress(json.getBytes(StandardCharsets.UTF_8));
164+
165+
BasicHttpEntity basicEntity = new BasicHttpEntity(
166+
new ByteArrayInputStream(compressed),
167+
-1,
168+
ContentType.APPLICATION_JSON,
169+
"gzip",
170+
true
171+
);
172+
173+
BasicClassicHttpResponse underTest = new BasicClassicHttpResponse(200, "Ok");
174+
RemoteResponse response = new RemoteResponse(underTest, true);
175+
underTest.setEntity(basicEntity);
176+
underTest.addHeader("Content-Type", "application/json;charset=utf-8");
177+
178+
assertThat(new String(response.withBody().getBody())).isNotEqualTo(json);
179+
}
180+
181+
static byte[] compress(byte[] data) throws IOException {
182+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
183+
try (GZIPOutputStream gzipOut = new GZIPOutputStream(outputStream)) {
184+
gzipOut.write(data);
185+
}
186+
return outputStream.toByteArray();
187+
}
106188
}

logbook-spring-boot-autoconfigure/src/main/java/org/zalando/logbook/autoconfigure/LogbookAutoConfiguration.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -425,15 +425,14 @@ public LogbookHttpResponseInterceptor logbookHttpResponseInterceptor(@Value("${l
425425
static class HttpClient5AutoConfiguration {
426426

427427
@Bean
428-
@ConditionalOnMissingBean(org.zalando.logbook.httpclient5.LogbookHttpRequestInterceptor.class)
429428
public org.zalando.logbook.httpclient5.LogbookHttpRequestInterceptor logbookHttpClient5RequestInterceptor(final Logbook logbook) {
430429
return new org.zalando.logbook.httpclient5.LogbookHttpRequestInterceptor(logbook);
431430
}
432431

433432
@Bean
434433
@ConditionalOnMissingBean(org.zalando.logbook.httpclient5.LogbookHttpResponseInterceptor.class)
435-
public org.zalando.logbook.httpclient5.LogbookHttpResponseInterceptor logbookHttpClient5ResponseInterceptor() {
436-
return new org.zalando.logbook.httpclient5.LogbookHttpResponseInterceptor();
434+
public org.zalando.logbook.httpclient5.LogbookHttpResponseInterceptor logbookHttpClient5ResponseInterceptor(@Value("${logbook.httpclient5.decompress-response:false}") final boolean decompressResponse) {
435+
return new org.zalando.logbook.httpclient5.LogbookHttpResponseInterceptor(decompressResponse);
437436
}
438437

439438
}

0 commit comments

Comments
 (0)