Brotli Compressed Requests and Responses

Sending and Handling Brotli Compressed Requests and Responses in Spring Boot

Learn how to send and handle Brotli compressed requests and responses using RestTemplate in Spring Boot.

1. Introduction

In our previous articles, we covered:

    In this article, we’ll continue with Brotli compression by exploring how to send Brotli compressed requests and handle Brotli compressed responses using RestTemplate in a Spring Boot application.

    What Will You Learn?

    • How to configure RestTemplate to send Brotli compressed requests.
    • How to handle Brotli compressed responses received from external services.


    2. Setting up the Brotli Compression Library

    To use Brotli compression and decompression, we’ll need to add the Brotli4j library dependency in our pom.xml. The latest version of the dependency can be obtained from here.

    <dependency>
        <groupId>com.aayushatharva.brotli4j</groupId>
        <artifactId>brotli4j</artifactId>
        <version>1.17.0</version>
    </dependency>
    
    pom.xml

    This dependency allows us to easily compress and decompress data using Brotli encoding. Once added, we can start implementing Brotli compression for our requests and responses.

    3. Creating the Mock Controller for Brotli Compression Testing

    To test our Brotli compression with RestTemplate, we’ll create a mock controller called MockBrotliController. This controller simulates a server endpoint that expects Brotli compressed request bodies and responds with Brotli compressed data.

    import com.aayushatharva.brotli4j.decoder.Decoder;
    import com.aayushatharva.brotli4j.encoder.Encoder;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestHeader;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.io.IOException;
    
    @RestController
    public class MockBrotliController {
    
        @PostMapping(value = "/mock/brotli-request-response")
        public ResponseEntity<byte[]> processCompressedData(@RequestBody byte[] compressedRequestBody, @RequestHeader HttpHeaders headers) throws IOException {
            // Step 1: Decompress the Brotli request body
            byte[] decompressedBody = Decoder.decompress(compressedRequestBody).getDecompressedData();
    
            // Here, you would typically deserialize the decompressed body into a model (e.g., Employee)
            // For simplicity, we'll just print out the decompressed string data
            String decompressedRequest = new String(decompressedBody);
            System.out.println("Decompressed Request: " + decompressedRequest);
    
    
            // Step 2: Compress the response body using Brotli
            byte[] compressedResponseBody = Encoder.compress(decompressedRequest.getBytes());
    
            // Step 3: Return Brotli-compressed response
            HttpHeaders responseHeaders = new HttpHeaders();
            responseHeaders.set("Content-Encoding", "br");  // Set Brotli encoding
            
            return ResponseEntity.ok()
                    .headers(responseHeaders)
                    .body(compressedResponseBody);
        }
    }
    MockBrotliController.java

    Explanation:

    • Decompressing Request Data: When a Brotli compressed request arrives, the Decoder.decompress() method decompresses the data.
    • Compressing Response Data: The response data is compressed back using Encoder.compress() to Brotli format, and the response header is set to Content-Encoding: br.


    4. Configuring RestTemplate for Brotli Compression

    To send Brotli compressed requests, we configure a Brotli request interceptor for RestTemplate. This interceptor compresses the request body with Brotli encoding and sets the appropriate headers.

    Code for Brotli Request Interceptor in RestTemplateConfig:

    import com.aayushatharva.brotli4j.encoder.Encoder;
    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;
    
    @Configuration
    public class RestTemplateConfig {
    
        @Bean
        public RestTemplate restTemplate(RestTemplateBuilder builder) {
            RestTemplate restTemplate = builder.build();
            restTemplate.getInterceptors().add(brotliRequestInterceptor());
            return restTemplate;
        }
    
        private ClientHttpRequestInterceptor brotliRequestInterceptor() {
            return (request, body, execution) -> {
                request.getHeaders().remove("Content-Length");
                // Compress the request body using Brotli
                byte[] compressedBody = Encoder.compress(body);
    
                // Set the Content-Encoding header to 'br'
                request.getHeaders().set("Content-Encoding", "br");
    
                // Proceed with the compressed request
                return execution.execute(request, compressedBody);
            };
        }
    }
    RestTemplateConfig.java

    Explanation:

    • Removing Content-Length Header: The Content-Length header is removed because the compressed body will have a different length than the original.
    • Compressing the Body: The interceptor uses Encoder.compress() to convert the request body to Brotli format.
    • Setting Headers: The Content-Encoding header is set to br, indicating Brotli encoding.

    With this interceptor, RestTemplate will automatically compress request bodies using Brotli whenever this configuration is used.

    5. Configuring RestTemplate to Handle Brotli Compressed Responses

    To handle Brotli compressed responses, we configure a Brotli response interceptor for RestTemplate. This interceptor uses a custom ClientHttpResponse class to decompress Brotli responses.

    Code for Brotli Response Interceptor in RestTemplateConfig:

    import com.aayushatharva.brotli4j.encoder.Encoder;
    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;
    
    @Configuration
    public class RestTemplateConfig {
    
        @Bean
        public RestTemplate restTemplate(RestTemplateBuilder builder) {
            RestTemplate restTemplate = builder.build();
            restTemplate.getInterceptors().add(brotliRequestInterceptor()); // Already covered above
            restTemplate.getInterceptors().add(brotliResponseInterceptor());
            return restTemplate;
        }
    
    
        public ClientHttpRequestInterceptor brotliResponseInterceptor() {
            return (request, body, execution) -> {
                ClientHttpResponse response = execution.execute(request, body);
                return new BrotliDecompressingClientHttpResponse(response);
            };
        }
    }
    
    RestTemplateConfig.java


    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 Brotli decompression by reading the compressed data from the response’s body, decompressing it, and processing the decompressed content as needed.

    Code for BrotliDecompressingClientHttpResponse:

    import com.aayushatharva.brotli4j.decoder.Decoder;
    import org.springframework.http.HttpStatusCode;
    import org.springframework.http.client.ClientHttpResponse;
    
    import java.io.ByteArrayInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    
    public class BrotliDecompressingClientHttpResponse implements ClientHttpResponse {
        private final ClientHttpResponse response;
        private byte[] decompressedBody;
    
        public BrotliDecompressingClientHttpResponse(ClientHttpResponse response) throws IOException {
            this.response = response;
    
            // Check if the response has 'Content-Encoding: br' header
            if (isBrotliEncoded(response)) {
                // Decompress the Brotli response body
                this.decompressedBody = Decoder.decompress(response.getBody().readAllBytes()).getDecompressedData();
            } else {
                // If not Brotli encoded, just pass through the original response body
                this.decompressedBody = response.getBody().readAllBytes();
            }
        }
    
        @Override
        public InputStream getBody() {
            return new ByteArrayInputStream(decompressedBody);
        }
    
        @Override
        public org.springframework.http.HttpHeaders getHeaders() {
            return response.getHeaders();
        }
    
        @Override
        public HttpStatusCode getStatusCode() throws IOException {
            return response.getStatusCode();
        }
    
        @Override
        public String getStatusText() throws IOException {
            return response.getStatusText();
        }
    
        @Override
        public void close() {
            response.close();
        }
    
        private boolean isBrotliEncoded(ClientHttpResponse response) {
            return response.getHeaders().getFirst("Content-Encoding") != null &&
                    response.getHeaders().getFirst("Content-Encoding").equalsIgnoreCase("br");
        }
    }
    BrotliDecompressingClientHttpResponse.java

    Explanation:

    • Checking for Brotli Encoding: The response interceptor uses BrotliDecompressingClientHttpResponse to check if Content-Encoding is set to br. If it is, the response is Brotli compressed.
    • Decompressing the Body: The response body is decompressed using Decoder.decompress() and stored for easy access.
    • Returning Decompressed Data: The getBody() method returns the decompressed response body as a stream, ready for further processing.


    5. Testing Changes with Test Controller

    Finally, let’s test our Brotli compression and decompression with a controller called BrotliTestController. This controller uses the configured RestTemplate to send a Brotli compressed requests and handle a Brotli compressed responses.

    Code for BrotliTestController:

    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 BrotliTestController {
    
        @Autowired
        private RestTemplate restTemplate;
    
        @GetMapping("/brotli-rest-template-example")
        public Employee gzipRequestResponse() {
            String url = "http://localhost:8080/mock/brotli-request-response";
            HttpHeaders headers = new HttpHeaders();
            headers.add("Content-Encoding", "br");
            headers.add("Accept-Encoding", "br");
    
            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);
    
            System.out.println("Rest template response headers");
            response.getHeaders().forEach((s, strings) -> {
                System.out.println("Header name and value: " + s + " : " + strings);
            });
    
            return response.getBody();
        }
    }
    BrotliTestController.java

    Explanation:

    • Brotli Compressed Requests: The controller sends a Brotli compressed requests to the mock endpoint.
    • Handling Compressed Response: The response body is decompressed by the BrotliDecompressingClientHttpResponse, demonstrating both Brotli request and response handling.

    6. Understanding the Flow and Output

    When we access the /brotli/rest-template-test endpoint, BrotliTestController sends a Brotli compressed request via Rest Template to the /mock/brotli-test endpoint on MockBrotliController. Here’s what happens in each step:

    1. Request Compression:
      • The BrotliRequestInterceptor intercepts the outgoing request from BrotliTestController Rest Template and compress the request body.
      • It adds the Content-Encoding: br header, informing the server that the body is Brotli compressed.
    2. Response Decompression:
      • When the MockBrotliController receives this request, it checks for Content-Encoding: br.
      • The controller decompresses the request body, processes it, and then compresses the response body back to Brotli format.
      • The response is sent with Content-Encoding: br, signaling that the response body is Brotli compressed.
    3. Handling the Response:
      • When the Brotli compressed response is received by BrotliTestController Rest Template, the BrotliDecompressingClientHttpResponse decompresses the response body.
      • Any Brotli related headers in the response, such as Content-Encoding: br, are accessible and printed in the console for verification.

    Output:

    When the /brotli/rest-template-test endpoint is accessed, you’ll see the following output in the console:

    Rest template response headers
    Header name and value: Content-Encoding : [br]
    Header name and value: Content-Type : [application/json]
    Header name and value: Content-Length : [30]
    Header name and value: Date : [Fri, 25 Oct 2024 20:48:57 GMT]
    Header name and value: Keep-Alive : [timeout=60]
    Header name and value: Connection : [keep-alive]
    Console

    Explanation of the Console Output

    • Content-Encoding: br: This header indicates that the response was Brotli compressed.
    • Date and Content-Type: These are typical headers showing the date of the response and the response content type.

    The output confirms that the RestTemplate successfully handled both Brotli compression for outgoing requests and Brotli decompression for incoming responses.



    7. Source Code

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

    8. Things to Consider

    Here are some important considerations to keep in mind while working with Brotli:

    • Compatibility with Client and Server: Ensure that both the client and server support Brotli compression. Verify that any external services you communicate with can handle Content-Encoding: br.
    • Error Handling for Decompression Failures: Implement graceful error handling for decompression failures due to unexpected or corrupted Brotli-encoded data. Ensure fallback mechanisms are in place, such as retrying without compression or logging errors for troubleshooting.
    • Monitoring and Logging: Implement logging for Brotli compression and decompression processes. This helps in troubleshooting issues related to data transmission and provides insights into the effectiveness of compression.
    • Cache Management: Consider how Brotli-compressed responses are cached. Ensure that your caching strategy accounts for the Content-Encoding header, as compressed responses may require different caching policies.

    9. FAQs

    How do I send Brotli compressed requests using RestTemplate in Spring Boot?

    How can I handle Brotli compressed responses using RestTemplate in Spring Boot?



    10. Conclusion

    With this setup, you can efficiently send and handle Brotli compressed requests and responses in Spring Boot using RestTemplate. This approach provides a streamlined way to reduce data transfer sizes for applications dealing with large payloads or limited bandwidth.

    11. Learn More

    #

    Interested in learning more?

    Checkout our blog on How to Enable Gzip Compression in Spring Boot



    Add a Comment

    Your email address will not be published.