Support java 11 http2 client gh-689 (#869)

This commit is contained in:
小魏,小魏,我们要去哪里呀
2023-06-15 21:57:12 +08:00
committed by GitHub
parent cca510cbb2
commit 8020b8840e
12 changed files with 635 additions and 5 deletions

View File

@@ -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.

View File

@@ -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>

View File

@@ -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 {

View File

@@ -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();
}
}

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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",

View File

@@ -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);
}
}

View File

@@ -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();

View File

@@ -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;
}
}
}

View File

@@ -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));
}
}
}