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

时间:2016-05-24 13:36:45

标签: java spring http http-compression

如何gzip由org.springframework.web.client.RestTemplate?

创建的HTTP 请求

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

我正在制作一些非常大的POST请求,我希望压缩请求正文

4 个答案:

答案 0 :(得分:12)

我提出了两个解决方案,一个更简单,没有流媒体,另一个支持流媒体。

如果您不需要流媒体,请使用自定义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创建请求,如果已知大小,则可以更改此设置内容长度标头。

ClientHttpRequestInterceptor和/或自定义ClientHttpRequestFactory方法的优点是它可以与RestTemplate的任何方法一起使用。另一种方法是,仅使用RequestCallback方法传递execute,这是因为RestTemplate的其他方法在内部创建了自己的RequestCallback来生成内容。

BTW似乎有little support to decompress gzip request on the server。还相关:Sending gzipped data in WebRequest?指向Zip Bomb问题。我想你必须为它写some code

答案 1 :(得分:2)

主要想法是创建requestCallback,它会将您要从gzipOutputStream直接发送的数据直接复制到request流。

RequestCallback requestCallback = new RequestCallback() {
    @Override
    public void doWithRequest(ClientHttpRequest request) throws IOException {
        GZIPOutputStream gzipOutputStream;
        try {
            gzipOutputStream = new GZIPOutputStream(request.getBody());
        } catch (IOException ignored) {
            return;
        }

        request.getHeaders().add("Content-Type", "application/octet-stream");
        request.getHeaders().add("Content-Encoding", "gzip");

        try {
            String data = "Test data.";
            gzipOutputStream.write(data.getBytes(StandardCharsets.UTF_8));
            gzipOutputStream.flush(); // Optional in this example.
            gzipOutputStream.finish();
        } catch (IOException ignored) {
        }
    }
};

现在您可以在下一个方式使用它:

RestTemplate restTemplate = new RestTemplate();
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
restTemplate.setRequestFactory(requestFactory);

ResponseExtractor<String> responseExtractor = new HttpMessageConverterExtractor<>(String.class,
        restTemplate.getMessageConverters());
String response = restTemplate.execute("http://localhost:8080/gzip.php", HttpMethod.POST, requestCallback,
        responseExtractor);

System.out.println(response);

链接:

  1. Simple example of compressing HTTP request via gzip with using Spring RestTemplate
  2. How to forward large files with RestTemplate?

答案 2 :(得分:1)

进一步来自@TestoTestini的上述答案,如果我们利用Java 7+的“ try-with-resources”语法,因为ByteArrayOutputStreamGZIPOutputStream都实现了closeable(),我们可以将getGzip函数缩小为以下内容:

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

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

}

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

答案 3 :(得分:0)

由于我无法评论@roj的帖子,因此我在这里写一个答案。

@roj代码段虽然很简洁,但实际上与@Testo Testini的代码段不同。

Testo在关闭流之前: 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;
    }

}

出现错误(“压缩文件在到达流结束标记之前已结束”),以上内容解决了我的错误,我认为我应该与大家分享。