From e5e4ae9ac99071eb2e1519f82d906da80b27659d Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 30 Jan 2019 13:43:29 +0100 Subject: [PATCH] fix gzip filter on resteasy 3.6.2 --- .../sonia/scm/filter/GZipResponseFilter.java | 52 +++++++++--- .../scm/filter/GZipResponseFilterTest.java | 81 +++++++++++++++++++ 2 files changed, 120 insertions(+), 13 deletions(-) create mode 100644 scm-core/src/test/java/sonia/scm/filter/GZipResponseFilterTest.java diff --git a/scm-core/src/main/java/sonia/scm/filter/GZipResponseFilter.java b/scm-core/src/main/java/sonia/scm/filter/GZipResponseFilter.java index 1fa525d4fd..92dd033af0 100644 --- a/scm-core/src/main/java/sonia/scm/filter/GZipResponseFilter.java +++ b/scm-core/src/main/java/sonia/scm/filter/GZipResponseFilter.java @@ -1,24 +1,50 @@ package sonia.scm.filter; -import lombok.extern.slf4j.Slf4j; -import sonia.scm.util.WebUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import javax.ws.rs.container.ContainerRequestContext; -import javax.ws.rs.container.ContainerResponseContext; -import javax.ws.rs.container.ContainerResponseFilter; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.ext.Provider; +import javax.ws.rs.ext.WriterInterceptor; +import javax.ws.rs.ext.WriterInterceptorContext; import java.io.IOException; +import java.io.OutputStream; +import java.util.Locale; import java.util.zip.GZIPOutputStream; @Provider -@Slf4j -public class GZipResponseFilter implements ContainerResponseFilter { - public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException { - if (WebUtil.isGzipSupported(requestContext::getHeaderString)) { - log.trace("compress output with gzip"); - GZIPOutputStream wrappedResponse = new GZIPOutputStream(responseContext.getEntityStream()); - responseContext.getHeaders().add("Content-Encoding", "gzip"); - responseContext.setEntityStream(wrappedResponse); +public class GZipResponseFilter implements WriterInterceptor { + + private static final Logger LOG = LoggerFactory.getLogger(GZipResponseFilter.class); + + @Override + public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException { + if (isGZipSupported(context)) { + LOG.trace("compress output with gzip"); + encodeWithGZip(context); + } else { + context.proceed(); } } + + private void encodeWithGZip(WriterInterceptorContext context) throws IOException { + context.getHeaders().remove(HttpHeaders.CONTENT_LENGTH); + context.getHeaders().add(HttpHeaders.CONTENT_ENCODING, "gzip"); + + OutputStream outputStream = context.getOutputStream(); + GZIPOutputStream compressedOutputStream = new GZIPOutputStream(outputStream); + context.setOutputStream(compressedOutputStream); + try { + context.proceed(); + } finally { + compressedOutputStream.finish(); + context.setOutputStream(outputStream); + } + } + + private boolean isGZipSupported(WriterInterceptorContext context) { + Object encoding = context.getHeaders().getFirst(HttpHeaders.ACCEPT_ENCODING); + return encoding != null && encoding.toString().toLowerCase(Locale.ENGLISH).contains("gzip"); + } } diff --git a/scm-core/src/test/java/sonia/scm/filter/GZipResponseFilterTest.java b/scm-core/src/test/java/sonia/scm/filter/GZipResponseFilterTest.java new file mode 100644 index 0000000000..b0a704c765 --- /dev/null +++ b/scm-core/src/test/java/sonia/scm/filter/GZipResponseFilterTest.java @@ -0,0 +1,81 @@ +package sonia.scm.filter; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.WriterInterceptorContext; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.zip.GZIPOutputStream; + +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class GZipResponseFilterTest { + + @Mock + private WriterInterceptorContext context; + + @Mock + private MultivaluedMap headers; + + private final GZipResponseFilter filter = new GZipResponseFilter(); + + @BeforeEach + void setUpContext() { + when(context.getHeaders()).thenReturn(headers); + } + + @Test + void shouldSkipGZipCompression() throws IOException { + when(headers.getFirst(HttpHeaders.ACCEPT_ENCODING)).thenReturn("deflate, br"); + + filter.aroundWriteTo(context); + + verifySkipped(); + } + + @Test + void shouldSkipGZipCompressionWithoutAcceptEncodingHeader() throws IOException { + filter.aroundWriteTo(context); + + verifySkipped(); + } + + private void verifySkipped() throws IOException { + verify(context, never()).getOutputStream(); + verify(context).proceed(); + } + + + @Nested + class AcceptGZipEncoding { + + @BeforeEach + void setUpContext() { + when(headers.getFirst(HttpHeaders.ACCEPT_ENCODING)).thenReturn("gzip, deflate, br"); + when(context.getOutputStream()).thenReturn(new ByteArrayOutputStream()); + } + + @Test + void shouldEncode() throws IOException { + filter.aroundWriteTo(context); + + verify(headers).remove(HttpHeaders.CONTENT_LENGTH); + verify(headers).add(HttpHeaders.CONTENT_ENCODING, "gzip"); + + verify(context).setOutputStream(any(GZIPOutputStream.class)); + verify(context, times(2)).setOutputStream(any(OutputStream.class)); + } + + } + + +}