Skip to content

Commit

Permalink
Support java 11 http2 client gh-689 (#869)
Browse files Browse the repository at this point in the history
  • Loading branch information
galaxy-sea committed Jun 15, 2023
1 parent cca510c commit 8020b88
Show file tree
Hide file tree
Showing 12 changed files with 635 additions and 5 deletions.
4 changes: 2 additions & 2 deletions docs/src/main/asciidoc/spring-cloud-openfeign.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
5 changes: 5 additions & 0 deletions spring-cloud-openfeign-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -39,6 +40,7 @@
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;
Expand Down Expand Up @@ -383,6 +385,25 @@ public OAuth2AccessTokenInterceptor defaultOAuth2AccessTokenInterceptor(

}

// 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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}

}
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -54,7 +54,7 @@
// 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
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -180,6 +187,14 @@ public void setOkHttp(OkHttp okHttp) {
this.okHttp = okHttp;
}

public Http2Properties getHttp2() {
return http2;
}

public void setHttp2(Http2Properties http2) {
this.http2 = http2;
}

public static class Hc5Properties {

/**
Expand Down Expand Up @@ -362,4 +377,25 @@ public void setProtocols(List<String> protocols) {

}

/**
* {@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;
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}

}

0 comments on commit 8020b88

Please sign in to comment.