web-dev-qa-db-ja.com

Spring RestTemplateを使用してHTTPリクエストをZip圧縮する方法は?

HTTPをgzipで圧縮する方法requestorg.springframework.web.client.RestTemplate?によって作成されました

Spring Spring 4.2.6Boot 1.3.5を使用しています(Java SE、AndroidまたはWebブラウザーのJavascriptではありません))。

非常に大きなPOSTリクエストを行っていますが、リクエスト本文を圧縮したいと思います。

12
Bartosz Bilicki

私は2つのソリューションを提案します。1つはストリーミングなしのより単純なもので、もう1つはストリーミングをサポートするものです。

ストリーミングを必要としない場合、Spring機能であるカスタム ClientHttpRequestInterceptor を使用します。

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);
    } 
 }

getGzip I コピー

    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();
    }
}

このアプローチでは、 チャンク転送エンコーディング でリクエストが作成されます。サイズがわかっている場合は、コンテンツの長さヘッダーを設定してこれを変更できます。

ClientHttpRequestInterceptorおよび/またはカスタムClientHttpRequestFactoryアプローチの利点は、RestTemplateの任意のメソッドで機能することです。 RequestCallback を渡す別のアプローチは、 execute メソッドでのみ可能です。これは、RestTemplateの他のメソッドが内部で独自のRequestCallbackを作成して生成するためです。コンテンツ。

ところで、 サーバーでgzipリクエストを解凍するためのサポートはほとんどない があるようです。また、関連: WebRequestでgzip圧縮されたデータを送信しますか? これは Zip Bomb の問題を示しています。私はあなたがそれのために いくつかのコード を書かなければならないと思います。

12
Testo Testini

主なアイデアは、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. Spring RestTemplateを使用してgzip経由でHTTPリクエストを圧縮する簡単な例
  2. RestTemplateで大きなファイルを転送する方法は?
2
berserkk

@TestoTestiniからの上記の回答に加えて、ByteArrayOutputStreamGZIPOutputStreamの両方がcloseable()を実装しているため、Java 7+の 'try-with-resources'構文を利用すると、 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の元の回答にコメントし、上記のコード形式を保持する方法が見つかりませんでした。したがって、この回答)。

1
roj