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());

Reply via email to