From 8bdfaf2c3d619dad5f882e2b7524904a2ec8760c Mon Sep 17 00:00:00 2001 From: Jakub Narloch Date: Tue, 18 Aug 2015 20:21:26 +0200 Subject: [PATCH] Feign request/response gzip compression --- .../encoding/BaseRequestInterceptor.java | 61 ++++++++++ ...gnAcceptGzipEncodingAutoConfiguration.java | 48 ++++++++ .../FeignAcceptGzipEncodingInterceptor.java | 49 ++++++++ .../FeignClientEncodingProperties.java | 40 +++++++ ...nContentGzipEncodingAutoConfiguration.java | 48 ++++++++ .../FeignContentGzipEncodingInterceptor.java | 111 ++++++++++++++++++ .../netflix/feign/encoding/HttpEncoding.java | 55 +++++++++ .../main/resources/META-INF/spring.factories | 2 + .../cloud/netflix/feign/encoding/Demo.java | 99 ++++++++++++++++ .../encoding/FeignAcceptEncodingTest.java | 96 +++++++++++++++ .../encoding/FeignContentEncodingTest.java | 99 ++++++++++++++++ .../netflix/feign/encoding/Invoices.java | 43 +++++++ .../encoding/app/client/InvoiceClient.java | 42 +++++++ .../feign/encoding/app/domain/Invoice.java | 47 ++++++++ .../app/resource/InvoiceResource.java | 63 ++++++++++ .../src/test/resources/application.yml | 4 + 16 files changed, 907 insertions(+) create mode 100644 spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/encoding/BaseRequestInterceptor.java create mode 100644 spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/encoding/FeignAcceptGzipEncodingAutoConfiguration.java create mode 100644 spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/encoding/FeignAcceptGzipEncodingInterceptor.java create mode 100644 spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/encoding/FeignClientEncodingProperties.java create mode 100644 spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/encoding/FeignContentGzipEncodingAutoConfiguration.java create mode 100644 spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/encoding/FeignContentGzipEncodingInterceptor.java create mode 100644 spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/encoding/HttpEncoding.java create mode 100644 spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/encoding/Demo.java create mode 100644 spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/encoding/FeignAcceptEncodingTest.java create mode 100644 spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/encoding/FeignContentEncodingTest.java create mode 100644 spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/encoding/Invoices.java create mode 100644 spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/encoding/app/client/InvoiceClient.java create mode 100644 spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/encoding/app/domain/Invoice.java create mode 100644 spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/encoding/app/resource/InvoiceResource.java diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/encoding/BaseRequestInterceptor.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/encoding/BaseRequestInterceptor.java new file mode 100644 index 00000000..c374e9a8 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/encoding/BaseRequestInterceptor.java @@ -0,0 +1,61 @@ +/* + * Copyright 2013-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.netflix.feign.encoding; + +import feign.RequestInterceptor; +import feign.RequestTemplate; +import lombok.AccessLevel; +import lombok.Getter; +import org.springframework.util.Assert; + +/** + * The base request interceptor. + * + * @author Jakub Narloch + */ +public abstract class BaseRequestInterceptor implements RequestInterceptor { + + /** + * The encoding properties. + */ + @Getter(AccessLevel.PROTECTED) + private final FeignClientEncodingProperties properties; + + /** + * Creates new instance of {@link BaseRequestInterceptor}. + * + * @param properties the encoding properties + */ + protected BaseRequestInterceptor(FeignClientEncodingProperties properties) { + Assert.notNull(properties, "Properties can not be null"); + this.properties = properties; + } + + /** + * Adds the header if it wasn't yet specified. + * + * @param requestTemplate the request + * @param name the header name + * @param values the header values + */ + protected void addHeader(RequestTemplate requestTemplate, String name, String... values) { + + if (!requestTemplate.headers().containsKey(name)) { + requestTemplate.header(name, values); + } + } +} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/encoding/FeignAcceptGzipEncodingAutoConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/encoding/FeignAcceptGzipEncodingAutoConfiguration.java new file mode 100644 index 00000000..3144343d --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/encoding/FeignAcceptGzipEncodingAutoConfiguration.java @@ -0,0 +1,48 @@ +/* + * Copyright 2013-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.netflix.feign.encoding; + +import feign.Feign; +import feign.httpclient.ApacheHttpClient; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.netflix.feign.FeignAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Configures the Feign response compression. + * + * @author Jakub Narloch + * @see FeignAcceptGzipEncodingInterceptor + */ +@Configuration +@EnableConfigurationProperties(FeignClientEncodingProperties.class) +@ConditionalOnClass(Feign.class) +@ConditionalOnBean(ApacheHttpClient.class) +@ConditionalOnProperty(value = "feign.compression.response.enabled", matchIfMissing = false) +@AutoConfigureBefore(FeignAutoConfiguration.class) +public class FeignAcceptGzipEncodingAutoConfiguration { + + @Bean + public FeignAcceptGzipEncodingInterceptor feignAcceptGzipEncodingInterceptor(FeignClientEncodingProperties properties) { + return new FeignAcceptGzipEncodingInterceptor(properties); + } +} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/encoding/FeignAcceptGzipEncodingInterceptor.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/encoding/FeignAcceptGzipEncodingInterceptor.java new file mode 100644 index 00000000..bc822457 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/encoding/FeignAcceptGzipEncodingInterceptor.java @@ -0,0 +1,49 @@ +/* + * Copyright 2013-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.netflix.feign.encoding; + +import feign.RequestTemplate; + +/** + * Enables the HTTP response payload compression by specifying the {@code Accept-Encoding} headers. + * Although this does not yet mean that the requests will be compressed, it requires the remote server + * to understand the header and be configured to compress responses. Still no all responses might be compressed + * based on the media type matching and other factors like the response content length. + * + * @author Jakub Narloch + */ +public class FeignAcceptGzipEncodingInterceptor extends BaseRequestInterceptor { + + /** + * Creates new instance of {@link FeignAcceptGzipEncodingInterceptor}. + * + * @param properties the encoding properties + */ + protected FeignAcceptGzipEncodingInterceptor(FeignClientEncodingProperties properties) { + super(properties); + } + + /** + * {@inheritDoc} + */ + @Override + public void apply(RequestTemplate template) { + + addHeader(template, HttpEncoding.ACCEPT_ENCODING_HEADER, HttpEncoding.GZIP_ENCODING, + HttpEncoding.DEFLATE_ENCODING); + } +} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/encoding/FeignClientEncodingProperties.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/encoding/FeignClientEncodingProperties.java new file mode 100644 index 00000000..7635002f --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/encoding/FeignClientEncodingProperties.java @@ -0,0 +1,40 @@ +/* + * Copyright 2013-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.netflix.feign.encoding; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * The Feign encoding properties. + * + * @author Jakub Narloch + */ +@Data +@ConfigurationProperties("feign.compression.request") +public class FeignClientEncodingProperties { + + /** + * The list of supported mime types. + */ + private String[] mimeTypes = new String[]{"text/xml", "application/xml", "application/json"}; + + /** + * The minimum threshold content size. + */ + private int minRequestSize = 2048; +} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/encoding/FeignContentGzipEncodingAutoConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/encoding/FeignContentGzipEncodingAutoConfiguration.java new file mode 100644 index 00000000..a9dd22b7 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/encoding/FeignContentGzipEncodingAutoConfiguration.java @@ -0,0 +1,48 @@ +/* + * Copyright 2013-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.netflix.feign.encoding; + +import feign.Feign; +import feign.httpclient.ApacheHttpClient; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.netflix.feign.FeignAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Configures the Feign request compression. + * + * @author Jakub Narloch + * @see FeignContentGzipEncodingInterceptor + */ +@Configuration +@EnableConfigurationProperties(FeignClientEncodingProperties.class) +@ConditionalOnClass(Feign.class) +@ConditionalOnBean(ApacheHttpClient.class) +@ConditionalOnProperty(value = "feign.compression.request.enabled", matchIfMissing = false) +@AutoConfigureBefore(FeignAutoConfiguration.class) +public class FeignContentGzipEncodingAutoConfiguration { + + @Bean + public FeignContentGzipEncodingInterceptor feignContentGzipEncodingInterceptor(FeignClientEncodingProperties properties) { + return new FeignContentGzipEncodingInterceptor(properties); + } +} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/encoding/FeignContentGzipEncodingInterceptor.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/encoding/FeignContentGzipEncodingInterceptor.java new file mode 100644 index 00000000..74bfdadf --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/encoding/FeignContentGzipEncodingInterceptor.java @@ -0,0 +1,111 @@ +/* + * Copyright 2013-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.netflix.feign.encoding; + +import feign.RequestTemplate; + +import java.util.Collection; +import java.util.Map; + +/** + * Enables the HTTP request payload compression by specifying the {@code Content-Encoding} headers. + * + * @author Jakub Narloch + */ +public class FeignContentGzipEncodingInterceptor extends BaseRequestInterceptor { + + /** + * Creates new instance of {@link FeignContentGzipEncodingInterceptor}. + * + * @param properties the encoding properties + */ + protected FeignContentGzipEncodingInterceptor(FeignClientEncodingProperties properties) { + super(properties); + } + + /** + * {@inheritDoc} + */ + @Override + public void apply(RequestTemplate template) { + + if (requiresCompression(template)) { + addHeader(template, HttpEncoding.CONTENT_ENCODING_HEADER, HttpEncoding.GZIP_ENCODING, + HttpEncoding.DEFLATE_ENCODING); + } + } + + /** + * Returns whether the request requires GZIP compression. + * + * @param template the request template + * @return true if request requires compression, false otherwise + */ + private boolean requiresCompression(RequestTemplate template) { + + final Map> headers = template.headers(); + return matchesMimeType(headers.get(HttpEncoding.CONTENT_TYPE)) + && contentLengthExceedThreshold(headers.get(HttpEncoding.CONTENT_LENGTH)); + } + + /** + * Returns whether the request content length exceed configured minimum size. + * + * @param contentLength the content length header value + * @return true if length is grater than minimum size, false otherwise + */ + private boolean contentLengthExceedThreshold(Collection contentLength) { + + try { + if (contentLength.size() != 1) { + return false; + } + + final String strLen = contentLength.iterator().next(); + final long length = Long.parseLong(strLen); + return length > getProperties().getMinRequestSize(); + } catch (NumberFormatException ex) { + // ignores the exception + } + return false; + } + + /** + * Returns whether the content mime types matches the configures mime types. + * + * @param contentTypes the content types + * @return true if any specified content type matches the request content types + */ + private boolean matchesMimeType(Collection contentTypes) { + if (contentTypes == null || contentTypes.size() == 0) { + return false; + } + + if (getProperties().getMimeTypes() == null || getProperties().getMimeTypes().length == 0) { + // no specific mime types has been set - matching everything + return true; + } + + for (String mimeType : getProperties().getMimeTypes()) { + if (contentTypes.contains(mimeType)) { + return true; + } + } + + return false; + } +} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/encoding/HttpEncoding.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/encoding/HttpEncoding.java new file mode 100644 index 00000000..380b02e6 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/feign/encoding/HttpEncoding.java @@ -0,0 +1,55 @@ +/* + * Copyright 2013-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.netflix.feign.encoding; + +/** + * Lists all constants used by Feign encoders. + * + * @author Jakub Narloch + */ +public interface HttpEncoding { + + /** + * The HTTP Content-Length header. + */ + String CONTENT_LENGTH = "Content-Length"; + + /** + * The HTTP Content-Type header. + */ + String CONTENT_TYPE = "Content-Type"; + + /** + * The HTTP Accept-Encoding header. + */ + String ACCEPT_ENCODING_HEADER = "Accept-Encoding"; + + /** + * The HTTP Content-Encoding header. + */ + String CONTENT_ENCODING_HEADER = "Content-Encoding"; + + /** + * The GZIP encoding. + */ + String GZIP_ENCODING = "gzip"; + + /** + * The Deflate encoding. + */ + String DEFLATE_ENCODING = "deflate"; +} diff --git a/spring-cloud-netflix-core/src/main/resources/META-INF/spring.factories b/spring-cloud-netflix-core/src/main/resources/META-INF/spring.factories index 8e19fd89..c1dc0641 100644 --- a/spring-cloud-netflix-core/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-netflix-core/src/main/resources/META-INF/spring.factories @@ -4,6 +4,8 @@ org.springframework.cloud.netflix.config.EurekaClientConfigServerAutoConfigurati org.springframework.cloud.netflix.config.DiscoveryClientConfigServiceAutoConfiguration,\ org.springframework.cloud.netflix.feign.ribbon.FeignRibbonClientAutoConfiguration,\ org.springframework.cloud.netflix.feign.FeignAutoConfiguration,\ +org.springframework.cloud.netflix.feign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\ +org.springframework.cloud.netflix.feign.encoding.FeignContentGzipEncodingAutoConfiguration,\ org.springframework.cloud.netflix.hystrix.HystrixAutoConfiguration,\ org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\ org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration,\ diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/encoding/Demo.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/encoding/Demo.java new file mode 100644 index 00000000..9a386af6 --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/encoding/Demo.java @@ -0,0 +1,99 @@ +/* + * Copyright 2013-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.netflix.feign.encoding; + +import com.netflix.loadbalancer.BaseLoadBalancer; +import com.netflix.loadbalancer.ILoadBalancer; +import com.netflix.loadbalancer.Server; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.IntegrationTest; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.cloud.netflix.feign.EnableFeignClients; +import org.springframework.cloud.netflix.feign.encoding.app.client.InvoiceClient; +import org.springframework.cloud.netflix.feign.encoding.app.domain.Invoice; +import org.springframework.cloud.netflix.ribbon.RibbonClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Demonstrates usage of this component. + * + * @author Jakub Narloch + */ +@WebAppConfiguration +@IntegrationTest({"server.port=0","feign.compression.request.enabled=true","feign.compression.response.enabled=true"}) +@SpringApplicationConfiguration(classes = {Demo.Application.class}) +@RunWith(SpringJUnit4ClassRunner.class) +public class Demo { + + @Autowired + private InvoiceClient invoiceClient; + + @Test + public void compressedResponse() { + + // given + final List invoices = Invoices.createInvoiceList(50); + + // when + final ResponseEntity> response = invoiceClient.saveInvoices(invoices); + + // then + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertEquals(invoices.size(), response.getBody().size()); + + } + + @EnableFeignClients + @RibbonClient(name = "local", configuration = LocalRibbonClientConfiguration.class) + @ComponentScan("org.springframework.cloud.netflix.feign.encoding.app") + @EnableAutoConfiguration + @Configuration + public static class Application { + } + + @Configuration + static class LocalRibbonClientConfiguration { + + @Value("${local.server.port}") + private int port = 0; + + @Bean + public ILoadBalancer ribbonLoadBalancer() { + BaseLoadBalancer balancer = new BaseLoadBalancer(); + balancer.setServersList(Collections.singletonList(new Server("localhost", this.port))); + return balancer; + } + } +} \ No newline at end of file diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/encoding/FeignAcceptEncodingTest.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/encoding/FeignAcceptEncodingTest.java new file mode 100644 index 00000000..a832d64f --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/encoding/FeignAcceptEncodingTest.java @@ -0,0 +1,96 @@ +/* + * Copyright 2013-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.netflix.feign.encoding; + +import com.netflix.loadbalancer.BaseLoadBalancer; +import com.netflix.loadbalancer.ILoadBalancer; +import com.netflix.loadbalancer.Server; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.IntegrationTest; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.cloud.netflix.feign.EnableFeignClients; +import org.springframework.cloud.netflix.feign.encoding.app.client.InvoiceClient; +import org.springframework.cloud.netflix.feign.encoding.app.domain.Invoice; +import org.springframework.cloud.netflix.ribbon.RibbonClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Tests the response compression. + * + * @author Jakub Narloch + */ +@WebAppConfiguration +@IntegrationTest({"server.port=0","feign.compression.response.enabled=true"}) +@SpringApplicationConfiguration(classes = {FeignAcceptEncodingTest.Application.class}) +@RunWith(SpringJUnit4ClassRunner.class) +public class FeignAcceptEncodingTest { + + @Autowired + private InvoiceClient invoiceClient; + + @Test + public void compressedResponse() { + + // when + final ResponseEntity> invoices = invoiceClient.getInvoices(); + + // then + assertNotNull(invoices); + assertEquals(HttpStatus.OK, invoices.getStatusCode()); + assertNotNull(invoices.getBody()); + assertEquals(100, invoices.getBody().size()); + + } + + @EnableFeignClients + @RibbonClient(name = "local", configuration = LocalRibbonClientConfiguration.class) + @ComponentScan("org.springframework.cloud.netflix.feign.encoding.app") + @EnableAutoConfiguration + @Configuration + public static class Application { + } + + @Configuration + static class LocalRibbonClientConfiguration { + + @Value("${local.server.port}") + private int port = 0; + + @Bean + public ILoadBalancer ribbonLoadBalancer() { + BaseLoadBalancer balancer = new BaseLoadBalancer(); + balancer.setServersList(Collections.singletonList(new Server("localhost", this.port))); + return balancer; + } + } +} \ No newline at end of file diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/encoding/FeignContentEncodingTest.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/encoding/FeignContentEncodingTest.java new file mode 100644 index 00000000..a79e482d --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/encoding/FeignContentEncodingTest.java @@ -0,0 +1,99 @@ +/* + * Copyright 2013-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.netflix.feign.encoding; + +import com.netflix.loadbalancer.BaseLoadBalancer; +import com.netflix.loadbalancer.ILoadBalancer; +import com.netflix.loadbalancer.Server; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.IntegrationTest; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.cloud.netflix.feign.EnableFeignClients; +import org.springframework.cloud.netflix.feign.encoding.app.client.InvoiceClient; +import org.springframework.cloud.netflix.feign.encoding.app.domain.Invoice; +import org.springframework.cloud.netflix.ribbon.RibbonClient; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; + +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Tests the response compression. + * + * @author Jakub Narloch + */ +@WebAppConfiguration +@IntegrationTest({"server.port=0","feign.compression.request.enabled=true"}) +@SpringApplicationConfiguration(classes = {FeignContentEncodingTest.Application.class}) +@RunWith(SpringJUnit4ClassRunner.class) +public class FeignContentEncodingTest { + + @Autowired + private InvoiceClient invoiceClient; + + @Test + public void compressedResponse() { + + // given + final List invoices = Invoices.createInvoiceList(50); + + // when + final ResponseEntity> response = invoiceClient.saveInvoices(invoices); + + // then + assertNotNull(response); + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + assertEquals(invoices.size(), response.getBody().size()); + + } + + @EnableFeignClients + @RibbonClient(name = "local", configuration = LocalRibbonClientConfiguration.class) + @ComponentScan("org.springframework.cloud.netflix.feign.encoding.app") + @EnableAutoConfiguration + @Configuration + public static class Application { + } + + @Configuration + static class LocalRibbonClientConfiguration { + + @Value("${local.server.port}") + private int port = 0; + + @Bean + public ILoadBalancer ribbonLoadBalancer() { + BaseLoadBalancer balancer = new BaseLoadBalancer(); + balancer.setServersList(Collections.singletonList(new Server("localhost", this.port))); + return balancer; + } + } +} \ No newline at end of file diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/encoding/Invoices.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/encoding/Invoices.java new file mode 100644 index 00000000..3bf3e726 --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/encoding/Invoices.java @@ -0,0 +1,43 @@ +/* + * Copyright 2013-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.netflix.feign.encoding; + +import org.springframework.cloud.netflix.feign.encoding.app.domain.Invoice; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +/** + * Utility class used for testing. + * + * @author Jakub Narloch + */ +final class Invoices { + + public static List createInvoiceList(int count) { + final List invoices = new ArrayList<>(); + for (int ind = 0; ind < count; ind++) { + final Invoice invoice = new Invoice(); + invoice.setTitle("Invoice " + (ind + 1)); + invoice.setAmount(new BigDecimal(String.format(Locale.US, "%.2f", Math.random() * 1000))); + invoices.add(invoice); + } + return invoices; + } +} diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/encoding/app/client/InvoiceClient.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/encoding/app/client/InvoiceClient.java new file mode 100644 index 00000000..47e76ad9 --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/encoding/app/client/InvoiceClient.java @@ -0,0 +1,42 @@ +/* + * Copyright 2013-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.netflix.feign.encoding.app.client; + +import org.springframework.cloud.netflix.feign.FeignClient; +import org.springframework.cloud.netflix.feign.encoding.app.domain.Invoice; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import java.util.List; + +/** + * Simple Feign client for retrieving the invoice list. + * + * @author Jakub Narloch + */ +@FeignClient("local") +public interface InvoiceClient { + + @RequestMapping(value = "invoices", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) + ResponseEntity> getInvoices(); + + @RequestMapping(value = "invoices", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, + produces = MediaType.APPLICATION_JSON_VALUE) + ResponseEntity> saveInvoices(List invoices); +} diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/encoding/app/domain/Invoice.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/encoding/app/domain/Invoice.java new file mode 100644 index 00000000..56802d74 --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/encoding/app/domain/Invoice.java @@ -0,0 +1,47 @@ +/* + * Copyright 2013-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.netflix.feign.encoding.app.domain; + +import java.math.BigDecimal; + +/** + * An Invoice model - used for testing. + * + * @author Jakub Narloch + */ +public class Invoice { + + private String title; + + private BigDecimal amount; + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public BigDecimal getAmount() { + return amount; + } + + public void setAmount(BigDecimal amount) { + this.amount = amount; + } +} diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/encoding/app/resource/InvoiceResource.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/encoding/app/resource/InvoiceResource.java new file mode 100644 index 00000000..84c65f61 --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/feign/encoding/app/resource/InvoiceResource.java @@ -0,0 +1,63 @@ +/* + * Copyright 2013-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.netflix.feign.encoding.app.resource; + +import org.springframework.cloud.netflix.feign.encoding.app.domain.Invoice; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +/** + * An sample REST controller, that potentially returns large response - used for testing. + * + * @author Jakub Narloch + */ +@RestController +public class InvoiceResource { + + @RequestMapping(value = "invoices", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity> getInvoices() { + + return ResponseEntity.ok(createInvoiceList(100)); + } + + @RequestMapping(value = "invoices", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, + produces = MediaType.APPLICATION_JSON_VALUE) + ResponseEntity> saveInvoices(@RequestBody List invoices) { + + return ResponseEntity.ok(invoices); + } + + private List createInvoiceList(int count) { + final List invoices = new ArrayList<>(); + for (int ind = 0; ind < count; ind++) { + final Invoice invoice = new Invoice(); + invoice.setTitle("Invoice " + (ind + 1)); + invoice.setAmount(new BigDecimal(String.format(Locale.US, "%.2f", Math.random() * 1000))); + invoices.add(invoice); + } + return invoices; + } +} diff --git a/spring-cloud-netflix-core/src/test/resources/application.yml b/spring-cloud-netflix-core/src/test/resources/application.yml index 080dafad..e4c05322 100644 --- a/spring-cloud-netflix-core/src/test/resources/application.yml +++ b/spring-cloud-netflix-core/src/test/resources/application.yml @@ -1,5 +1,9 @@ server: port: 9999 + compression: + enabled: true + min-response-size: 1024 + mime-types: application/xml,application/json logging: level: org.springframework.web: DEBUG