Compare commits

...
17 Commits
Author SHA1 Message Date
buildmaster e257ec31ff Update SNAPSHOT to 3.0.0-M6 2020-12-01 22:46:17 +00:00
Olga Maciaszek-Sharma 246805815f Merge remote-tracking branch 'origin/2.2.x' 2020-12-01 17:30:47 +01:00
Olga Maciaszek-Sharma 89fb98daf7 Upgrade protobuf-java. 2020-12-01 17:30:13 +01:00
Olga Maciaszek-Sharma 15054fa37e Merge remote-tracking branch 'origin/2.2.x'
# Conflicts:
#	docs/src/main/asciidoc/_configprops.adoc
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringMvcContractIntegrationTests.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringMvcContractTests.java
2020-12-01 17:22:59 +01:00
Olga Maciaszek-Sharma 1a7917e2bb Add information about decodeSlash property to docs. 2020-12-01 17:08:43 +01:00
Olga Maciaszek-Sharma 49d3830276 Remove unnecessary keyword from recently modified class. 2020-12-01 17:02:09 +01:00
Ram Anaswara d419826ca0 Fix for Path variables with / are not url encoded
# Conflicts:
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientProperties.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/support/SpringMvcContract.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringMvcContractIntegrationTests.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/support/SpringMvcContractTests.java
2020-12-01 16:53:39 +01:00
Olga Maciaszek-Sharma b231783e84 Merge remote-tracking branch 'origin/2.2.x'
# Conflicts:
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/FeignBlockingLoadBalancerClient.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/loadbalancer/RetryableFeignBlockingLoadBalancerClient.java
2020-11-30 19:17:55 +01:00
Olga Maciaszek-Sharma 64d3f05e70 Allow overriding creating requests. Fixes gh-423. 2020-11-30 19:12:14 +01:00
Olga Maciaszek-Sharma a7c804db63 Merge remote-tracking branch 'origin/2.2.x'
# Conflicts:
#	docs/pom.xml
#	pom.xml
#	spring-cloud-openfeign-core/pom.xml
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignBuilderCustomizer.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/clientconfig/HttpClientFeignConfiguration.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignBuilderCustomizerTests.java
#	spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignClientsRegistrarTests.java
#	spring-cloud-openfeign-dependencies/pom.xml
#	spring-cloud-starter-openfeign/pom.xml
2020-11-30 18:58:29 +01:00
Olga Maciaszek-Sharma 4d021fd3f8 Do not throw exceptions from @PreDestroy methods. Fixes gh-435. 2020-11-30 18:21:44 +01:00
matt62king a8bcda0b5b add customizer for feign builder (#289).
Fixes gh-436.

* add customizer for feign builder

* support @Order for customizers

* switch to annotation aware comparator for @Order support

* remove intermediate instances

* removed default feign builder customizer
# Conflicts:
#	spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignClientFactoryBean.java
2020-11-30 17:56:48 +01:00
buildmaster 4120ca2bc6 Bumping versions 2020-11-19 11:37:07 +00:00
buildmaster 29295425f3 Going back to snapshots 2020-11-17 20:42:04 +00:00
buildmaster 1b42820c3a Bumping versions to 2.2.7.BUILD-SNAPSHOT after release 2020-11-09 10:40:38 +00:00
buildmaster e31defb2dd Going back to snapshots 2020-11-09 10:40:37 +00:00
buildmaster f4ca971253 Update SNAPSHOT to 2.2.6.RELEASE 2020-11-09 10:38:56 +00:00
18 changed files with 351 additions and 123 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign</artifactId>
<version>3.0.0-M5</version>
<version>3.0.0-M6</version>
</parent>
<artifactId>spring-cloud-openfeign-docs</artifactId>
<packaging>jar</packaging>
+3 -2
View File
@@ -1,7 +1,8 @@
|===
|Name | Default | Description
|feign.client.config | `` |
|feign.client.config | |
|feign.client.decode-slash | `true` | Feign clients do not encode slash `/` characters by default. To change this behavior, set the `decodeSlash` to `false`.
|feign.client.default-config | `default` |
|feign.client.default-to-properties | `true` |
|feign.compression.request.enabled | `false` | Enables the request sent by Feign to be compressed.
@@ -17,7 +18,7 @@
|feign.httpclient.max-connections | `200` |
|feign.httpclient.max-connections-per-route | `50` |
|feign.httpclient.time-to-live | `900` |
|feign.httpclient.time-to-live-unit | `` |
|feign.httpclient.time-to-live-unit | |
|feign.hystrix.enabled | `false` | If true, an OpenFeign client will be wrapped with a Hystrix circuit breaker.
|feign.okhttp.enabled | `false` | Enables the use of the OK HTTP Client by Feign.
@@ -272,6 +272,8 @@ public FeignClientConfigurer feignClientConfigurer() {
}
----
TIP: By default, Feign clients do not encode slash `/` characters. You can change this behaviour, by setting the value of `feign.client.decodeSlash` to `false`.
[[timeout-handling]]
=== Timeout Handling
+3 -3
View File
@@ -4,14 +4,14 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-openfeign</artifactId>
<version>3.0.0-M5</version>
<version>3.0.0-M6</version>
<packaging>pom</packaging>
<name>Spring Cloud OpenFeign</name>
<description>Spring Cloud OpenFeign</description>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-build</artifactId>
<version>3.0.0-M5</version>
<version>3.0.0-M6</version>
<relativePath/>
</parent>
<scm>
@@ -26,7 +26,7 @@
<properties>
<main.basedir>${basedir}</main.basedir>
<jackson.version>2.11.3</jackson.version>
<spring-cloud-commons.version>3.0.0-M5</spring-cloud-commons.version>
<spring-cloud-commons.version>3.0.0-M6</spring-cloud-commons.version>
<!-- Plugin versions -->
<maven-eclipse-plugin.version>2.10</maven-eclipse-plugin.version>
+2 -2
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign</artifactId>
<version>3.0.0-M5</version>
<version>3.0.0-M6</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-openfeign-core</artifactId>
@@ -168,7 +168,7 @@
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.13.0</version>
<version>3.14.0</version>
<scope>test</scope>
</dependency>
<dependency>
@@ -16,6 +16,7 @@
package org.springframework.cloud.openfeign;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
@@ -29,6 +30,8 @@ import feign.Feign;
import feign.httpclient.ApacheHttpClient;
import feign.okhttp.OkHttpClient;
import okhttp3.ConnectionPool;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.RegistryBuilder;
@@ -61,6 +64,8 @@ import org.springframework.context.annotation.Import;
@Import(DefaultGzipDecoderConfiguration.class)
public class FeignAutoConfiguration {
private static final Log LOG = LogFactory.getLog(FeignAutoConfiguration.class);
@Autowired(required = false)
private List<FeignClientSpecification> configurations = new ArrayList<>();
@@ -142,10 +147,17 @@ public class FeignAutoConfiguration {
}
@PreDestroy
public void destroy() throws Exception {
public void destroy() {
this.connectionManagerTimer.cancel();
if (this.httpClient != null) {
this.httpClient.close();
try {
this.httpClient.close();
}
catch (IOException e) {
if (LOG.isErrorEnabled()) {
LOG.error("Could not correctly close httpClient.");
}
}
}
}
@@ -36,6 +36,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author Eko Kurniawan Khannedy
* @author Ilia Ilinykh
* @author Ram Anaswara
*/
@ConfigurationProperties("feign.client")
public class FeignClientProperties {
@@ -46,8 +47,14 @@ public class FeignClientProperties {
private Map<String, FeignClientConfiguration> config = new HashMap<>();
/**
* Feign clients do not encode slash `/` characters by default. To change this
* behavior, set the `decodeSlash` to `false`.
*/
private boolean decodeSlash = true;
public boolean isDefaultToProperties() {
return this.defaultToProperties;
return defaultToProperties;
}
public void setDefaultToProperties(boolean defaultToProperties) {
@@ -55,7 +62,7 @@ public class FeignClientProperties {
}
public String getDefaultConfig() {
return this.defaultConfig;
return defaultConfig;
}
public void setDefaultConfig(String defaultConfig) {
@@ -63,13 +70,21 @@ public class FeignClientProperties {
}
public Map<String, FeignClientConfiguration> getConfig() {
return this.config;
return config;
}
public void setConfig(Map<String, FeignClientConfiguration> config) {
this.config = config;
}
public boolean isDecodeSlash() {
return decodeSlash;
}
public void setDecodeSlash(boolean decodeSlash) {
this.decodeSlash = decodeSlash;
}
@Override
public boolean equals(Object o) {
if (this == o) {
@@ -79,13 +94,13 @@ public class FeignClientProperties {
return false;
}
FeignClientProperties that = (FeignClientProperties) o;
return this.defaultToProperties == that.defaultToProperties
&& Objects.equals(this.defaultConfig, that.defaultConfig) && Objects.equals(this.config, that.config);
return defaultToProperties == that.defaultToProperties && Objects.equals(defaultConfig, that.defaultConfig)
&& Objects.equals(config, that.config) && Objects.equals(decodeSlash, that.decodeSlash);
}
@Override
public int hashCode() {
return Objects.hash(this.defaultToProperties, this.defaultConfig, this.config);
return Objects.hash(defaultToProperties, defaultConfig, config, decodeSlash);
}
/**
@@ -120,7 +135,7 @@ public class FeignClientProperties {
private ExceptionPropagationPolicy exceptionPropagationPolicy;
public Logger.Level getLoggerLevel() {
return this.loggerLevel;
return loggerLevel;
}
public void setLoggerLevel(Logger.Level loggerLevel) {
@@ -128,7 +143,7 @@ public class FeignClientProperties {
}
public Integer getConnectTimeout() {
return this.connectTimeout;
return connectTimeout;
}
public void setConnectTimeout(Integer connectTimeout) {
@@ -136,7 +151,7 @@ public class FeignClientProperties {
}
public Integer getReadTimeout() {
return this.readTimeout;
return readTimeout;
}
public void setReadTimeout(Integer readTimeout) {
@@ -144,7 +159,7 @@ public class FeignClientProperties {
}
public Class<Retryer> getRetryer() {
return this.retryer;
return retryer;
}
public void setRetryer(Class<Retryer> retryer) {
@@ -152,7 +167,7 @@ public class FeignClientProperties {
}
public Class<ErrorDecoder> getErrorDecoder() {
return this.errorDecoder;
return errorDecoder;
}
public void setErrorDecoder(Class<ErrorDecoder> errorDecoder) {
@@ -160,7 +175,7 @@ public class FeignClientProperties {
}
public List<Class<RequestInterceptor>> getRequestInterceptors() {
return this.requestInterceptors;
return requestInterceptors;
}
public void setRequestInterceptors(List<Class<RequestInterceptor>> requestInterceptors) {
@@ -184,7 +199,7 @@ public class FeignClientProperties {
}
public Boolean getDecode404() {
return this.decode404;
return decode404;
}
public void setDecode404(Boolean decode404) {
@@ -192,7 +207,7 @@ public class FeignClientProperties {
}
public Class<Decoder> getDecoder() {
return this.decoder;
return decoder;
}
public void setDecoder(Class<Decoder> decoder) {
@@ -200,7 +215,7 @@ public class FeignClientProperties {
}
public Class<Encoder> getEncoder() {
return this.encoder;
return encoder;
}
public void setEncoder(Class<Encoder> encoder) {
@@ -208,7 +223,7 @@ public class FeignClientProperties {
}
public Class<Contract> getContract() {
return this.contract;
return contract;
}
public void setContract(Class<Contract> contract) {
@@ -232,23 +247,22 @@ public class FeignClientProperties {
return false;
}
FeignClientConfiguration that = (FeignClientConfiguration) o;
return this.loggerLevel == that.loggerLevel && Objects.equals(this.connectTimeout, that.connectTimeout)
&& Objects.equals(this.readTimeout, that.readTimeout) && Objects.equals(this.retryer, that.retryer)
&& Objects.equals(this.errorDecoder, that.errorDecoder)
&& Objects.equals(this.requestInterceptors, that.requestInterceptors)
&& Objects.equals(this.decode404, that.decode404) && Objects.equals(this.encoder, that.encoder)
&& Objects.equals(this.decoder, that.decoder) && Objects.equals(this.contract, that.contract)
&& Objects.equals(this.exceptionPropagationPolicy, that.exceptionPropagationPolicy)
&& Objects.equals(this.defaultRequestHeaders, that.defaultRequestHeaders)
&& Objects.equals(this.defaultQueryParameters, that.defaultQueryParameters);
return loggerLevel == that.loggerLevel && Objects.equals(connectTimeout, that.connectTimeout)
&& Objects.equals(readTimeout, that.readTimeout) && Objects.equals(retryer, that.retryer)
&& Objects.equals(errorDecoder, that.errorDecoder)
&& Objects.equals(requestInterceptors, that.requestInterceptors)
&& Objects.equals(decode404, that.decode404) && Objects.equals(encoder, that.encoder)
&& Objects.equals(decoder, that.decoder) && Objects.equals(contract, that.contract)
&& Objects.equals(exceptionPropagationPolicy, that.exceptionPropagationPolicy)
&& Objects.equals(defaultRequestHeaders, that.defaultRequestHeaders)
&& Objects.equals(defaultQueryParameters, that.defaultQueryParameters);
}
@Override
public int hashCode() {
return Objects.hash(this.loggerLevel, this.connectTimeout, this.readTimeout, this.retryer,
this.errorDecoder, this.requestInterceptors, this.decode404, this.encoder, this.decoder,
this.contract, this.exceptionPropagationPolicy, this.defaultQueryParameters,
this.defaultRequestHeaders);
return Objects.hash(loggerLevel, connectTimeout, readTimeout, retryer, errorDecoder, requestInterceptors,
decode404, encoder, decoder, contract, exceptionPropagationPolicy, defaultQueryParameters,
defaultRequestHeaders);
}
}
@@ -79,6 +79,9 @@ public class FeignClientsConfiguration {
@Autowired(required = false)
private SpringDataWebProperties springDataWebProperties;
@Autowired(required = false)
private FeignClientProperties feignClientProperties;
@Bean
@ConditionalOnMissingBean
public Decoder feignDecoder() {
@@ -109,7 +112,8 @@ public class FeignClientsConfiguration {
@Bean
@ConditionalOnMissingBean
public Contract feignContract(ConversionService feignConversionService) {
return new SpringMvcContract(this.parameterProcessors, feignConversionService);
boolean decodeSlash = feignClientProperties == null || feignClientProperties.isDecodeSlash();
return new SpringMvcContract(this.parameterProcessors, feignConversionService, decodeSlash);
}
@Bean
@@ -16,11 +16,14 @@
package org.springframework.cloud.openfeign.clientconfig;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
import javax.annotation.PreDestroy;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.HttpClientConnectionManager;
@@ -48,6 +51,8 @@ import org.springframework.context.annotation.Configuration;
@ConditionalOnMissingBean(CloseableHttpClient.class)
public class HttpClientFeignConfiguration {
private static final Log LOG = LogFactory.getLog(HttpClientFeignConfiguration.class);
private final Timer connectionManagerTimer = new Timer("FeignApacheHttpClientConfiguration.connectionManagerTimer",
true);
@@ -103,10 +108,17 @@ public class HttpClientFeignConfiguration {
}
@PreDestroy
public void destroy() throws Exception {
public void destroy() {
this.connectionManagerTimer.cancel();
if (this.httpClient != null) {
this.httpClient.close();
try {
this.httpClient.close();
}
catch (IOException e) {
if (LOG.isErrorEnabled()) {
LOG.error("Could not correctly close httpClient.");
}
}
}
}
@@ -100,12 +100,16 @@ public class FeignBlockingLoadBalancerClient implements Client {
.body(message, StandardCharsets.UTF_8).build();
}
String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri).toString();
Request newRequest = Request.create(request.httpMethod(), reconstructedUrl, request.headers(), request.body(),
request.charset(), request.requestTemplate());
Request newRequest = buildRequest(request, reconstructedUrl);
return executeWithLoadBalancerLifecycleProcessing(delegate, options, newRequest, lbResponse,
supportedLifecycleProcessors);
}
protected Request buildRequest(Request request, String reconstructedUrl) {
return Request.create(request.httpMethod(), reconstructedUrl, request.headers(), request.body(),
request.charset(), request.requestTemplate());
}
// Visible for Sleuth instrumentation
public Client getDelegate() {
return delegate;
@@ -148,8 +148,7 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client {
}
String reconstructedUrl = loadBalancerClient.reconstructURI(retrievedServiceInstance, originalUri)
.toString();
feignRequest = Request.create(request.httpMethod(), reconstructedUrl, request.headers(),
request.body(), request.charset(), request.requestTemplate());
feignRequest = buildRequest(request, reconstructedUrl);
}
}
org.springframework.cloud.client.loadbalancer.Response<ServiceInstance> lbResponse = new DefaultResponse(
@@ -173,6 +172,11 @@ public class RetryableFeignBlockingLoadBalancerClient implements Client {
});
}
protected Request buildRequest(Request request, String reconstructedUrl) {
return Request.create(request.httpMethod(), reconstructedUrl, request.headers(), request.body(),
request.charset(), request.requestTemplate());
}
private RetryTemplate buildRetryTemplate(String serviceId, Request request, LoadBalancedRetryPolicy retryPolicy) {
RetryTemplate retryTemplate = new RetryTemplate();
BackOffPolicy backOffPolicy = this.loadBalancedRetryFactory.createBackOffPolicy(serviceId);
@@ -78,6 +78,7 @@ import static org.springframework.core.annotation.AnnotatedElementUtils.findMerg
* @author Aaron Whiteside
* @author Artyom Romanenko
* @author Darren Foong
* @author Ram Anaswara
*/
public class SpringMvcContract extends Contract.BaseContract implements ResourceLoaderAware {
@@ -101,6 +102,8 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource
private ResourceLoader resourceLoader = new DefaultResourceLoader();
private boolean decodeSlash;
public SpringMvcContract() {
this(Collections.emptyList());
}
@@ -111,6 +114,11 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource
public SpringMvcContract(List<AnnotatedParameterProcessor> annotatedParameterProcessors,
ConversionService conversionService) {
this(annotatedParameterProcessors, conversionService, true);
}
public SpringMvcContract(List<AnnotatedParameterProcessor> annotatedParameterProcessors,
ConversionService conversionService, boolean decodeSlash) {
Assert.notNull(annotatedParameterProcessors, "Parameter processors can not be null.");
Assert.notNull(conversionService, "ConversionService can not be null.");
@@ -120,6 +128,7 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource
annotatedArgumentProcessors = toAnnotatedArgumentProcessorMap(processors);
this.conversionService = conversionService;
convertingExpanderFactory = new ConvertingExpanderFactory(conversionService);
this.decodeSlash = decodeSlash;
}
private static TypeDescriptor createTypeDescriptor(Method method, int paramIndex) {
@@ -171,6 +180,9 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource
pathValue = "/" + pathValue;
}
data.template().uri(pathValue);
if (data.template().decodeSlash() != decodeSlash) {
data.template().decodeSlash(decodeSlash);
}
}
}
}
@@ -232,6 +244,9 @@ public class SpringMvcContract extends Contract.BaseContract implements Resource
pathValue = "/" + pathValue;
}
data.template().uri(pathValue, true);
if (data.template().decodeSlash() != decodeSlash) {
data.template().decodeSlash(decodeSlash);
}
}
}
@@ -0,0 +1,112 @@
/*
* Copyright 2013-2020 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
*
* https://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.support;
import java.nio.charset.Charset;
import feign.Response;
import feign.codec.Decoder;
import feign.codec.Encoder;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.test.NoSecurityConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpHeaders;
import org.springframework.util.SocketUtils;
import org.springframework.web.bind.annotation.GetMapping;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* Abstract class for the integration tests for {@link SpringMvcContract}.
*
* @author Ram Anaswara
*/
public class AbstractSpringMvcContractIntegrationTests {
@BeforeAll
public static void beforeClass() {
System.setProperty("server.port", String.valueOf(SocketUtils.findAvailableTcpPort()));
}
@AfterAll
public static void afterClass() {
System.clearProperty("server.port");
}
protected String getUrlQueryParam(Response response) {
return response.request().requestTemplate().queries().get("url").stream().findFirst()
.orElseThrow(IllegalStateException::new);
}
@FeignClient(name = "test", url = "http://localhost:${server.port}/",
configuration = NoCodecsFeignConfiguration.class)
interface TestClient {
@PostMapping("/test")
Object sendMessage(@RequestBody String message, @RequestHeader(HttpHeaders.CONTENT_TYPE) String acceptHeader);
@GetMapping("/get")
Object getMessage(@RequestParam String url);
}
@Configuration(proxyBeanMethods = false)
@EnableFeignClients(clients = TestClient.class)
@EnableAutoConfiguration
@RestController
@Import(NoSecurityConfiguration.class)
protected static class Config {
@PostMapping("/test")
Object sendMessage(@RequestBody String message, @RequestHeader(HttpHeaders.CONTENT_TYPE) String acceptHeader) {
return message;
}
@GetMapping("/get")
Object getMessage(@RequestParam String url) {
return url;
}
}
// Avoid feign.codec.EncodeException - this feature works for users that override
// Encoder
protected static class NoCodecsFeignConfiguration {
@Bean
public Decoder decoder() {
return (response, type) -> response;
}
@Bean
public Encoder encoder() {
return (object, bodyType, request) -> request.body(object.toString().getBytes(), Charset.defaultCharset());
}
}
}
@@ -16,96 +16,39 @@
package org.springframework.cloud.openfeign.support;
import java.nio.charset.Charset;
import feign.codec.Decoder;
import feign.codec.Encoder;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import feign.Response;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.cloud.openfeign.test.NoSecurityConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpHeaders;
import org.springframework.util.SocketUtils;
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 static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
/**
* Integration tests for {@link SpringMvcContract}
* Integration tests for {@link SpringMvcContract}.
*
* @author Olga Maciaszek-Sharma
* @author Ram Anaswara
*/
@SpringBootTest(classes = SpringMvcContractIntegrationTests.Config.class,
@SpringBootTest(classes = AbstractSpringMvcContractIntegrationTests.Config.class,
webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class SpringMvcContractIntegrationTests {
public class SpringMvcContractIntegrationTests extends AbstractSpringMvcContractIntegrationTests {
@Autowired
private TestClient client;
@BeforeAll
public static void beforeClass() {
System.setProperty("server.port", String.valueOf(SocketUtils.findAvailableTcpPort()));
}
@AfterAll
public static void afterClass() {
System.clearProperty("server.port");
}
@Test
public void shouldNotThrowInvalidMediaTypeExceptionWhenContentTypeTemplateUsed() {
assertThatCode(() -> client.sendMessage("test", "text/markdown")).doesNotThrowAnyException();
}
@FeignClient(name = "test", url = "http://localhost:${server.port}/",
configuration = NoCodecsFeignConfiguration.class)
interface TestClient {
@PostMapping("/test")
Object sendMessage(@RequestBody String message, @RequestHeader(HttpHeaders.CONTENT_TYPE) String acceptHeader);
}
@Configuration(proxyBeanMethods = false)
@EnableFeignClients(clients = TestClient.class)
@EnableAutoConfiguration
@RestController
@Import(NoSecurityConfiguration.class)
protected static class Config {
@PostMapping("/test")
Object sendMessage(@RequestBody String message, @RequestHeader(HttpHeaders.CONTENT_TYPE) String acceptHeader) {
return message;
}
}
// avoid feign.codec.EncodeException - this feature works for users that override
// Encoder
protected static class NoCodecsFeignConfiguration {
@Bean
public Decoder decoder() {
return (response, type) -> response;
}
@Bean
public Encoder encoder() {
return (object, bodyType, request) -> request.body(object.toString().getBytes(), Charset.defaultCharset());
}
@Test
public void feignClientShouldPreserveSlash() {
Response response = (Response) client.getMessage("https://www.google.com");
String urlQueryParam = getUrlQueryParam(response);
assertThat(urlQueryParam).isEqualTo("https%3A//www.google.com");
}
}
@@ -0,0 +1,47 @@
/*
* Copyright 2013-2020 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
*
* https://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.support;
import feign.Response;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Integration tests for {@link SpringMvcContract}.
*
* @author Ram Anaswara
*/
@SpringBootTest(classes = SpringMvcContractSlashEncodingIntegrationTests.Config.class,
webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, properties = { "feign.client.decodeSlash=false" })
public class SpringMvcContractSlashEncodingIntegrationTests extends AbstractSpringMvcContractIntegrationTests {
@Autowired
private TestClient client;
@Test
public void feignClientShouldNotDecodeEncodedSlash() {
Response response = (Response) client.getMessage("https://www.google.com");
String urlQueryParam = getUrlQueryParam(response);
assertThat(urlQueryParam).isEqualTo("https%3A%2F%2Fwww.google.com");
}
}
@@ -114,11 +114,7 @@ public class SpringMvcContractTests {
@Before
public void setup() {
FormattingConversionServiceFactoryBean conversionServiceFactoryBean = new FormattingConversionServiceFactoryBean();
conversionServiceFactoryBean.afterPropertiesSet();
ConversionService conversionService = conversionServiceFactoryBean.getObject();
contract = new SpringMvcContract(Collections.emptyList(), conversionService);
contract = new SpringMvcContract(Collections.emptyList(), getConversionService());
}
@Test
@@ -130,6 +126,19 @@ public class SpringMvcContractTests {
assertThat(data.template().method()).isEqualTo("GET");
assertThat(data.template().headers().get("Accept").iterator().next())
.isEqualTo(MediaType.APPLICATION_JSON_VALUE);
assertThat(data.template().decodeSlash()).isTrue();
}
@Test
public void testProcessAnnotationOnMethod_Simple_SlashEncoded() throws Exception {
contract = new SpringMvcContract(Collections.emptyList(), getConversionService(), false);
Method method = TestTemplate_Simple.class.getDeclaredMethod("getTest", String.class);
MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method);
assertThat(data.template().url()).isEqualTo("/test/{id}");
assertThat(data.template().decodeSlash()).isFalse();
}
@Test
@@ -180,6 +189,19 @@ public class SpringMvcContractTests {
assertThat(data.template().method()).isEqualTo("GET");
assertThat(data.indexToName().get(0).iterator().next()).isEqualTo("classId");
assertThat(data.template().decodeSlash()).isTrue();
}
@Test
public void testProcessAnnotations_Class_AnnotationsGetAllTests_EncodeSlash() throws Exception {
contract = new SpringMvcContract(Collections.emptyList(), getConversionService(), false);
Method method = TestTemplate_Class_Annotations.class.getDeclaredMethod("getAllTests", String.class);
MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method);
assertThat(data.template().url()).isEqualTo("/prepend/{classId}");
assertThat(data.template().decodeSlash()).isFalse();
}
@Test
@@ -193,8 +215,25 @@ public class SpringMvcContractTests {
assertThat(data.template().url()).isEqualTo(extendedData.template().url());
assertThat(data.template().method()).isEqualTo(extendedData.template().method());
assertThat(data.indexToName().get(0).iterator().next()).isEqualTo(data.indexToName().get(0).iterator().next());
assertThat(data.indexToName().get(0).iterator().next()).isEqualTo(data.indexToName().get(0).iterator().next());
assertThat(data.template().decodeSlash()).isTrue();
}
@Test
public void testProcessAnnotations_ExtendedInterface_EncodeSlash() throws Exception {
contract = new SpringMvcContract(Collections.emptyList(), getConversionService(), false);
Method extendedMethod = TestTemplate_Extended.class.getMethod("getAllTests", String.class);
MethodMetadata extendedData = contract.parseAndValidateMetadata(extendedMethod.getDeclaringClass(),
extendedMethod);
Method method = TestTemplate_Class_Annotations.class.getDeclaredMethod("getAllTests", String.class);
MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method);
assertThat(data.template().url()).isEqualTo(extendedData.template().url());
assertThat(data.template().method()).isEqualTo(extendedData.template().method());
assertThat(data.template().decodeSlash()).isFalse();
}
@Test
@@ -343,6 +382,19 @@ public class SpringMvcContractTests {
assertThat(data.template().method()).isEqualTo("GET");
assertThat(data.template().headers().get("Accept").iterator().next())
.isEqualTo(MediaType.APPLICATION_JSON_VALUE);
assertThat(data.template().decodeSlash()).isTrue();
}
@Test
public void testProcessAnnotations_Advanced3_DecodeSlashFlagNotModified() throws Exception {
contract = new SpringMvcContract(Collections.emptyList(), getConversionService(), false);
Method method = TestTemplate_Simple.class.getDeclaredMethod("getTest");
MethodMetadata data = contract.parseAndValidateMetadata(method.getDeclaringClass(), method);
assertThat(data.template().url()).isEqualTo("/");
assertThat(data.template().decodeSlash()).isTrue();
}
@Test
@@ -524,6 +576,12 @@ public class SpringMvcContractTests {
assertThat(data.formParams()).contains("file", "id");
}
private ConversionService getConversionService() {
FormattingConversionServiceFactoryBean conversionServiceFactoryBean = new FormattingConversionServiceFactoryBean();
conversionServiceFactoryBean.afterPropertiesSet();
return conversionServiceFactoryBean.getObject();
}
public interface TestTemplate_Simple {
@RequestMapping(value = "/test/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
+2 -2
View File
@@ -6,11 +6,11 @@
<parent>
<artifactId>spring-cloud-dependencies-parent</artifactId>
<groupId>org.springframework.cloud</groupId>
<version>3.0.0-M5</version>
<version>3.0.0-M6</version>
<relativePath/>
</parent>
<artifactId>spring-cloud-openfeign-dependencies</artifactId>
<version>3.0.0-M5</version>
<version>3.0.0-M6</version>
<packaging>pom</packaging>
<name>spring-cloud-openfeign-dependencies</name>
<description>Spring Cloud OpenFeign Dependencies</description>
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-openfeign</artifactId>
<version>3.0.0-M5</version>
<version>3.0.0-M6</version>
<relativePath>..</relativePath>
</parent>
<artifactId>spring-cloud-starter-openfeign</artifactId>