This is an automated email from the ASF dual-hosted git repository.
Aias00 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git
The following commit(s) were added to refs/heads/master by this push:
new 9573974045 fix(logging): decompress the assembled gzip response body
once (#7434)
9573974045 is described below
commit 95739740451a22acf4fed179bc097d803aa7b5d2
Author: Sean-Walker0 <[email protected]>
AuthorDate: Thu Oct 8 14:45:45 2026 +0800
fix(logging): decompress the assembled gzip response body once (#7434)
LoggingConsolePlugin tried to decompress each response DataBuffer
chunk as an independent gzip stream. A gzip stream that spans several
chunks cannot be decompressed chunk by chunk, so the per-chunk
GZIPInputStream either returned truncated data or threw and the raw
compressed bytes were written to the log, producing unreadable
response-body logs for any gzipped response delivered in more than one
buffer.
Assemble the raw chunks while streaming and decompress the whole gzip
stream once when the body completes; the plain (non-gzip) path is
unchanged, and a failed decompression still falls back to the raw
bytes with an error log. Regression test logs a gzip payload split
across two buffers (red on the previous code, which never logged the
decompressed text).
Fixes #6512
Co-authored-by: Sean-Walker0
<[email protected]>
---
.../logging/console/LoggingConsolePlugin.java | 41 ++++++++++++----------
.../logging/console/LoggingConsolePluginTest.java | 31 ++++++++++++++++
2 files changed, 54 insertions(+), 18 deletions(-)
diff --git
a/shenyu-plugin/shenyu-plugin-logging/shenyu-plugin-logging-console/src/main/java/org/apache/shenyu/plugin/logging/console/LoggingConsolePlugin.java
b/shenyu-plugin/shenyu-plugin-logging/shenyu-plugin-logging-console/src/main/java/org/apache/shenyu/plugin/logging/console/LoggingConsolePlugin.java
index 10ba1e9474..98800a84fe 100644
---
a/shenyu-plugin/shenyu-plugin-logging/shenyu-plugin-logging-console/src/main/java/org/apache/shenyu/plugin/logging/console/LoggingConsolePlugin.java
+++
b/shenyu-plugin/shenyu-plugin-logging/shenyu-plugin-logging-console/src/main/java/org/apache/shenyu/plugin/logging/console/LoggingConsolePlugin.java
@@ -276,40 +276,45 @@ public class LoggingConsolePlugin extends
AbstractShenyuPlugin {
});
}
BodyWriter writer = new BodyWriter();
+ final ByteArrayOutputStream gzipBuffer = new
ByteArrayOutputStream();
+ final boolean gzipEncoded =
serverHttpResponse.getHeaders().containsKey(Constants.CONTENT_ENCODING)
+ &&
serverHttpResponse.getHeaders().getFirst(Constants.CONTENT_ENCODING).contains("gzip");
return Flux.from(body).doOnNext(buffer -> {
try (DataBuffer.ByteBufferIterator bufferIterator =
buffer.readableByteBuffers()) {
bufferIterator.forEachRemaining(byteBuffer -> {
- // Handle gzip encoded response
- if
(serverHttpResponse.getHeaders().containsKey(Constants.CONTENT_ENCODING)
- &&
serverHttpResponse.getHeaders().getFirst(Constants.CONTENT_ENCODING).contains("gzip"))
{
- try {
- ByteBuffer readOnlyBuffer =
byteBuffer.asReadOnlyBuffer();
- byte[] compressed = new
byte[readOnlyBuffer.remaining()];
- readOnlyBuffer.get(compressed);
-
- // Decompress gzipped content
- byte[] decompressed =
decompressGzip(compressed);
- writer.write(ByteBuffer.wrap(decompressed));
-
- } catch (IOException e) {
- LOG.error("Failed to decompress gzipped
response", e);
- writer.write(byteBuffer.asReadOnlyBuffer());
- }
+ ByteBuffer readOnlyBuffer =
byteBuffer.asReadOnlyBuffer();
+ if (gzipEncoded) {
+ // a gzip stream can only be decompressed as a
whole, so the raw
+ // chunks are assembled first and decompressed
once at the end
+ byte[] chunk = new
byte[readOnlyBuffer.remaining()];
+ readOnlyBuffer.get(chunk);
+ gzipBuffer.writeBytes(chunk);
} else {
- writer.write(byteBuffer.asReadOnlyBuffer());
+ writer.write(readOnlyBuffer);
}
});
}
}).doFinally(signal -> {
logInfo.append("[Response Body
Start]").append(System.lineSeparator());
// desensitize data
- String responseBody =
DataDesensitizeUtils.desensitizeBody(desensitized, writer.output(),
keyWordMatch, dataDesensitizeAlg);
+ final String assembledBody = gzipEncoded ?
decompressAssembledGzip(gzipBuffer) : writer.output();
+ String responseBody =
DataDesensitizeUtils.desensitizeBody(desensitized, assembledBody, keyWordMatch,
dataDesensitizeAlg);
logInfo.append(responseBody).append(System.lineSeparator());
logInfo.append("[Response Body
End]").append(System.lineSeparator());
// when response, print all request info.
print(logInfo.toString());
});
}
+
+ private String decompressAssembledGzip(final ByteArrayOutputStream
gzipBuffer) {
+ final byte[] compressed = gzipBuffer.toByteArray();
+ try {
+ return new String(decompressGzip(compressed),
StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ LOG.error("Failed to decompress gzipped response", e);
+ return new String(compressed, StandardCharsets.UTF_8);
+ }
+ }
private byte[] decompressGzip(final byte[] compressed) throws
IOException {
try (GZIPInputStream gzipInputStream = new GZIPInputStream(new
ByteArrayInputStream(compressed));
diff --git
a/shenyu-plugin/shenyu-plugin-logging/shenyu-plugin-logging-console/src/test/java/org/apache/shenyu/plugin/logging/console/LoggingConsolePluginTest.java
b/shenyu-plugin/shenyu-plugin-logging/shenyu-plugin-logging-console/src/test/java/org/apache/shenyu/plugin/logging/console/LoggingConsolePluginTest.java
index 3b24654022..064a7ae523 100644
---
a/shenyu-plugin/shenyu-plugin-logging/shenyu-plugin-logging-console/src/test/java/org/apache/shenyu/plugin/logging/console/LoggingConsolePluginTest.java
+++
b/shenyu-plugin/shenyu-plugin-logging/shenyu-plugin-logging-console/src/test/java/org/apache/shenyu/plugin/logging/console/LoggingConsolePluginTest.java
@@ -38,9 +38,12 @@ import reactor.core.publisher.Mono;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
+import java.util.zip.GZIPOutputStream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -140,6 +143,34 @@ public final class LoggingConsolePluginTest {
assertFalse(replaceResponseLog.toString().contains(md5Value));
}
+ @Test
+ public void testGzipResponseLoggedDecompressedAcrossChunks() throws
IOException {
+ final StringBuilder responseLog = new StringBuilder();
+ final MockServerHttpResponse response = new MockServerHttpResponse();
+ response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
+ response.getHeaders().add("Content-Encoding", "gzip");
+ final LoggingConsolePlugin.LoggingServerHttpResponse decorator =
loggingConsolePlugin.new LoggingServerHttpResponse(
+ response, responseLog, false, new
KeyWordMatch(Collections.emptySet()), null);
+
+ final String plainBody = "{\"msg\":\"hello gzip logging across
multiple buffer chunks"
+ + " with a long enough payload to produce several compressed
segments\"}";
+ final ByteArrayOutputStream compressed = new ByteArrayOutputStream();
+ try (GZIPOutputStream gzipOutputStream = new
GZIPOutputStream(compressed)) {
+ gzipOutputStream.write(plainBody.getBytes(StandardCharsets.UTF_8));
+ }
+ final byte[] gzipBytes = compressed.toByteArray();
+ final int splitAt = gzipBytes.length / 2;
+ final DataBuffer firstChunk = response.bufferFactory()
+ .wrap(java.util.Arrays.copyOfRange(gzipBytes, 0, splitAt));
+ final DataBuffer secondChunk = response.bufferFactory()
+ .wrap(java.util.Arrays.copyOfRange(gzipBytes, splitAt,
gzipBytes.length));
+
+ decorator.writeWith(Flux.just(firstChunk, secondChunk)).block();
+
+ assertTrue(responseLog.toString().contains("hello gzip logging"),
+ "the logged body should be the decompressed payload");
+ }
+
@Test
public void testGetOrder() {
assertEquals(loggingConsolePlugin.getOrder(),
PluginEnum.LOGGING_CONSOLE.getCode());