복붙노트

[SPRING] Spring RestTemplate으로 HTTP 요청을 압축하는 방법은 무엇입니까?

SPRING

Spring RestTemplate으로 HTTP 요청을 압축하는 방법은 무엇입니까?

org.springframework.web.client.RestTemplate에 의해 생성 된 HTTP 요청을 gzip하는 방법?

스프링 부트 1.3.5 (Java SE, 안드로이드 또는 웹 브라우저의 Javascript가 아닌)와 함께 Spring 4.2.6을 사용하고 있습니다.

정말 큰 POST 요청을 만들고 있는데 요청 본문을 압축하려고합니다.

해결법

  1. ==============================

    1.스트리밍이없는 간단한 솔루션과 스트리밍을 지원하는 솔루션을 제안합니다.

    스트리밍이없는 간단한 솔루션과 스트리밍을 지원하는 솔루션을 제안합니다.

    스트리밍을 필요로하지 않는다면 Spring의 커스텀 ClientHttpRequestInterceptor를 사용하십시오.

    RestTemplate rt = new RestTemplate();
    rt.setInterceptors(Collections.singletonList(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 복사했습니다.

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

    GZip ClientHttpRequestFactory는 다음과 같습니다.

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

    그리고 압축 된 ClientHttpRequest는 다음과 같습니다.

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

    마지막으로 Wrapper ClientHttpRequest는 다음과 같습니다.

    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을 작성하기 때문입니다.

    BTW 그것은 서버에 gzip 요청 압축을 거의 지원하지 않는 것 같습니다. 관련 항목 : WebRequest에서 gzipped 데이터 보내기? Zip Bomb 문제를 지적합니다. 나는 당신이 그것에 대한 몇 가지 코드를 작성해야 할 것이라고 생각합니다.

  2. ==============================

    2.주요 아이디어는 requestCallback을 생성하는 것입니다. requestCallback은 gzipOutputStream에서 보내려는 데이터를 요청 스트림에 직접 복사합니다.

    주요 아이디어는 requestCallback을 생성하는 것입니다. requestCallback은 gzipOutputStream에서 보내려는 데이터를 요청 스트림에 직접 복사합니다.

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

    모래밭:

  3. ==============================

    3.위의 답변에서 @TestoTestini에서 ByteArrayOutputStream과 GZIPOutputStream이 closeable ()을 구현하기 때문에 Java 7 +의 'try-with-resources'구문을 활용하면 getGzip 함수를 다음과 같이 축소 할 수 있습니다.

    위의 답변에서 @TestoTestini에서 ByteArrayOutputStream과 GZIPOutputStream이 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의 원래 답변에 대해 언급하고 위의 코드 형식을 유지하는 방법을 찾을 수 없으므로이 답변을 참조하십시오).

  4. from https://stackoverflow.com/questions/37415294/how-to-zip-compress-http-request-with-spring-resttemplate by cc-by-sa and MIT license