Skip to content

Commit

Permalink
Do not set ClientHttpRequestFactory when Spring Boot >= 3.4.0 (#71)
Browse files Browse the repository at this point in the history
* Do not set ClientHttpRequestFactory when Spring Boot >= 3.4.0

* not verify Spring Cloud version

* use ?

* reflection registers RestClientBuilderConfigurer and RestTemplateBuilderConfigurer

* deprecate RequestConfigurator, Requester and requestMappingSupportEnabled

* native compile is alive
  • Loading branch information
DanielLiu1123 authored Nov 25, 2024
1 parent 33720da commit dfad8a5
Show file tree
Hide file tree
Showing 16 changed files with 404 additions and 47 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ on:
push:
branches:
- main
- 3.3.x
- 3.2.x
- 3.1.x
pull_request:
branches:
- main
- 3.3.x
- 3.2.x
- 3.1.x
jobs:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/release-snapshot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ on:
push:
branches:
- main
- 3.3.x
- 3.2.x
- 3.1.x
jobs:
Expand Down
7 changes: 7 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,16 @@ allprojects {
}

test {
systemProperties("spring.cloud.compatibility-verifier.enabled": "false")
useJUnitPlatform()
}

afterEvaluate {
tasks.findByName("bootRun")?.configure {
systemProperty("spring.cloud.compatibility-verifier.enabled", "false")
}
}

apply plugin: "com.diffplug.spotless"
spotless {
encoding "UTF-8"
Expand Down
4 changes: 4 additions & 0 deletions examples/loadbalancer/build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
plugins {
id "org.springframework.boot"
}

dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation(project(":starters:httpexchange-spring-boot-starter"))
Expand Down
4 changes: 4 additions & 0 deletions examples/minimal/build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
plugins {
id "org.springframework.boot"
}

dependencies {
implementation(project(":starters:httpexchange-spring-boot-starter"))

Expand Down
4 changes: 4 additions & 0 deletions examples/quick-start/build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
plugins {
id "org.springframework.boot"
}

dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-validation")
Expand Down
4 changes: 4 additions & 0 deletions examples/reactive/build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
plugins {
id "org.springframework.boot"
}

dependencies {
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation(project(":starters:httpexchange-spring-boot-starter"))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package io.github.danielliu1123.httpexchange;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.springframework.boot.autoconfigure.web.client.RestClientBuilderConfigurer;
import org.springframework.boot.autoconfigure.web.client.RestTemplateBuilderConfigurer;
import org.springframework.util.ReflectionUtils;

/**
* @author Freeman
*/
final class ConfigurerCopier {

private static final Map<String, Field> restClientBuilderConfigurerProperties;
private static final Map<String, Field> restTemplateBuilderConfigurerProperties;

static {
restClientBuilderConfigurerProperties = Arrays.stream(RestClientBuilderConfigurer.class.getDeclaredFields())
.peek(ReflectionUtils::makeAccessible)
.collect(Collectors.toMap(Field::getName, Function.identity()));
restTemplateBuilderConfigurerProperties = Arrays.stream(RestTemplateBuilderConfigurer.class.getDeclaredFields())
.peek(ReflectionUtils::makeAccessible)
.collect(Collectors.toMap(Field::getName, Function.identity()));
}

public static RestClientBuilderConfigurer copyRestClientBuilderConfigurer(RestClientBuilderConfigurer source) {

var target = new RestClientBuilderConfigurer();

for (var entry : restClientBuilderConfigurerProperties.entrySet()) {
var field = entry.getValue();
var value = ReflectionUtils.getField(field, source);
if (value != null) {
ReflectionUtils.setField(field, target, value);
}
}

return target;
}

public static void setRestClientBuilderConfigurerProperty(
RestClientBuilderConfigurer target, String name, Object value) {
var field = restClientBuilderConfigurerProperties.get(name);
if (field != null) {
ReflectionUtils.setField(field, target, value);
}
}

public static RestTemplateBuilderConfigurer copyRestTemplateBuilderConfigurer(
RestTemplateBuilderConfigurer source) {

var target = new RestTemplateBuilderConfigurer();

for (var entry : restTemplateBuilderConfigurerProperties.entrySet()) {
var field = entry.getValue();
var value = ReflectionUtils.getField(field, source);
if (value != null) {
ReflectionUtils.setField(field, target, value);
}
}

return target;
}

public static void setRestTemplateBuilderConfigurerProperty(
RestTemplateBuilderConfigurer target, String name, Object value) {
var field = restTemplateBuilderConfigurerProperties.get(name);
if (field != null) {
ReflectionUtils.setField(field, target, value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.github.danielliu1123.httpexchange.shaded.ShadedHttpServiceProxyFactory;
import jakarta.annotation.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.time.Duration;
Expand All @@ -24,6 +25,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.boot.SpringBootVersion;
import org.springframework.boot.autoconfigure.web.client.RestClientBuilderConfigurer;
import org.springframework.boot.autoconfigure.web.client.RestTemplateBuilderConfigurer;
import org.springframework.boot.ssl.SslBundle;
Expand Down Expand Up @@ -218,11 +220,9 @@ private void setEmbeddedValueResolver(HttpServiceProxyFactory.Builder builder) {

private RestTemplate buildRestTemplate(HttpExchangeProperties.Channel channelConfig) {
RestTemplateBuilder builder = new RestTemplateBuilder();
RestTemplateBuilderConfigurer configurer =
beanFactory.getBeanProvider(RestTemplateBuilderConfigurer.class).getIfUnique();
if (configurer != null) {
builder = configurer.configure(builder);
}

builder = configureRestTemplateBuilder(builder, channelConfig);

if (StringUtils.hasText(channelConfig.getBaseUrl())) {
builder = builder.rootUri(getRealBaseUrl(channelConfig));
}
Expand All @@ -234,7 +234,10 @@ private RestTemplate buildRestTemplate(HttpExchangeProperties.Channel channelCon
}

// Set default request factory
builder = builder.requestFactory(() -> getRequestFactory(channelConfig));
// No need to do this when Spring Boot version >= 3.4.0
if (isSpringBootVersionLessThan340()) {
builder = builder.requestFactory(() -> getRequestFactory(channelConfig));
}

if (isLoadBalancerEnabled(channelConfig)) {
Set<ClientHttpRequestInterceptor> lbInterceptors = new LinkedHashSet<>();
Expand All @@ -255,7 +258,9 @@ private RestTemplate buildRestTemplate(HttpExchangeProperties.Channel channelCon
restTemplate.setInterceptors(
restTemplate.getInterceptors().stream().distinct().toList());

setTimeoutByConfig(restTemplate.getRequestFactory(), channelConfig);
if (isSpringBootVersionLessThan340()) {
setTimeoutByConfig(restTemplate.getRequestFactory(), channelConfig);
}

beanFactory
.getBeanProvider(HttpClientCustomizer.RestTemplateCustomizer.class)
Expand All @@ -280,10 +285,12 @@ private WebClient buildWebClient(HttpExchangeProperties.Channel channelConfig) {
.forEach(header -> builder.defaultHeader(
header.getKey(), header.getValues().toArray(String[]::new)));
}
if (channelConfig.getReadTimeout() != null) {
builder.filter((request, next) ->
next.exchange(request).timeout(Duration.ofMillis(channelConfig.getReadTimeout())));

var readTimeout = getReadTimeout(channelConfig);
if (readTimeout != null) {
builder.filter((request, next) -> next.exchange(request).timeout(readTimeout));
}

if (isLoadBalancerEnabled(channelConfig)) {
builder.filters(filters -> {
Set<ExchangeFilterFunction> allFilters = new LinkedHashSet<>(filters);
Expand All @@ -306,12 +313,33 @@ private WebClient buildWebClient(HttpExchangeProperties.Channel channelConfig) {
return builder.build();
}

@Nullable
private Duration getReadTimeout(HttpExchangeProperties.Channel channelConfig) {
var duration = Optional.ofNullable(channelConfig.getReadTimeout())
.map(Duration::ofMillis)
.orElse(null);
if (duration != null) { // Channel config has higher priority
return duration;
}

// less than 3.4.0, there is no org.springframework.boot.http.client.ClientHttpRequestFactorySettings
if (isSpringBootVersionLessThan340()) {
return null;
}

// Spring Boot 3.4.0+
var settings = beanFactory
.getBeanProvider(org.springframework.boot.http.client.ClientHttpRequestFactorySettings.class)
.getIfUnique(org.springframework.boot.http.client.ClientHttpRequestFactorySettings::defaults);
return settings.readTimeout();
}

private RestClient buildRestClient(HttpExchangeProperties.Channel channelConfig) {
// Do not use RestClient.Builder bean here, because we can't know requestFactory is configured by user or not
RestClient.Builder builder = RestClient.builder();
beanFactory
.getBeanProvider(RestClientBuilderConfigurer.class)
.ifUnique(configurer -> configurer.configure(builder));

configureRestClientBuilder(builder, channelConfig);

if (StringUtils.hasText(channelConfig.getBaseUrl())) {
builder.baseUrl(getRealBaseUrl(channelConfig));
}
Expand All @@ -322,12 +350,14 @@ private RestClient buildRestClient(HttpExchangeProperties.Channel channelConfig)
header.getKey(), header.getValues().toArray(String[]::new)));
}

ClientHttpRequestFactory requestFactory =
unwrapRequestFactoryIfNecessary(getFieldValue(builder, "requestFactory"));
if (requestFactory == null) {
builder.requestFactory(getRequestFactory(channelConfig));
} else {
setTimeoutByConfig(requestFactory, channelConfig);
if (isSpringBootVersionLessThan340()) {
ClientHttpRequestFactory requestFactory =
unwrapRequestFactoryIfNecessary(getFieldValue(builder, "requestFactory"));
if (requestFactory == null) {
builder.requestFactory(getRequestFactory(channelConfig));
} else {
setTimeoutByConfig(requestFactory, channelConfig);
}
}

if (isLoadBalancerEnabled(channelConfig)) {
Expand Down Expand Up @@ -357,6 +387,61 @@ private RestClient buildRestClient(HttpExchangeProperties.Channel channelConfig)
return builder.build();
}

private void configureRestClientBuilder(RestClient.Builder builder, HttpExchangeProperties.Channel channelConfig) {
var configurer = beanFactory
.getBeanProvider(RestClientBuilderConfigurer.class)
.getIfUnique(RestClientBuilderConfigurer::new);

// requestFactorySettings have been available since Spring Boot 3.4.0
var f = ReflectionUtils.findField(RestClientBuilderConfigurer.class, "requestFactorySettings");
if (f != null) {
var copied = ConfigurerCopier.copyRestClientBuilderConfigurer(configurer);
ConfigurerCopier.setRestClientBuilderConfigurerProperty(
copied, "requestFactorySettings", getClientHttpRequestFactorySettings(channelConfig));

configurer = copied;
}

configurer.configure(builder);
}

private RestTemplateBuilder configureRestTemplateBuilder(
RestTemplateBuilder builder, HttpExchangeProperties.Channel channelConfig) {
RestTemplateBuilderConfigurer configurer = beanFactory
.getBeanProvider(RestTemplateBuilderConfigurer.class)
.getIfUnique(RestTemplateBuilderConfigurer::new);

// requestFactorySettings have been available since Spring Boot 3.4.0
var f = ReflectionUtils.findField(RestTemplateBuilderConfigurer.class, "requestFactorySettings");
if (f != null) {
var copied = ConfigurerCopier.copyRestTemplateBuilderConfigurer(configurer);
ConfigurerCopier.setRestTemplateBuilderConfigurerProperty(
copied, "requestFactorySettings", getClientHttpRequestFactorySettings(channelConfig));

configurer = copied;
}

return configurer.configure(builder);
}

private org.springframework.boot.http.client.ClientHttpRequestFactorySettings getClientHttpRequestFactorySettings(
HttpExchangeProperties.Channel channelConfig) {
var settings = beanFactory
.getBeanProvider(org.springframework.boot.http.client.ClientHttpRequestFactorySettings.class)
.getIfUnique(org.springframework.boot.http.client.ClientHttpRequestFactorySettings::defaults);
if (channelConfig.getConnectTimeout() != null) {
settings = settings.withConnectTimeout(Duration.ofMillis(channelConfig.getConnectTimeout()));
}
if (channelConfig.getReadTimeout() != null) {
settings = settings.withReadTimeout(Duration.ofMillis(channelConfig.getReadTimeout()));
}
return settings;
}

private static boolean isSpringBootVersionLessThan340() {
return SpringBootVersion.getVersion().compareTo("3.4.0") < 0;
}

private ClientHttpRequestFactory getRequestFactory(HttpExchangeProperties.Channel channelConfig) {
ClientHttpRequestFactorySettings settings = new ClientHttpRequestFactorySettings(
Optional.ofNullable(channelConfig.getConnectTimeout())
Expand Down
Loading

0 comments on commit dfad8a5

Please sign in to comment.