Support java 11 http2 client gh-689 (#869)
This commit is contained in:
@@ -140,10 +140,10 @@ If none of them is on the classpath, the default feign client is used.
|
||||
|
||||
NOTE: `spring-cloud-starter-openfeign` supports `spring-cloud-starter-loadbalancer`. However, as is an optional dependency, you need to make sure it has been added to your project if you want to use it.
|
||||
|
||||
The OkHttpClient and Apache HttpClient 5 Feign clients can be used by setting `spring.cloud.openfeign.okhttp.enabled` or `spring.cloud.openfeign.httpclient.hc5.enabled` to `true`, respectively, and having them on the classpath.
|
||||
The OkHttpClient, Apache HttpClient 5 and Http2Client Feign clients can be used by setting `spring.cloud.openfeign.okhttp.enabled` or `spring.cloud.openfeign.httpclient.hc5.enabled` or `spring.cloud.openfeign.http2client.enabled` to `true`, respectively, and having them on the classpath.
|
||||
You can customize the HTTP client used by providing a bean of either `org.apache.hc.client5.http.impl.classic.CloseableHttpClient` when using Apache HC5.
|
||||
|
||||
You can further customise http clients by setting values in the `spring.cloud.openfeign.httpclient.xxx` properties. The ones prefixed just with `httpclient` will work for all the clients, the ones prefixed with `httpclient.hc5` to Apache HttpClient 5 and the ones prefixed with `httpclient.okhttp` to OkHttpClient. You can find a full list of properties you can customise in the appendix.
|
||||
You can further customise http clients by setting values in the `spring.cloud.openfeign.httpclient.xxx` properties. The ones prefixed just with `httpclient` will work for all the clients, the ones prefixed with `httpclient.hc5` to Apache HttpClient 5, the ones prefixed with `httpclient.okhttp` to OkHttpClient and the ones prefixed with `httpclient.http2` to Http2Client. You can find a full list of properties you can customise in the appendix.
|
||||
|
||||
TIP: Starting with Spring Cloud OpenFeign 4, the Feign Apache HttpClient 4 is no longer supported. We suggest using Apache HttpClient 5 instead.
|
||||
|
||||
|
||||
@@ -133,6 +133,11 @@
|
||||
<artifactId>feign-okhttp</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.openfeign</groupId>
|
||||
<artifactId>feign-java11</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.cloud.openfeign;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.http.HttpClient;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.X509Certificate;
|
||||
@@ -39,6 +40,7 @@ import feign.Client;
|
||||
import feign.Feign;
|
||||
import feign.Target;
|
||||
import feign.hc5.ApacheHttp5Client;
|
||||
import feign.http2client.Http2Client;
|
||||
import feign.okhttp.OkHttpClient;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import okhttp3.ConnectionPool;
|
||||
@@ -383,6 +385,25 @@ public class FeignAutoConfiguration {
|
||||
|
||||
}
|
||||
|
||||
// the following configuration is for alternate feign clients if
|
||||
// SC loadbalancer is not on the class path.
|
||||
// see corresponding configurations in FeignLoadBalancerAutoConfiguration
|
||||
// for load-balanced clients.
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass({ Http2Client.class, HttpClient.class })
|
||||
@ConditionalOnMissingBean(HttpClient.class)
|
||||
@ConditionalOnProperty("spring.cloud.openfeign.http2client.enabled")
|
||||
@Import(org.springframework.cloud.openfeign.clientconfig.Http2ClientFeignConfiguration.class)
|
||||
protected static class Http2ClientFeignConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(Client.class)
|
||||
public Client feignClient(HttpClient httpClient) {
|
||||
return new Http2Client(httpClient);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FeignHints implements RuntimeHintsRegistrar {
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2013-2023 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.clientconfig;
|
||||
|
||||
import java.net.http.HttpClient;
|
||||
import java.time.Duration;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Default configuration for {@link HttpClient}.
|
||||
*
|
||||
* @author changjin wei(魏昌进)
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnMissingBean(HttpClient.class)
|
||||
public class Http2ClientFeignConfiguration {
|
||||
|
||||
@Bean
|
||||
public HttpClient httpClient(FeignHttpClientProperties httpClientProperties) {
|
||||
return HttpClient.newBuilder()
|
||||
.followRedirects(httpClientProperties.isFollowRedirects() ? HttpClient.Redirect.ALWAYS
|
||||
: HttpClient.Redirect.NEVER)
|
||||
.version(HttpClient.Version.valueOf(httpClientProperties.getHttp2().getVersion()))
|
||||
.connectTimeout(Duration.ofMillis(httpClientProperties.getConnectionTimeout())).build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2022 the original author or authors.
|
||||
* Copyright 2013-2023 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.
|
||||
@@ -54,7 +54,7 @@ import org.springframework.context.annotation.Import;
|
||||
// see
|
||||
// https://github.com/spring-cloud/spring-cloud-netflix/issues/2086#issuecomment-316281653
|
||||
@Import({ OkHttpFeignLoadBalancerConfiguration.class, HttpClient5FeignLoadBalancerConfiguration.class,
|
||||
DefaultFeignLoadBalancerConfiguration.class })
|
||||
Http2ClientFeignLoadBalancerConfiguration.class, DefaultFeignLoadBalancerConfiguration.class })
|
||||
public class FeignLoadBalancerAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2013-2023 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.loadbalancer;
|
||||
|
||||
import java.net.http.HttpClient;
|
||||
import java.util.List;
|
||||
|
||||
import feign.Client;
|
||||
import feign.http2client.Http2Client;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
|
||||
import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties;
|
||||
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Configuration instantiating a {@link LoadBalancerClient}-based {@link Client} object
|
||||
* that uses {@link Http2Client} under the hood.
|
||||
*
|
||||
* @author changjin wei(魏昌进)
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass({ Http2Client.class, HttpClient.class })
|
||||
@ConditionalOnBean({ LoadBalancerClient.class, LoadBalancerClientFactory.class })
|
||||
@ConditionalOnProperty("spring.cloud.openfeign.http2client.enabled")
|
||||
@EnableConfigurationProperties(LoadBalancerClientsProperties.class)
|
||||
class Http2ClientFeignLoadBalancerConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@Conditional(OnRetryNotEnabledCondition.class)
|
||||
public Client feignClient(LoadBalancerClient loadBalancerClient, HttpClient httpClient,
|
||||
LoadBalancerClientFactory loadBalancerClientFactory,
|
||||
List<LoadBalancerFeignRequestTransformer> transformers) {
|
||||
Client delegate = new Http2Client(httpClient);
|
||||
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancerClientFactory,
|
||||
transformers);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnClass(name = "org.springframework.retry.support.RetryTemplate")
|
||||
@ConditionalOnBean(LoadBalancedRetryFactory.class)
|
||||
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
|
||||
matchIfMissing = true)
|
||||
public Client feignRetryClient(LoadBalancerClient loadBalancerClient, HttpClient httpClient,
|
||||
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory,
|
||||
List<LoadBalancerFeignRequestTransformer> transformers) {
|
||||
Client delegate = new Http2Client(httpClient);
|
||||
return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancedRetryFactory,
|
||||
loadBalancerClientFactory, transformers);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,10 +16,12 @@
|
||||
|
||||
package org.springframework.cloud.openfeign.support;
|
||||
|
||||
import java.net.http.HttpClient;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import feign.http2client.Http2Client;
|
||||
import feign.okhttp.OkHttpClient;
|
||||
import okhttp3.Protocol;
|
||||
|
||||
@@ -100,6 +102,11 @@ public class FeignHttpClientProperties {
|
||||
*/
|
||||
private OkHttp okHttp = new OkHttp();
|
||||
|
||||
/**
|
||||
* Additional {@link Http2Client}-specific properties.
|
||||
*/
|
||||
private Http2Properties http2 = new Http2Properties();
|
||||
|
||||
public int getConnectionTimerRepeat() {
|
||||
return connectionTimerRepeat;
|
||||
}
|
||||
@@ -180,6 +187,14 @@ public class FeignHttpClientProperties {
|
||||
this.okHttp = okHttp;
|
||||
}
|
||||
|
||||
public Http2Properties getHttp2() {
|
||||
return http2;
|
||||
}
|
||||
|
||||
public void setHttp2(Http2Properties http2) {
|
||||
this.http2 = http2;
|
||||
}
|
||||
|
||||
public static class Hc5Properties {
|
||||
|
||||
/**
|
||||
@@ -362,4 +377,25 @@ public class FeignHttpClientProperties {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Http2Client}-specific properties.
|
||||
*/
|
||||
public static class Http2Properties {
|
||||
|
||||
/**
|
||||
* Configure the protocols used by this client to communicate with remote servers.
|
||||
* Uses {@link String} value of {@link HttpClient.Version}.
|
||||
*/
|
||||
private String version = "HTTP_2";
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -44,6 +44,12 @@
|
||||
"description": "Enables the use of the OK HTTP Client by Feign.",
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.openfeign.http2client.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Enables the use of the Java11 HTTP 2 Client by Feign.",
|
||||
"defaultValue": "false"
|
||||
},
|
||||
{
|
||||
"name": "spring.cloud.openfeign.compression.response.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2013-2023 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;
|
||||
|
||||
import java.net.http.HttpClient;
|
||||
import java.time.Duration;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.WebApplicationType;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author changjin wei(魏昌进)
|
||||
*/
|
||||
class FeignHttp2ClientConfigurationTests {
|
||||
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
context = new SpringApplicationBuilder()
|
||||
.properties("debug=true", "spring.cloud.openfeign.http2client.enabled=true",
|
||||
"spring.cloud.openfeign.httpclient.http2.version=HTTP_1_1",
|
||||
"spring.cloud.openfeign.httpclient.connectionTimeout=15")
|
||||
.web(WebApplicationType.NONE).sources(FeignAutoConfiguration.class).run();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() {
|
||||
if (context != null) {
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConfigureConnectTimeout() {
|
||||
HttpClient httpClient = context.getBean(HttpClient.class);
|
||||
|
||||
assertThat(httpClient.connectTimeout()).isEqualTo(Optional.ofNullable(Duration.ofMillis(15)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolveVersionFromProperties() {
|
||||
HttpClient httpClient = context.getBean(HttpClient.class);
|
||||
|
||||
assertThat(httpClient.version()).isEqualTo(HttpClient.Version.HTTP_1_1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2022 the original author or authors.
|
||||
* Copyright 2013-2023 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.
|
||||
@@ -16,10 +16,12 @@
|
||||
|
||||
package org.springframework.cloud.openfeign.loadbalancer;
|
||||
|
||||
import java.net.http.HttpClient;
|
||||
import java.util.Map;
|
||||
|
||||
import feign.Client;
|
||||
import feign.hc5.ApacheHttp5Client;
|
||||
import feign.http2client.Http2Client;
|
||||
import feign.okhttp.OkHttpClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -37,6 +39,7 @@ import static org.springframework.test.util.ReflectionTestUtils.getField;
|
||||
/**
|
||||
* @author Olga Maciaszek-Sharma
|
||||
* @author Nguyen Ky Thanh
|
||||
* @author changjin wei(魏昌进)
|
||||
*/
|
||||
class FeignLoadBalancerAutoConfigurationTests {
|
||||
|
||||
@@ -65,6 +68,21 @@ class FeignLoadBalancerAutoConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstantiateHttp2ClientFeignClientWhenEnabled() {
|
||||
ConfigurableApplicationContext context = initContext("spring.cloud.openfeign.httpclient.hc5.enabled=false",
|
||||
"spring.cloud.openfeign.http2client.enabled=true", "spring.cloud.loadbalancer.retry.enabled=false");
|
||||
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
|
||||
Map<String, FeignBlockingLoadBalancerClient> beans = context
|
||||
.getBeansOfType(FeignBlockingLoadBalancerClient.class);
|
||||
assertThat(beans).as("Missing bean of type %s", Http2Client.class).hasSize(1);
|
||||
Client client = beans.get("feignClient").getDelegate();
|
||||
assertThat(client).isInstanceOf(Http2Client.class);
|
||||
Http2Client http2Client = (Http2Client) client;
|
||||
HttpClient httpClient = (HttpClient) getField(http2Client, "client");
|
||||
assertThat(httpClient).isInstanceOf(HttpClient.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstantiateHttpFeignClient5WhenAvailableAndOkHttpDisabled() {
|
||||
ConfigurableApplicationContext context = initContext("spring.cloud.openfeign.okhttp.enabled=false",
|
||||
@@ -73,6 +91,14 @@ class FeignLoadBalancerAutoConfigurationTests {
|
||||
assertLoadBalanced(context, ApacheHttp5Client.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstantiateHttpFeignClient5WhenAvailableAndHttp2ClientDisabled() {
|
||||
ConfigurableApplicationContext context = initContext("spring.cloud.openfeign.http2client.enabled=false",
|
||||
"spring.cloud.loadbalancer.retry.enabled=false");
|
||||
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
|
||||
assertLoadBalanced(context, ApacheHttp5Client.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstantiateRetryableDefaultFeignBlockingLoadBalancerClientWhenHttpClientDisabled() {
|
||||
ConfigurableApplicationContext context = initContext("spring.cloud.openfeign.httpclient.hc5.enabled=false");
|
||||
@@ -88,6 +114,14 @@ class FeignLoadBalancerAutoConfigurationTests {
|
||||
assertLoadBalancedWithRetries(context, OkHttpClient.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstantiateRetryableHttp2ClientFeignClientWhenEnabled() {
|
||||
ConfigurableApplicationContext context = initContext("spring.cloud.openfeign.httpclient.hc5.enabled=false",
|
||||
"spring.cloud.openfeign.http2client.enabled=true");
|
||||
assertThatOneBeanPresent(context, BlockingLoadBalancerClient.class);
|
||||
assertLoadBalancedWithRetries(context, Http2Client.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldInstantiateRetryableHttpFeignClient5WhenEnabled() {
|
||||
ConfigurableApplicationContext context = initContext();
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2013-2023 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.test;
|
||||
|
||||
import java.net.http.HttpClient;
|
||||
|
||||
import feign.Client;
|
||||
import feign.http2client.Http2Client;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author changjin wei(魏昌进)
|
||||
*/
|
||||
@SpringBootTest(properties = { "spring.cloud.openfeign.http2client.enabled= true",
|
||||
"spring.cloud.openfeign.httpclient.hc5.enabled= false", "spring.cloud.loadbalancer.retry.enabled= false" })
|
||||
@DirtiesContext
|
||||
class Http2ClientConfigurationTests {
|
||||
|
||||
@Autowired
|
||||
FeignBlockingLoadBalancerClient feignClient;
|
||||
|
||||
private static final HttpClient defaultHttpClient = HttpClient.newHttpClient();
|
||||
|
||||
@Test
|
||||
void shouldInstantiateFeignHttp2Client() {
|
||||
Client delegate = feignClient.getDelegate();
|
||||
assertThat(delegate instanceof Http2Client).isTrue();
|
||||
Http2Client http2Client = (Http2Client) delegate;
|
||||
HttpClient httpClient = getField(http2Client, "client");
|
||||
assertThat(httpClient).isEqualTo(defaultHttpClient);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T> T getField(Object target, String name) {
|
||||
Object value = ReflectionTestUtils.getField(target, target.getClass(), name);
|
||||
return (T) value;
|
||||
}
|
||||
|
||||
@FeignClient(name = "foo")
|
||||
interface FooClient {
|
||||
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
@EnableAutoConfiguration
|
||||
static class TestConfig {
|
||||
|
||||
@Bean
|
||||
public HttpClient client() {
|
||||
return defaultHttpClient;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* Copyright 2013-2023 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.valid;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import feign.Client;
|
||||
import feign.http2client.Http2Client;
|
||||
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.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||
import org.springframework.cloud.client.DefaultServiceInstance;
|
||||
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient;
|
||||
import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClients;
|
||||
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
|
||||
import org.springframework.cloud.loadbalancer.support.ServiceInstanceListSuppliers;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient;
|
||||
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.ResponseEntity;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PatchMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author changjin wei(魏昌进)
|
||||
*/
|
||||
@SpringBootTest(classes = FeignHttp2ClientTests.Application.class, webEnvironment = WebEnvironment.RANDOM_PORT,
|
||||
value = { "spring.application.name=feignclienttest", "spring.cloud.openfeign.circuitbreaker.enabled=false",
|
||||
"spring.cloud.openfeign.httpclient.hc5.enabled=false",
|
||||
"spring.cloud.openfeign.http2client.enabled=true", "spring.cloud.loadbalancer.retry.enabled=false" })
|
||||
@DirtiesContext
|
||||
class FeignHttp2ClientTests {
|
||||
|
||||
@Autowired
|
||||
private TestClient testClient;
|
||||
|
||||
@Autowired
|
||||
private Client feignClient;
|
||||
|
||||
@Autowired
|
||||
private UserClient userClient;
|
||||
|
||||
@Test
|
||||
void testSimpleType() {
|
||||
Hello hello = testClient.getHello();
|
||||
assertThat(hello).as("hello was null").isNotNull();
|
||||
assertThat(hello).as("first hello didn't match").isEqualTo(new Hello("hello world 1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPatch() {
|
||||
ResponseEntity<Void> response = testClient.patchHello(new Hello("foo"));
|
||||
assertThat(response).isNotNull();
|
||||
String header = response.getHeaders().getFirst("x-hello");
|
||||
assertThat(header).isEqualTo("hello world patch");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFeignClientType() {
|
||||
assertThat(feignClient).isInstanceOf(FeignBlockingLoadBalancerClient.class);
|
||||
FeignBlockingLoadBalancerClient client = (FeignBlockingLoadBalancerClient) feignClient;
|
||||
Client delegate = client.getDelegate();
|
||||
assertThat(delegate).isInstanceOf(Http2Client.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFeignInheritanceSupport() {
|
||||
assertThat(userClient).as("UserClient was null").isNotNull();
|
||||
final User user = userClient.getUser(1);
|
||||
assertThat(user).as("Returned user was null").isNotNull();
|
||||
assertThat(new User("John Smith")).as("Users were different").isEqualTo(user);
|
||||
}
|
||||
|
||||
@FeignClient("localapp")
|
||||
protected interface TestClient extends BaseTestClient {
|
||||
|
||||
}
|
||||
|
||||
protected interface BaseTestClient {
|
||||
|
||||
@GetMapping("/hello")
|
||||
Hello getHello();
|
||||
|
||||
@PatchMapping(value = "/hellop", consumes = "application/json")
|
||||
ResponseEntity<Void> patchHello(Hello hello);
|
||||
|
||||
}
|
||||
|
||||
protected interface UserService {
|
||||
|
||||
@GetMapping("/users/{id}")
|
||||
User getUser(@PathVariable("id") long id);
|
||||
|
||||
}
|
||||
|
||||
@FeignClient("localapp1")
|
||||
protected interface UserClient extends UserService {
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableAutoConfiguration
|
||||
@RestController
|
||||
@EnableFeignClients(clients = { TestClient.class, UserClient.class })
|
||||
@LoadBalancerClients({
|
||||
@LoadBalancerClient(name = "localapp", configuration = FeignHttpClientTests.LocalClientConfiguration.class),
|
||||
@LoadBalancerClient(name = "localapp1",
|
||||
configuration = FeignHttpClientTests.LocalClientConfiguration.class) })
|
||||
@Import(NoSecurityConfiguration.class)
|
||||
protected static class Application implements UserService {
|
||||
|
||||
@GetMapping("/hello")
|
||||
public Hello getHello() {
|
||||
return new Hello("hello world 1");
|
||||
}
|
||||
|
||||
@PatchMapping("/hellop")
|
||||
public ResponseEntity<Void> patchHello(@RequestBody Hello hello,
|
||||
@RequestHeader("Content-Length") int contentLength) {
|
||||
if (contentLength <= 0) {
|
||||
throw new IllegalArgumentException("Invalid Content-Length " + contentLength);
|
||||
}
|
||||
if (!hello.getMessage().equals("foo")) {
|
||||
throw new IllegalArgumentException("Invalid Hello: " + hello.getMessage());
|
||||
}
|
||||
return ResponseEntity.ok().header("X-Hello", "hello world patch").build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getUser(@PathVariable("id") long id) {
|
||||
return new User("John Smith");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Hello {
|
||||
|
||||
private String message;
|
||||
|
||||
Hello() {
|
||||
}
|
||||
|
||||
Hello(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Hello that = (Hello) o;
|
||||
return Objects.equals(message, that.message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class User {
|
||||
|
||||
private String name;
|
||||
|
||||
User() {
|
||||
}
|
||||
|
||||
User(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
User that = (User) o;
|
||||
return Objects.equals(name, that.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Load balancer with fixed server list for "local" pointing to localhost
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class LocalClientConfiguration {
|
||||
|
||||
@LocalServerPort
|
||||
private int port = 0;
|
||||
|
||||
@Bean
|
||||
public ServiceInstanceListSupplier staticServiceInstanceListSupplier() {
|
||||
return ServiceInstanceListSuppliers.from("local",
|
||||
new DefaultServiceInstance("local-1", "local", "localhost", port, false));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user