Gzip Compressed Requests and Responses with RestTemplate

Sending and Handling Gzip Compressed Requests and Responses with RestTemplate in Spring Boot

Learn how to send and handle Gzip compressed requests and responses in a Spring Boot application using RestTemplate.

1. Introduction

In our previous articles, we discussed various aspects of Gzip compression in Spring Boot:

Building upon these foundations, this article will guide you on how to send Gzip compressed requests and handle Gzip compressed responses using RestTemplate in a Spring Boot application.



2. Sending Gzip Compressed Requests

First, let’s set up a RestTemplate to send Gzip compressed requests. We’ll create an interceptor to compress the request body. Assume we have an existing Spring Boot application that accepts Gzip compressed requests and sends Gzip compressed responses.

Steps:

1. RestTemplate Configuration

We’ll configure a RestTemplate bean with an interceptor for compressing request bodies.

import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;

import java.io.ByteArrayOutputStream;
import java.util.zip.GZIPOutputStream;

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
        RestTemplate restTemplate = builder.build();
        restTemplate.getInterceptors().add(gzipRequestInterceptor());
        return restTemplate;
    }

    private ClientHttpRequestInterceptor gzipRequestInterceptor() {
        return (request, body, execution) -> {
            request.getHeaders().add("Content-Encoding", "gzip");
            request.getHeaders().remove("Content-Length");

            ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
            try (GZIPOutputStream gzipStream = new GZIPOutputStream(byteStream)) {
                gzipStream.write(body);
            } catch (IOException e) {
                throw new IOException("Failed to compress request body", e);
            }
            byte[] compressedBody = byteStream.toByteArray();
            return execution.execute(request, compressedBody);
        };
    }
}
RestTemplateConfig.java

The RestTemplateConfig class configures a RestTemplate bean with a custom interceptor that compresses the request body using Gzip.

The Content-Encoding header in the request indicates to the server that the body is compressed using Gzip, prompting the server to decompress it before processing. The removal of the Content-Length header is necessary because compression changes the size of the request body, making the original length inaccurate. GZIPOutputStream is used to compress the request body by writing the original data into a stream that outputs compressed bytes, ensuring that the data is sent in a Gzip compressed format.

2. Sending a Gzip Compressed Request

Now, let’s use the configured RestTemplate to send a Gzip compressed request. We’ll create a simple controller that sends a request to another application endpoint.

For our demo purpose, we will send a Gzip compressed request to the application that we created in our previous article, “Conditionally Enabling Gzip Compression“. This app accepts Gzip compressed requests and, for our example, we have enabled Gzip compression for responses using simple Spring Boot application properties.

import com.bootcamptoprod.dto.Employee;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class GzipController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/gzipRequestResponse")
    public Employee gzipRequestResponse() {
    
        String url = "http://localhost:8080/decompress";
        HttpHeaders headers = new HttpHeaders();
        headers.add("Accept-Encoding", "gzip");

        Employee employee = new Employee(1, "John Doe");
        HttpEntity<Employee> entity = new HttpEntity<>(employee, headers);

        ResponseEntity<Employee> response = restTemplate.exchange(url, HttpMethod.POST, entity, Employee.class);

        response.getHeaders().forEach((s, strings) -> {
            System.out.println("Header name and value: " + s + " : " + strings);
        });

        return response.getBody();
    }
}
GzipController.java
public class Employee {

    private int id;
    private String name;
    
    // No args constructor
    // All args constructor
    // Getters and setters

}
Employee.java

3. Checking Gzip compressed request in the destination app:

For demonstration purposes, we have added logging in the destination app where the Gzip compressed request is sent using RestTemplate. This logging captures and displays all request headers, including Content-Encoding: gzip, which confirms that the request body is compressed in Gzip format.



3. Handling Gzip Compressed Responses

There are two main approaches to handle Gzip compressed responses in case of Rest template: using a custom response interceptor and using the Apache HttpClient library.

3.1 Approach 1: Using a Response Interceptor

We will create a custom ClientHttpResponse to handle Gzip decompression.

What is ClientHttpResponse?

ClientHttpResponse is an interface in Spring’s HTTP client library that represents the response returned from an HTTP request. It provides methods to access the response’s headers, status code, and body. This interface is essential when creating custom response interceptors, as it allows you to inspect and manipulate the response before it reaches the client.

For instance, we can use ClientHttpResponse to handle Gzip decompression by reading the compressed data from the response’s body, decompressing it, and processing the decompressed content as needed.

Steps:

1. Custom ClientHttpResponse Implementation

import org.springframework.http.HttpStatusCode;
import org.springframework.http.client.ClientHttpResponse;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.GZIPInputStream;

public class GzipDecompressingClientHttpResponse implements ClientHttpResponse {

    private final ClientHttpResponse response;

    public GzipDecompressingClientHttpResponse(ClientHttpResponse response) {
        this.response = response;
    }

    @Override
    public InputStream getBody() throws IOException {
        InputStream body = response.getBody();
        if (isGzipped(response)) {
            return new GZIPInputStream(new BufferedInputStream(body));
        }
        return body;
    }

    private boolean isGzipped(ClientHttpResponse response) {
        String contentEncoding = response.getHeaders().getFirst("Content-Encoding");
        return contentEncoding != null && contentEncoding.toLowerCase().contains("gzip");
    }


    @Override
    public HttpStatusCode getStatusCode() throws IOException {
        return response.getStatusCode();
    }

