如何使用 Spring RestTemplate 压缩 HTTP 请求?

How to zip- compress HTTP request with Spring RestTemplate?

如何 gzip HTTP 请求,由 org.springframework.web.client.RestTemplate?

创建

我正在使用 Spring 4.2.6 和 Spring Boot 1.3.5(Java SE,而不是网络浏览器中的 Android 或 Javascript) .

我正在发出一些非常大的 POST 请求,我希望 请求正文 被压缩。

我提出两种解决方案,一种不带流式传输的更简单,一种支持流式传输。

如果您不需要流式传输,请使用自定义 ClientHttpRequestInterceptor、Spring 功能。

RestTemplate rt = new RestTemplate();
rt.setInterceptors(Collections.singletonList(interceptor));

其中 interceptor 可能是:

ClientHttpRequestInterceptor interceptor = new ClientHttpRequestInterceptor() {

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
            throws IOException {
        request.getHeaders().add("Content-Encoding", "gzip");
        byte[] gzipped = getGzip(body);
        return execution.execute(request, gzipped);
    } 
 }

getGzipcopied

    private byte[] getGzip(byte[] body) throws IOException {

        ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
        try {
            GZIPOutputStream zipStream = new GZIPOutputStream(byteStream);
            try {
                zipStream.write(body);
            } finally {
                zipStream.close();
            }
        } finally {
            byteStream.close();
        }

        byte[] compressedData = byteStream.toByteArray();
        return compressedData;

    }

配置拦截器后,所有请求都将被压缩。

这种方法的缺点是它不支持流式传输,因为 ClientHttpRequestInterceptor 接收内容作为 byte[]

如果您需要流式传输 创建自定义 ClientHttpRequestFactory,说 GZipClientHttpRequestFactory,然后像这样使用它:

    SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
    requestFactory.setBufferRequestBody(false);
    ClientHttpRequestFactory gzipRequestFactory = new GZipClientHttpRequestFactory(requestFactory);
    RestTemplate rt = new RestTemplate(gzipRequestFactory);

其中 GZipClientHttpRequestFactory 是:

public class GZipClientHttpRequestFactory extends AbstractClientHttpRequestFactoryWrapper {

    public GZipClientHttpRequestFactory(ClientHttpRequestFactory requestFactory) {
        super(requestFactory);
    }

    @Override
    protected ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod, ClientHttpRequestFactory requestFactory)
            throws IOException {
        ClientHttpRequest delegate = requestFactory.createRequest(uri, httpMethod);
        return new ZippedClientHttpRequest(delegate);
    }

}

ZippedClientHttpRequest是:

public class ZippedClientHttpRequest extends WrapperClientHttpRequest
{
    private GZIPOutputStream zip;

    public ZippedClientHttpRequest(ClientHttpRequest delegate) {
        super(delegate);
        delegate.getHeaders().add("Content-Encoding", "gzip");
        // here or in getBody could add content-length to avoid chunking
        // but is it available ? 
        // delegate.getHeaders().add("Content-Length", "39");

    }

    @Override
    public OutputStream getBody() throws IOException {
        final OutputStream body = super.getBody();
        zip = new GZIPOutputStream(body);
        return zip;
    }

    @Override
    public ClientHttpResponse execute() throws IOException {
        if (zip!=null) zip.close();
        return super.execute();
    }

}

最后 WrapperClientHttpRequest 是:

public class WrapperClientHttpRequest implements ClientHttpRequest {

    private final ClientHttpRequest delegate;

    protected WrapperClientHttpRequest(ClientHttpRequest delegate) {
        super();
        if (delegate==null)
            throw new IllegalArgumentException("null delegate");
        this.delegate = delegate;
    }

    protected final ClientHttpRequest getDelegate() {
        return delegate;
    }

    @Override
    public OutputStream getBody() throws IOException {
        return delegate.getBody();
    }

    @Override
    public HttpHeaders getHeaders() {
        return delegate.getHeaders();
    }

    @Override
    public URI getURI() {
        return delegate.getURI();
    }

    @Override
    public HttpMethod getMethod() {
        return delegate.getMethod();
    }

    @Override
    public ClientHttpResponse execute() throws IOException {
        return delegate.execute();
    }
}

此方法使用 chunked transfer encoding 创建一个请求,如果大小已知,可以更改设置内容长度 header。

ClientHttpRequestInterceptor and/or 自定义 ClientHttpRequestFactory 方法的优势在于它适用于 RestTemplate 的任何方法。另一种方法,传递 RequestCallback is possible only with execute 方法,这是因为 RestTemplate 的其他方法在内部创建它们自己的 RequestCallback(s) 来生成内容。

顺便说一句,似乎有 little support to decompress gzip request on the server. Also related: Sending gzipped data in WebRequest? that points to the Zip Bomb issue. I think you will have to write some code

根据@TestoTestini 的上述回答,如果我们利用 Java 7+ 的 'try-with-resources' 语法(因为 ByteArrayOutputStreamGZIPOutputStream 实现可关闭() ) 然后我们可以将 getGzip 函数缩小为以下内容:

private byte[] getGzip(byte[] body) throws IOException {

    try (ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
         GZIPOutputStream zipStream = new GZIPOutputStream(byteStream)) {
            zipStream.write(body);
        byte[] compressedData = byteStream.toByteArray();
        return compressedData;
    }

}

(我找不到评论@TestoTestini 的原始答案并保留上述代码格式的方法,因此这个答案)。

因为我不能评论@roj 的 post 我在这里写一个答案。

@roj 片段虽然很简洁,但实际上与@Testo Testini 的片段不一样。

德图关闭流之前: byteStream.toByteArray(); 在@rog 回答中,这发生在 stream.close() 之前,因为流位于 try/resource 块中。

如果需要使用try-with-resources,zipStream应该在byteStream.toByteArray();

之前关闭

完整的片段应该是:

private byte[] getGzip(byte[] body) throws IOException {

    try (ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
         GZIPOutputStream zipStream = new GZIPOutputStream(byteStream)) {
            
        zipStream.write(body);
        zipStream.close();

        byte[] compressedData = byteStream.toByteArray();
        return compressedData;
    }

}

出现错误(“压缩文件在到达 end-of-stream 标记之前结束”)并且上面修复了我的错误,我认为我应该分享这个。