如何解压缩Flux <databuffer>(以及如何编写)?

时间:2017-12-31 22:19:18

标签: java spring spring-integration netty spring-webflux

我需要在没有中间存储的情况下读取和写入压缩(GZIP)流。目前,我正在使用Spring scala> def aLotOfZeroes: Int => String = "0" * _ aLotOfZeroes: Int => String scala> aLotOfZeroes(10) res16: String = 0000000000 进行编写,使用Apache HTTP客户端进行阅读(请参阅我的回答here,了解为什么RestTemplate不能用于读大流)。实施非常简单,我在回复RestTemplate上拍了GZIPInputStream并继续前进。

现在,我想切换到使用Spring 5 WebClient(因为我不是现状的粉丝)。但是,InputStream在性质上具有反应性并处理WebClient;我相信有可能获得Flux<Stuff>,其中DataBufferFlux<DataBuffer>的抽象。问题是,如何在不必将完整流存储在内存中(ByteBuffer,我正在看着你)或写入本地磁盘的情况下即时解压缩它?值得一提的是OutOfMemoryError使用了Netty。

我承认不太了解(de)压缩,但是,我做了我的研究,但网上提供的资料似乎都没什么特别有帮助。

compression on java nio direct buffers

Writing GZIP file with nio

Reading a GZIP file from a FileChannel (Java NIO)

(de)compressing files using NIO

Iterable gzip deflate/inflate in Java

2 个答案:

答案 0 :(得分:2)

public class HttpResponseHeadersHandler extends ChannelInboundHandlerAdapter {
    private final HttpHeaders httpHeaders;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        if (msg instanceof HttpResponse &&
                !HttpStatus.resolve(((HttpResponse) msg).status().code()).is1xxInformational()) {
            HttpHeaders headers = ((HttpResponse) msg).headers();

            httpHeaders.forEach(e -> {
                log.warn("Modifying {} from: {} to: {}.", e.getKey(), headers.get(e.getKey()), e.getValue());
                headers.set(e.getKey(), e.getValue());
            });
        }
        ctx.fireChannelRead(msg);
    }
}

然后我创建一个ClientHttpConnector以与WebClient一起使用,并在afterNettyContextInit中添加处理程序:

ctx.addHandlerLast(new ReadTimeoutHandler(readTimeoutMillis, TimeUnit.MILLISECONDS));
ctx.addHandlerLast(new Slf4JLoggingHandler());
if (forceDecompression) {
    io.netty.handler.codec.http.HttpHeaders httpHeaders = new ReadOnlyHttpHeaders(
            true,
            CONTENT_ENCODING, GZIP,
            CONTENT_TYPE, APPLICATION_JSON
    );
    HttpResponseHeadersHandler headersModifier = new HttpResponseHeadersHandler(httpHeaders);
    ctx.addHandlerFirst(headersModifier);
}
ctx.addHandlerLast(new HttpContentDecompressor());

当然,对于非GZIP压缩的响应,这会失败,因此我仅将WebClient的这个实例用于特定用例,我确定该响应已被压缩。

写作很简单:Spring有一个ResourceEncoder,因此InputStream可以简单地转换为InputStreamResource,瞧!

答案 1 :(得分:0)

在这里注意到这一点,让我有些困惑-从5.1开始,API进行了一些更改。

我对ChannelInboundHandler的接受设置与我类似:

public class GzipJsonHeadersHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        if (msg instanceof HttpResponse
                && !HttpStatus.resolve(((HttpResponse) msg).status().code()).is1xxInformational()) {
            HttpHeaders headers = ((HttpResponse) msg).headers();
            headers.clear();
            headers.set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.GZIP);
            headers.set(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON);
        }
        ctx.fireChannelRead(msg);
    }
}

(为简单起见,我所需的标头值只是在此处进行硬编码,否则完全相同。)

要注册它却有所不同:

WebClient.builder()
    .clientConnector(
            new ReactorClientHttpConnector(
                    HttpClient.from(
                            TcpClient.create()
                                    .doOnConnected(c -> {
                                        c.addHandlerFirst(new HttpContentDecompressor());
                                        c.addHandlerFirst(new HttpResponseHeadersHandler());
                                    })
                    ).compress(true)
            )
    )
    .build();

Netty现在似乎维护了一个与系统列表分开(以及之后)的处理程序的用户列表,而addHandlerFirst()仅将您的处理程序放在用户列表的前面。因此,它需要显式调用HttpContentDecompressor,以确保在您的处理程序插入正确的标头之后肯定可以执行它。