From 30d92458218067dfc84f529d8bb519450b268297 Mon Sep 17 00:00:00 2001 From: Ryan Baxter Date: Tue, 20 Feb 2018 15:34:38 -0500 Subject: [PATCH] Updated from netflix --- .../main/asciidoc/spring-cloud-openfeign.adoc | 3 + .../openfeign/FeignClientFactoryBean.java | 14 ++- .../openfeign/FeignClientProperties.java | 40 ++++++++- ...pClientFeignLoadBalancedConfiguration.java | 2 +- .../OkHttpFeignLoadBalancedConfiguration.java | 2 +- .../FeignClientUsingPropertiesTests.java | 60 ++++++++++++- ...ignRibbonHttpClientConfigurationTests.java | 87 +++++++++++++++++++ ...nRibbonOkHttpClientConfigurationTests.java | 70 +++++++++++++++ .../resources/feign-properties.properties | 4 +- 9 files changed, 273 insertions(+), 9 deletions(-) create mode 100644 spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonHttpClientConfigurationTests.java create mode 100644 spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonOkHttpClientConfigurationTests.java diff --git a/docs/src/main/asciidoc/spring-cloud-openfeign.adoc b/docs/src/main/asciidoc/spring-cloud-openfeign.adoc index 81f7fea5..f6b455b1 100644 --- a/docs/src/main/asciidoc/spring-cloud-openfeign.adoc +++ b/docs/src/main/asciidoc/spring-cloud-openfeign.adoc @@ -161,6 +161,9 @@ feign: - com.example.FooRequestInterceptor - com.example.BarRequestInterceptor decode404: false + encoder: com.example.SimpleEncoder + decoder: com.example.SimpleDecoder + contract: com.example.SimpleContract ---- Default configurations can be specified in the `@EnableFeignClients` attribute `defaultConfiguration` in a similar manner as described above. The difference is that this configuration will apply to _all_ feign clients. diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java index feb6d816..6f31f4ad 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 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. @@ -179,6 +179,18 @@ class FeignClientFactoryBean implements FactoryBean, InitializingBean, builder.decode404(); } } + + if (Objects.nonNull(config.getEncoder())) { + builder.encoder(getOrInstantiate(config.getEncoder())); + } + + if (Objects.nonNull(config.getDecoder())) { + builder.decoder(getOrInstantiate(config.getDecoder())); + } + + if (Objects.nonNull(config.getContract())) { + builder.contract(getOrInstantiate(config.getContract())); + } } private T getOrInstantiate(Class tClass) { diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java index 8a4687f2..2483caad 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java @@ -15,9 +15,12 @@ */ package org.springframework.cloud.openfeign; +import feign.Contract; import feign.Logger; import feign.RequestInterceptor; import feign.Retryer; +import feign.codec.Decoder; +import feign.codec.Encoder; import feign.codec.ErrorDecoder; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -93,6 +96,12 @@ public class FeignClientProperties { private Boolean decode404; + private Class decoder; + + private Class encoder; + + private Class contract; + public Logger.Level getLoggerLevel() { return loggerLevel; } @@ -149,6 +158,30 @@ public class FeignClientProperties { this.decode404 = decode404; } + public Class getDecoder() { + return decoder; + } + + public void setDecoder(Class decoder) { + this.decoder = decoder; + } + + public Class getEncoder() { + return encoder; + } + + public void setEncoder(Class encoder) { + this.encoder = encoder; + } + + public Class getContract() { + return contract; + } + + public void setContract(Class contract) { + this.contract = contract; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -160,13 +193,16 @@ public class FeignClientProperties { Objects.equals(retryer, that.retryer) && Objects.equals(errorDecoder, that.errorDecoder) && Objects.equals(requestInterceptors, that.requestInterceptors) && - Objects.equals(decode404, that.decode404); + Objects.equals(decode404, that.decode404) && + Objects.equals(encoder, that.encoder) && + Objects.equals(decoder, that.decoder) && + Objects.equals(contract, that.contract); } @Override public int hashCode() { return Objects.hash(loggerLevel, connectTimeout, readTimeout, retryer, - errorDecoder, requestInterceptors, decode404); + errorDecoder, requestInterceptors, decode404, encoder, decoder, contract); } } diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/HttpClientFeignLoadBalancedConfiguration.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/HttpClientFeignLoadBalancedConfiguration.java index 6a9a2fa6..90824608 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/HttpClientFeignLoadBalancedConfiguration.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/HttpClientFeignLoadBalancedConfiguration.java @@ -65,7 +65,7 @@ class HttpClientFeignLoadBalancedConfiguration { ApacheHttpClientConnectionManagerFactory connectionManagerFactory, FeignHttpClientProperties httpClientProperties) { final HttpClientConnectionManager connectionManager = connectionManagerFactory - .newConnectionManager(false, httpClientProperties.getMaxConnections(), + .newConnectionManager(httpClientProperties.isDisableSslValidation(), httpClientProperties.getMaxConnections(), httpClientProperties.getMaxConnectionsPerRoute(), httpClientProperties.getTimeToLive(), httpClientProperties.getTimeToLiveUnit(), registryBuilder); diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/OkHttpFeignLoadBalancedConfiguration.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/OkHttpFeignLoadBalancedConfiguration.java index 9d12641e..0e42d864 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/OkHttpFeignLoadBalancedConfiguration.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/ribbon/OkHttpFeignLoadBalancedConfiguration.java @@ -61,7 +61,7 @@ class OkHttpFeignLoadBalancedConfiguration { ConnectionPool connectionPool, FeignHttpClientProperties httpClientProperties) { Boolean followRedirects = httpClientProperties.isFollowRedirects(); Integer connectTimeout = httpClientProperties.getConnectionTimeout(); - this.okHttpClient = httpClientFactory.createBuilder(false). + this.okHttpClient = httpClientFactory.createBuilder(httpClientProperties.isDisableSslValidation()). connectTimeout(connectTimeout, TimeUnit.MILLISECONDS). followRedirects(followRedirects). connectionPool(connectionPool).build(); diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientUsingPropertiesTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientUsingPropertiesTests.java index 7c799a19..690cba2d 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientUsingPropertiesTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientUsingPropertiesTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright 2013-2018 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. @@ -20,6 +20,8 @@ import feign.RequestInterceptor; import feign.RequestTemplate; import feign.RetryableException; import feign.Retryer; +import feign.codec.EncodeException; +import feign.codec.Encoder; import feign.codec.ErrorDecoder; import org.junit.Test; import org.junit.runner.RunWith; @@ -30,6 +32,8 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -38,8 +42,11 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.Map; -import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; /** @@ -64,6 +71,8 @@ public class FeignClientUsingPropertiesTests { private FeignClientFactoryBean barFactoryBean; + private FeignClientFactoryBean formFactoryBean; + public FeignClientUsingPropertiesTests() { fooFactoryBean = new FeignClientFactoryBean(); fooFactoryBean.setName("foo"); @@ -72,6 +81,10 @@ public class FeignClientUsingPropertiesTests { barFactoryBean = new FeignClientFactoryBean(); barFactoryBean.setName("bar"); barFactoryBean.setType(FeignClientFactoryBean.class); + + formFactoryBean = new FeignClientFactoryBean(); + formFactoryBean.setName("form"); + formFactoryBean.setType(FeignClientFactoryBean.class); } public FooClient fooClient() { @@ -84,10 +97,15 @@ public class FeignClientUsingPropertiesTests { return barFactoryBean.feign(context).target(BarClient.class, "http://localhost:" + this.port); } + public FormClient formClient() { + formFactoryBean.setApplicationContext(applicationContext); + return formFactoryBean.feign(context).target(FormClient.class, "http://localhost:" + this.port); + } + @Test public void testFoo() { String response = fooClient().foo(); - assertNotNull("OK", response); + assertEquals("OK", response); } @Test(expected = RetryableException.class) @@ -96,6 +114,13 @@ public class FeignClientUsingPropertiesTests { fail("it should timeout"); } + @Test + public void testForm() { + Map request = Collections.singletonMap("form", "Data"); + String response = formClient().form(request); + assertEquals("Data", response); + } + protected interface FooClient { @RequestMapping(method = RequestMethod.GET, value = "/foo") @@ -108,6 +133,14 @@ public class FeignClientUsingPropertiesTests { String bar(); } + protected interface FormClient { + + @RequestMapping(value = "/form", method = RequestMethod.POST, + consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) + String form(Map form); + + } + @Configuration @EnableAutoConfiguration @RestController @@ -129,6 +162,12 @@ public class FeignClientUsingPropertiesTests { return "OK"; } + @RequestMapping(value = "/form", method = RequestMethod.POST, + consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) + public String form(HttpServletRequest request) { + return request.getParameter("form"); + } + } public static class FooRequestInterceptor implements RequestInterceptor { @@ -161,4 +200,19 @@ public class FeignClientUsingPropertiesTests { public static class DefaultErrorDecoder extends ErrorDecoder.Default { } + public static class FormEncoder implements Encoder { + + @Override + public void encode(Object o, Type type, RequestTemplate requestTemplate) throws EncodeException { + Map form = (Map) o; + StringBuilder builder = new StringBuilder(); + form.forEach((key, value) -> { + builder.append(key + "=" + value + "&"); + }); + + requestTemplate.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE); + requestTemplate.body(builder.toString()); + } + } + } diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonHttpClientConfigurationTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonHttpClientConfigurationTests.java new file mode 100644 index 00000000..4e35b5d5 --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonHttpClientConfigurationTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2013-2018 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.openfeign.ribbon; + +import java.lang.reflect.Field; +import javax.net.ssl.SSLContextSpi; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.X509TrustManager; +import org.apache.http.config.Lookup; +import org.apache.http.conn.HttpClientConnectionManager; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.impl.conn.DefaultHttpClientConnectionOperator; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.util.ReflectionUtils; +import org.springframework.web.bind.annotation.RestController; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +/** + * @author Ryan Baxter + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = FeignRibbonHttpClientConfigurationTests.Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = {"debug=true","feign.httpclient.disableSslValidation=true"}) +public class FeignRibbonHttpClientConfigurationTests { + + @Autowired + HttpClientConnectionManager connectionManager; + + @Test + public void disableSslTest() throws Exception { + Lookup socketFactoryRegistry = getConnectionSocketFactoryLookup(connectionManager); + assertNotNull(socketFactoryRegistry.lookup("https")); + assertNull(this.getX509TrustManager(socketFactoryRegistry).getAcceptedIssuers()); + } + + private Lookup getConnectionSocketFactoryLookup(HttpClientConnectionManager connectionManager) { + DefaultHttpClientConnectionOperator connectionOperator = (DefaultHttpClientConnectionOperator)this.getField(connectionManager, "connectionOperator"); + return (Lookup)this.getField(connectionOperator, "socketFactoryRegistry"); + } + + private X509TrustManager getX509TrustManager(Lookup socketFactoryRegistry) { + ConnectionSocketFactory connectionSocketFactory = (ConnectionSocketFactory)socketFactoryRegistry.lookup("https"); + SSLSocketFactory sslSocketFactory = (SSLSocketFactory)this.getField(connectionSocketFactory, "socketfactory"); + SSLContextSpi sslContext = (SSLContextSpi)this.getField(sslSocketFactory, "context"); + return (X509TrustManager)this.getField(sslContext, "trustManager"); + } + + protected Object getField(Object target, String name) { + Field field = ReflectionUtils.findField(target.getClass(), name); + ReflectionUtils.makeAccessible(field); + Object value = ReflectionUtils.getField(field, target); + return value; + } + + @Configuration + @EnableAutoConfiguration + @RestController + public static class Application { + public static void main(String[] args) { + new SpringApplicationBuilder(FeignRibbonClientRetryTests.Application.class) + .run(args); + } + } +} diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonOkHttpClientConfigurationTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonOkHttpClientConfigurationTests.java new file mode 100644 index 00000000..e05a3927 --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonOkHttpClientConfigurationTests.java @@ -0,0 +1,70 @@ +/* + * Copyright 2013-2018 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.openfeign.ribbon; + +import okhttp3.OkHttpClient; + +import java.lang.reflect.Field; +import javax.net.ssl.HostnameVerifier; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.commons.httpclient.OkHttpClientFactory; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.util.ReflectionUtils; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author Ryan Baxter + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = FeignRibbonOkHttpClientConfigurationTests.Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = {"debug=true","feign.httpclient.disableSslValidation=true", + "feign.okhttp.enabled=true", "feign.httpclient.enabled=false"}) +public class FeignRibbonOkHttpClientConfigurationTests { + + @Autowired + OkHttpClient httpClient; + + @Test + public void disableSslTest() throws Exception { + HostnameVerifier hostnameVerifier = (HostnameVerifier)this.getField(httpClient, "hostnameVerifier"); + Assert.assertTrue(OkHttpClientFactory.TrustAllHostnames.class.isInstance(hostnameVerifier)); + } + + protected Object getField(Object target, String name) { + Field field = ReflectionUtils.findField(target.getClass(), name); + ReflectionUtils.makeAccessible(field); + Object value = ReflectionUtils.getField(field, target); + return value; + } + + @Configuration + @EnableAutoConfiguration + @RestController + public static class Application { + public static void main(String[] args) { + new SpringApplicationBuilder(FeignRibbonClientRetryTests.Application.class) + .run(args); + } + } +} diff --git a/spring-cloud-openfeign-core/src/test/resources/feign-properties.properties b/spring-cloud-openfeign-core/src/test/resources/feign-properties.properties index 91abcebc..0fe10a1e 100644 --- a/spring-cloud-openfeign-core/src/test/resources/feign-properties.properties +++ b/spring-cloud-openfeign-core/src/test/resources/feign-properties.properties @@ -16,4 +16,6 @@ feign.client.config.foo.requestInterceptors[0]=org.springframework.cloud.openfei feign.client.config.foo.requestInterceptors[1]=org.springframework.cloud.openfeign.FeignClientUsingPropertiesTests.BarRequestInterceptor feign.client.config.bar.connectTimeout=1000 -feign.client.config.bar.readTimeout=1000 \ No newline at end of file +feign.client.config.bar.readTimeout=1000 + +feign.client.config.form.encoder=org.springframework.cloud.openfeign.FeignClientUsingPropertiesTests.FormEncoder \ No newline at end of file