springspring-bootspring-reactive

How to decompress gzipped content in spring reactive?


While migrating my spring server from servlets to reactive I had to change all the filters in the code to WebFilter. One of the filters was decompressing gzipped content, but I couldn't do the same with the new WebFilter.

With servlets I wrapped the inputstream with a GzipInputStream. What is the best practice to do it with spring reactive?


Solution

  • Solution:

    @Component
    public class GzipFilter implements WebFilter {
    
      private static final Logger LOG = LoggerFactory.getLogger(GzipFilter.class);
      public static final String CONTENT_ENCODING = "content-encoding";
      public static final String GZIP = "gzip";
      public static final String UTF_8 = "UTF-8";
    
      @Override
      public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
    
        ServerHttpRequest request = exchange.getRequest();
    
        if (!isGzip(request)) {
          return chain.filter(exchange);
        }
        else {
    
          ServerHttpRequest mutatedRequest = new ServerHttpRequestWrapper(request);
          ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();
    
          return chain.filter(mutatedExchange);
        }
      }
    
      private boolean isGzip(ServerHttpRequest serverHttpRequest) {
        String encoding = serverHttpRequest.getHeaders().getFirst(CONTENT_ENCODING);
    
        return encoding != null && encoding.contains(GZIP);
      }
    
    
      private static class ServerHttpRequestWrapper implements ServerHttpRequest {
    
        private ServerHttpRequest request;
    
        public ServerHttpRequestWrapper(ServerHttpRequest request) {
          this.request = request;
        }
    
        private static byte[] getDeflatedBytes(GZIPInputStream gzipInputStream) throws IOException {
          StringWriter writer = new StringWriter();
          IOUtils.copy(gzipInputStream, writer, UTF_8);
          return writer.toString().getBytes();
        }
    
    
        @Override
        public String getId() {
          return request.getId();
        }
    
        @Override
        public RequestPath getPath() {
          return request.getPath();
        }
    
        @Override
        public MultiValueMap<String, String> getQueryParams() {
          return request.getQueryParams();
        }
    
        @Override
        public MultiValueMap<String, HttpCookie> getCookies() {
          return request.getCookies();
        }
    
        @Override
        public String getMethodValue() {
          return request.getMethodValue();
        }
    
        @Override
        public URI getURI() {
          return request.getURI();
        }
    
        @Override
        public Flux<DataBuffer> getBody() {
    
          Mono<DataBuffer> mono = request.getBody()
              .map(dataBuffer -> dataBuffer.asInputStream(true))
              .reduce(SequenceInputStream::new)
              .map(inputStream -> {
                try (GZIPInputStream gzipInputStream = new GZIPInputStream(inputStream)) {
                  byte[] targetArray = getDeflatedBytes(gzipInputStream);
                  return new DefaultDataBufferFactory().wrap(targetArray);
                }
                catch (IOException e) {
                  throw new IllegalGzipRequest(String.format("failed to decompress gzip content. Path: %s", request.getPath()));
                }
              });
    
          return mono.flux();
        }
    
        @Override
        public HttpHeaders getHeaders() {
          return request.getHeaders();
        }
      }
    
    
    }