diff --git a/spring-resttemplate-2/README.md b/spring-resttemplate-2/README.md index e1e0ba40b0..37d8ac9740 100644 --- a/spring-resttemplate-2/README.md +++ b/spring-resttemplate-2/README.md @@ -8,3 +8,4 @@ This module contains articles about Spring RestTemplate - [Proxies With RestTemplate](https://www.baeldung.com/java-resttemplate-proxy) - [A Custom Media Type for a Spring REST API](https://www.baeldung.com/spring-rest-custom-media-type) - [RestTemplate Post Request with JSON](https://www.baeldung.com/spring-resttemplate-post-json) +- [How to compress requests using the Spring RestTemplate](https://www.baeldung.com/spring-resttemplate-compressing-requests) diff --git a/spring-resttemplate-2/pom.xml b/spring-resttemplate-2/pom.xml index b1d6f60c53..2aed154be6 100644 --- a/spring-resttemplate-2/pom.xml +++ b/spring-resttemplate-2/pom.xml @@ -20,7 +20,30 @@ org.springframework.boot spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + org.springframework.boot + spring-boot-starter-jetty + + + + org.apache.httpcomponents + httpclient + + + + commons-io + commons-io + ${commons-io.version} + + org.springframework.boot spring-boot-starter-test diff --git a/spring-resttemplate-2/src/main/java/com/baeldung/compress/CompressingClientHttpRequestInterceptor.java b/spring-resttemplate-2/src/main/java/com/baeldung/compress/CompressingClientHttpRequestInterceptor.java new file mode 100644 index 0000000000..e880e8f915 --- /dev/null +++ b/spring-resttemplate-2/src/main/java/com/baeldung/compress/CompressingClientHttpRequestInterceptor.java @@ -0,0 +1,38 @@ +package com.baeldung.compress; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpRequest; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; + +import java.io.IOException; + +public class CompressingClientHttpRequestInterceptor implements ClientHttpRequestInterceptor { + + private static final Logger LOG = LoggerFactory.getLogger(CompressingClientHttpRequestInterceptor.class); + + private static final String GZIP_ENCODING = "gzip"; + + /** + * Compress a request body using Gzip and add Http headers. + * + * @param req + * @param body + * @param exec + * @return + * @throws IOException + */ + @Override + public ClientHttpResponse intercept(HttpRequest req, byte[] body, ClientHttpRequestExecution exec) + throws IOException { + LOG.info("Compressing request..."); + HttpHeaders httpHeaders = req.getHeaders(); + httpHeaders.add(HttpHeaders.CONTENT_ENCODING, GZIP_ENCODING); + httpHeaders.add(HttpHeaders.ACCEPT_ENCODING, GZIP_ENCODING); + return exec.execute(req, GzipUtils.compress(body)); + } + +} diff --git a/spring-resttemplate-2/src/main/java/com/baeldung/compress/GzipUtils.java b/spring-resttemplate-2/src/main/java/com/baeldung/compress/GzipUtils.java new file mode 100644 index 0000000000..50c565d92c --- /dev/null +++ b/spring-resttemplate-2/src/main/java/com/baeldung/compress/GzipUtils.java @@ -0,0 +1,52 @@ +package com.baeldung.compress; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +import org.apache.commons.io.IOUtils; + +public class GzipUtils { + + /** + * Gzip a string. + * + * @param text + * @return + * @throws Exception + */ + public static byte[] compress(String text) throws Exception { + return GzipUtils.compress(text.getBytes(StandardCharsets.UTF_8)); + } + + /** + * Gzip a byte array. + * + * @param body + * @return + * @throws IOException + */ + public static byte[] compress(byte[] body) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(baos)) { + gzipOutputStream.write(body); + } + return baos.toByteArray(); + } + + /** + * Decompress a Gzipped byte array to a String. + * + * @param body + * @return + * @throws IOException + */ + public static String decompress(byte[] body) throws IOException { + try (GZIPInputStream gzipInputStream = new GZIPInputStream(new ByteArrayInputStream(body))) { + return IOUtils.toString(gzipInputStream, StandardCharsets.UTF_8); + } + } +} diff --git a/spring-resttemplate-2/src/main/java/com/baeldung/compress/JettyWebServerConfiguration.java b/spring-resttemplate-2/src/main/java/com/baeldung/compress/JettyWebServerConfiguration.java new file mode 100644 index 0000000000..3ac8c31ab3 --- /dev/null +++ b/spring-resttemplate-2/src/main/java/com/baeldung/compress/JettyWebServerConfiguration.java @@ -0,0 +1,38 @@ +package com.baeldung.compress; + +import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.server.handler.gzip.GzipHandler; +import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Configure Jetty web server so it handles compressed requests. + */ +@Configuration +public class JettyWebServerConfiguration { + + private static final int MIN_BYTES = 1; + + /** + * Customise the Jetty web server to automatically decompress requests. + */ + @Bean + public JettyServletWebServerFactory jettyServletWebServerFactory() { + + JettyServletWebServerFactory factory = new JettyServletWebServerFactory(); + factory.addServerCustomizers(server -> { + + GzipHandler gzipHandler = new GzipHandler(); + // Enable request decompression + gzipHandler.setInflateBufferSize(MIN_BYTES); + gzipHandler.setHandler(server.getHandler()); + + HandlerCollection handlerCollection = new HandlerCollection(gzipHandler); + server.setHandler(handlerCollection); + }); + + return factory; + } + +} diff --git a/spring-resttemplate-2/src/main/java/com/baeldung/compress/Message.java b/spring-resttemplate-2/src/main/java/com/baeldung/compress/Message.java new file mode 100644 index 0000000000..f43d06c2fc --- /dev/null +++ b/spring-resttemplate-2/src/main/java/com/baeldung/compress/Message.java @@ -0,0 +1,29 @@ +package com.baeldung.compress; + +public class Message { + + private String text; + + public Message() { + } + + public Message(String text) { + this.text = text; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("Message {"); + sb.append("text='").append(text).append('\''); + sb.append('}'); + return sb.toString(); + } +} diff --git a/spring-resttemplate-2/src/main/java/com/baeldung/compress/MessageController.java b/spring-resttemplate-2/src/main/java/com/baeldung/compress/MessageController.java new file mode 100644 index 0000000000..ec574d9dec --- /dev/null +++ b/spring-resttemplate-2/src/main/java/com/baeldung/compress/MessageController.java @@ -0,0 +1,38 @@ +package com.baeldung.compress; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +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.util.Map; + +@RestController +public class MessageController { + + protected static final String PROCESSED = "Processed "; + + protected static final String REQUEST_MAPPING = "process"; + + private static final Logger LOG = LoggerFactory.getLogger(MessageController.class); + + /** + * A simple endpoint that responds with "Processed " + supplied Message content. + * + * @param headers + * @param message + * @return + */ + @PostMapping(value = REQUEST_MAPPING) + public ResponseEntity processMessage(@RequestHeader Map headers, + @RequestBody Message message) { + + // Print headers + headers.forEach((k, v) -> LOG.info(k + "=" + v)); + + return ResponseEntity.ok(PROCESSED + message.getText()); + } +} diff --git a/spring-resttemplate-2/src/main/java/com/baeldung/compress/RestTemplateConfiguration.java b/spring-resttemplate-2/src/main/java/com/baeldung/compress/RestTemplateConfiguration.java new file mode 100644 index 0000000000..12b1e4249e --- /dev/null +++ b/spring-resttemplate-2/src/main/java/com/baeldung/compress/RestTemplateConfiguration.java @@ -0,0 +1,21 @@ +package com.baeldung.compress; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class RestTemplateConfiguration { + + /** + * A RestTemplate that compresses requests. + * + * @return RestTemplate + */ + @Bean + public RestTemplate getRestTemplate() { + RestTemplate restTemplate = new RestTemplate(); + restTemplate.getInterceptors().add(new CompressingClientHttpRequestInterceptor()); + return restTemplate; + } +} diff --git a/spring-resttemplate-2/src/main/java/com/baeldung/compress/SpringCompressRequestApplication.java b/spring-resttemplate-2/src/main/java/com/baeldung/compress/SpringCompressRequestApplication.java new file mode 100644 index 0000000000..9ff88ab257 --- /dev/null +++ b/spring-resttemplate-2/src/main/java/com/baeldung/compress/SpringCompressRequestApplication.java @@ -0,0 +1,15 @@ +package com.baeldung.compress; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +@EnableAutoConfiguration +public class SpringCompressRequestApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringCompressRequestApplication.class, args); + } + +} diff --git a/spring-resttemplate-2/src/test/java/com/baeldung/compress/GzipUtilsUnitTest.java b/spring-resttemplate-2/src/test/java/com/baeldung/compress/GzipUtilsUnitTest.java new file mode 100644 index 0000000000..10c2eeb748 --- /dev/null +++ b/spring-resttemplate-2/src/test/java/com/baeldung/compress/GzipUtilsUnitTest.java @@ -0,0 +1,19 @@ +package com.baeldung.compress; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class GzipUtilsUnitTest { + + @Test + public void givenCompressedText_whenDecompressed_thenSuccessful() throws Exception { + final String expectedText = "Hello Baeldung!"; + byte[] compressedText = GzipUtils.compress(expectedText); + String decompressedText = GzipUtils.decompress(compressedText); + assertNotNull(compressedText); + assertEquals(expectedText, decompressedText); + } + +} \ No newline at end of file diff --git a/spring-resttemplate-2/src/test/java/com/baeldung/compress/MessageControllerUnitTest.java b/spring-resttemplate-2/src/test/java/com/baeldung/compress/MessageControllerUnitTest.java new file mode 100644 index 0000000000..643e3f6881 --- /dev/null +++ b/spring-resttemplate-2/src/test/java/com/baeldung/compress/MessageControllerUnitTest.java @@ -0,0 +1,56 @@ +package com.baeldung.compress; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.client.RestTemplate; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class MessageControllerUnitTest { + + private static final Logger LOG = LoggerFactory.getLogger(MessageControllerUnitTest.class); + + @Autowired + private RestTemplate restTemplate; + + @LocalServerPort + private int randomServerPort; + + /** + * As a further test you can intercept the request body, using a tool like + * Wireshark, to see the request body is actually gzipped. + * + * @throws Exception + */ + @Test + public void givenRestTemplate_whenPostCompressedRequest_thenRespondsSuccessfully() throws Exception { + + final String text = "Hello Baeldung!"; + Message message = new Message(text); + + HttpEntity request = new HttpEntity<>(message); + String uri = String.format("http://localhost:%s/%s", randomServerPort, MessageController.REQUEST_MAPPING); + + ResponseEntity responseEntity = restTemplate.postForEntity(uri, request, String.class); + + String response = responseEntity.getBody(); + LOG.info("Got response [{}]", response); + + assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); + assertNotNull(response); + assertEquals(MessageController.PROCESSED + text, response); + } + +}