    @Override
    public String getStatusText() throws IOException {
        return response.getStatusText();
    }

    @Override
    public void close() {
        response.close();
    }

    @Override
    public org.springframework.http.HttpHeaders getHeaders() {
        return response.getHeaders();
    }
}
GzipDecompressingClientHttpResponse.java

The class GzipDecompressingClientHttpResponse wraps an existing ClientHttpResponse to handle Gzip decompression of the response body. When the getBody() method is called, it checks if the response is Gzip encoded by examining the Content-Encoding header. If the response is Gzip compressed, it wraps the input stream of the response body with a GZIPInputStream to decompress the data; otherwise, it returns the body as-is. The class also provides standard methods for accessing the status code, status text, headers, and closing the response, all of which are delegated to the wrapped ClientHttpResponse instance.

2. Adding the Response Interceptor

Update the RestTemplate configuration to include the response interceptor.

import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.RestTemplate;

import java.io.ByteArrayOutputStream;
import java.util.zip.GZIPOutputStream;

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
        RestTemplate restTemplate = builder.build();
        restTemplate.getInterceptors().add(gzipRequestInterceptor()); // already covered above
        restTemplate.getInterceptors().add(gzipResponseInterceptor());
        return restTemplate;
    }

    private ClientHttpRequestInterceptor gzipResponseInterceptor() {
        return (request, body, execution) -> {
            ClientHttpResponse response = execution.execute(request, body);
            return new GzipDecompressingClientHttpResponse(response);
        };
    }

    // gzipRequestInterceptor() remains the same as covered above
}
RestTemplateConfig.java

The RestTemplateConfig class adds a gzipResponseInterceptor to the RestTemplate configuration, which is responsible for handling GZIP compressed responses. This interceptor wraps the ClientHttpResponse received from the server with the GzipDecompressingClientHttpResponse class. This custom response wrapper checks if the response is Gzip compressed and, if so, uses GZIPInputStream to decompress the response body, allowing the application to process the data in its original, uncompressed form.

Output:

When a request is sent to /gzipRequestResponse (endpoint we created earlier), it requests a Gzip response by including the Accept-Encoding: gzip header. The response headers, which are printed out, show that the rest. template response is indeed Gzip compressed, as indicated by the Content-Encoding: gzip header. The output will include details such as Content-Encoding: gzip, confirming the compression, along with other headers like Content-Type, Transfer-Encoding, and Date, providing additional context about the response.



3.2 Approach 2: Using Apache HttpClient

Apache HttpClient can automatically handle Gzip responses, making it a convenient alternative. In this case, we don’t have to create or add any additional response interceptor.

What is Apache HttpClient?

Apache HttpClient is a powerful Java library for handling HTTP requests and responses, part of the Apache HttpComponents project. It simplifies making various types of HTTP requests and managing responses, supports advanced features like connection pooling, authentication, and automatic cookie handling.

Notably, it can automatically handle Gzip compressed responses, meaning it will decompress Gzip encoded data seamlessly, making it a convenient choice for applications that need to manage HTTP communication efficiently and with minimal manual handling of compression.

Steps:

1. Add Dependency

Add the Apache HttpClient dependency to your pom.xml.

<dependency>
    <groupId>org.apache.httpcomponents.client5</groupId>
    <artifactId>httpclient5</artifactId>
</dependency>
pom.xml

2. Configuring RestTemplate with Apache HttpClient

import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

import java.io.ByteArrayOutputStream;
import java.util.zip.GZIPOutputStream;

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
        RestTemplate restTemplate = builder.build();
        restTemplate.getInterceptors().add(gzipRequestInterceptor()); // already covered above
       
        restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(HttpClientBuilder.create().build()));
        return restTemplate;
    }

    // gzipRequestInterceptor() remains the same as covered above
}
RestTemplateConfig.java

The HttpComponentsClientHttpRequestFactory allows RestTemplate to utilize Apache HttpClient for making HTTP requests. This configuration enables automatic handling of features such as Gzip decompression, meaning the RestTemplate can seamlessly manage compressed responses without additional custom interceptors for that purpose.



4. Source Code

The complete source code of the above examples can be found here.

5. Things to Consider

Here are some important considerations to keep in mind when working with gzip:

  • Accept-Encoding Header: Ensure the request includes Accept-Encoding: gzip to indicate support for compressed responses.
  • Content-Encoding Header: Set Content-Encoding: gzip in the request to inform the server that the request body is compressed.
  • Remove Content-Length Header: Remove the Content-Length header before sending a Gzip compressed request, as it will be recalculated after compression.
  • Check Content-Encoding for Responses: Verify the Content-Encoding header in the response to confirm if it’s Gzip compressed before decompression.
  • Decompression: Use GZIPInputStream to properly handle and decompress the response body.
  • Performance: Monitor and manage CPU and memory usage for compression and decompression, and ensure performance is maintained under load.
  • Testing: Validate Gzip handling with various payloads using tools like curl and Postman to ensure correct implementation.
  • Exception Handling: Implement robust handling for errors such as ZipException, IOException, and EOFException to avoid application crashes and provide clear error messages.


6. FAQs

What headers are important for Gzip compression?

7. Conclusion

In this article, we demonstrated how to send and handle Gzip compressed requests and responses using RestTemplate in a Spring Boot application. This can improve data transfer efficiency by reducing payload sizes.

8. Learn More

#

Interested in learning more?

Checkout our blog on How to Decompress Gzip Requests in Spring Boot



Add a Comment

Your email address will not be published.