Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow customising HC5 RequestConfig in EurekaClientHttpRequestFactorySupplier #4394

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/modules/ROOT/pages/spring-cloud-netflix.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ eureka:
socket-timeout: 10000
----

You can also customise the `RequestConfig` for the underlying Apache HC5 client by creating a bean of type `EurekaClientHttpRequestFactorySupplier.RequestConfigCustomizer`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be good to provide a small code snippet showing how to do this.


=== Status Page and Health Indicator

The status page and health indicators for a Eureka instance default to `/info` and `/health` respectively, which are the default locations of useful endpoints in a Spring Boot Actuator application.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2018-2024 the original author or authors.
* Copyright 2018-2025 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 All @@ -16,6 +16,8 @@

package org.springframework.cloud.netflix.eureka;

import java.util.Collections;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.jupiter.api.BeforeAll;
Expand Down Expand Up @@ -64,8 +66,8 @@ public RestTemplateTransportClientFactories forceRestTemplateTransportClientFact

@Bean
public RestTemplateDiscoveryClientOptionalArgs discoveryClientOptionalArgs() {
return new RestTemplateDiscoveryClientOptionalArgs(
new DefaultEurekaClientHttpRequestFactorySupplier(new RestTemplateTimeoutProperties()), null);
return new RestTemplateDiscoveryClientOptionalArgs(new DefaultEurekaClientHttpRequestFactorySupplier(
new RestTemplateTimeoutProperties(), Collections.emptySet()));
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2024 the original author or authors.
* Copyright 2017-2025 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 All @@ -18,6 +18,7 @@

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Set;

import com.netflix.discovery.AbstractDiscoveryClientOptionalArgs;
import com.netflix.discovery.shared.transport.jersey.TransportClientFactories;
Expand Down Expand Up @@ -94,8 +95,10 @@ static class RestTemplateConfiguration {
@Bean
@ConditionalOnMissingBean
EurekaClientHttpRequestFactorySupplier defaultEurekaClientHttpRequestFactorySupplier(
RestTemplateTimeoutProperties restTemplateTimeoutProperties) {
return new DefaultEurekaClientHttpRequestFactorySupplier(restTemplateTimeoutProperties);
RestTemplateTimeoutProperties restTemplateTimeoutProperties,
Set<EurekaClientHttpRequestFactorySupplier.RequestConfigCustomizer> requestConfigCustomizers) {
return new DefaultEurekaClientHttpRequestFactorySupplier(restTemplateTimeoutProperties,
requestConfigCustomizers);
}

@Bean
Expand Down Expand Up @@ -189,8 +192,10 @@ protected static class RestClientConfiguration {
@Bean
@ConditionalOnMissingBean
EurekaClientHttpRequestFactorySupplier defaultEurekaClientHttpRequestFactorySupplier(
RestClientTimeoutProperties restClientTimeoutProperties) {
return new DefaultEurekaClientHttpRequestFactorySupplier(restClientTimeoutProperties);
RestClientTimeoutProperties restClientTimeoutProperties,
Set<EurekaClientHttpRequestFactorySupplier.RequestConfigCustomizer> requestConfigCustomizers) {
return new DefaultEurekaClientHttpRequestFactorySupplier(restClientTimeoutProperties,
requestConfigCustomizers);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2024 the original author or authors.
* Copyright 2013-2025 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 All @@ -16,6 +16,8 @@

package org.springframework.cloud.netflix.eureka.config;

import java.util.Set;

import com.netflix.discovery.EurekaClientConfig;
import com.netflix.discovery.shared.transport.EurekaHttpClient;

Expand Down Expand Up @@ -54,7 +56,7 @@
import org.springframework.web.reactive.function.client.WebClient;

/**
* Bootstrap configuration for config client that wants to lookup the config server via
* Bootstrap configuration for config client that wants to look the config server up via
* discovery.
*
* @author Dave Syer
Expand Down Expand Up @@ -103,8 +105,10 @@ public RestTemplateEurekaHttpClient configDiscoveryRestTemplateEurekaHttpClient(
@Bean
@ConditionalOnMissingBean
EurekaClientHttpRequestFactorySupplier defaultEurekaClientHttpRequestFactorySupplier(
RestTemplateTimeoutProperties restTemplateTimeoutProperties) {
return new DefaultEurekaClientHttpRequestFactorySupplier(restTemplateTimeoutProperties);
RestTemplateTimeoutProperties restTemplateTimeoutProperties,
Set<EurekaClientHttpRequestFactorySupplier.RequestConfigCustomizer> requestConfigCustomizers) {
return new DefaultEurekaClientHttpRequestFactorySupplier(restTemplateTimeoutProperties,
requestConfigCustomizers);
}

/**
Expand Down Expand Up @@ -174,8 +178,10 @@ public RestClientEurekaHttpClient configDiscoveryRestClientEurekaHttpClient(Eure
@Bean
@ConditionalOnMissingBean
EurekaClientHttpRequestFactorySupplier defaultEurekaClientHttpRequestFactorySupplier(
RestClientTimeoutProperties restClientTimeoutProperties) {
return new DefaultEurekaClientHttpRequestFactorySupplier(restClientTimeoutProperties);
RestClientTimeoutProperties restClientTimeoutProperties,
Set<EurekaClientHttpRequestFactorySupplier.RequestConfigCustomizer> requestConfigCustomizers) {
return new DefaultEurekaClientHttpRequestFactorySupplier(restClientTimeoutProperties,
requestConfigCustomizers);
}

static class OnRestClientPresentAndEnabledCondition extends AllNestedConditions {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2024 the original author or authors.
* Copyright 2013-2025 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 @@ -61,7 +61,8 @@ public void initialize(BootstrapRegistry registry) {
EurekaHttpClient httpClient = new RestClientTransportClientFactory(
context.getOrElse(TlsProperties.class, null),
context.getOrElse(EurekaClientHttpRequestFactorySupplier.class,
new DefaultEurekaClientHttpRequestFactorySupplier(new RestClientTimeoutProperties())))
new DefaultEurekaClientHttpRequestFactorySupplier(new RestClientTimeoutProperties(),
Collections.emptySet())))
.newClient(HostnameBasedUrlRandomizer.randomEndpoint(config, getPropertyResolver(context)));
return new EurekaConfigServerInstanceProvider(httpClient, config)::getInstances;
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2013-2024 the original author or authors.
* Copyright 2013-2025 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 All @@ -16,11 +16,14 @@

package org.springframework.cloud.netflix.eureka.http;

import java.util.Collections;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;

import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
Expand Down Expand Up @@ -50,9 +53,12 @@ public class DefaultEurekaClientHttpRequestFactorySupplier implements EurekaClie

private final TimeoutProperties timeoutProperties;

// TODO: switch to final after removing deprecated interfaces
private Set<RequestConfigCustomizer> requestConfigCustomizers = Collections.emptySet();

/**
* @deprecated in favour of
* {@link DefaultEurekaClientHttpRequestFactorySupplier#DefaultEurekaClientHttpRequestFactorySupplier(TimeoutProperties)}
* {@link DefaultEurekaClientHttpRequestFactorySupplier#DefaultEurekaClientHttpRequestFactorySupplier(TimeoutProperties, Set)}
*/
@Deprecated(forRemoval = true)
public DefaultEurekaClientHttpRequestFactorySupplier() {
Expand All @@ -61,27 +67,36 @@ public DefaultEurekaClientHttpRequestFactorySupplier() {

/**
* @deprecated in favour of
* {@link DefaultEurekaClientHttpRequestFactorySupplier#DefaultEurekaClientHttpRequestFactorySupplier(TimeoutProperties)}
* {@link DefaultEurekaClientHttpRequestFactorySupplier#DefaultEurekaClientHttpRequestFactorySupplier(TimeoutProperties, Set)}
*/
@Deprecated(forRemoval = true)
public DefaultEurekaClientHttpRequestFactorySupplier(RestTemplateTimeoutProperties timeoutProperties) {
this.timeoutProperties = timeoutProperties;
}

/**
* @deprecated in favour of
* {@link DefaultEurekaClientHttpRequestFactorySupplier#DefaultEurekaClientHttpRequestFactorySupplier(TimeoutProperties, Set)}
*/
@Deprecated(forRemoval = true)
public DefaultEurekaClientHttpRequestFactorySupplier(TimeoutProperties timeoutProperties) {
this.timeoutProperties = timeoutProperties;
}

public DefaultEurekaClientHttpRequestFactorySupplier(TimeoutProperties timeoutProperties,
Set<RequestConfigCustomizer> requestConfigCustomizers) {
this.timeoutProperties = timeoutProperties;
this.requestConfigCustomizers = requestConfigCustomizers;
}

@Override
public ClientHttpRequestFactory get(SSLContext sslContext, @Nullable HostnameVerifier hostnameVerifier) {
HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
if (sslContext != null || hostnameVerifier != null || timeoutProperties != null) {
httpClientBuilder
.setConnectionManager(buildConnectionManager(sslContext, hostnameVerifier, timeoutProperties));
}
if (timeoutProperties != null) {
httpClientBuilder.setDefaultRequestConfig(buildRequestConfig());
}
httpClientBuilder.setDefaultRequestConfig(buildRequestConfig());

CloseableHttpClient httpClient = httpClientBuilder.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
Expand All @@ -90,7 +105,7 @@ public ClientHttpRequestFactory get(SSLContext sslContext, @Nullable HostnameVer
}

private HttpClientConnectionManager buildConnectionManager(SSLContext sslContext, HostnameVerifier hostnameVerifier,
TimeoutProperties restTemplateTimeoutProperties) {
TimeoutProperties timeoutProperties) {
PoolingHttpClientConnectionManagerBuilder connectionManagerBuilder = PoolingHttpClientConnectionManagerBuilder
.create();
SSLConnectionSocketFactoryBuilder sslConnectionSocketFactoryBuilder = SSLConnectionSocketFactoryBuilder
Expand All @@ -102,20 +117,25 @@ private HttpClientConnectionManager buildConnectionManager(SSLContext sslContext
sslConnectionSocketFactoryBuilder.setHostnameVerifier(hostnameVerifier);
}
connectionManagerBuilder.setSSLSocketFactory(sslConnectionSocketFactoryBuilder.build());
if (restTemplateTimeoutProperties != null) {
if (timeoutProperties != null) {
connectionManagerBuilder.setDefaultSocketConfig(SocketConfig.custom()
.setSoTimeout(Timeout.of(restTemplateTimeoutProperties.getSocketTimeout(), TimeUnit.MILLISECONDS))
.setSoTimeout(Timeout.of(timeoutProperties.getSocketTimeout(), TimeUnit.MILLISECONDS))
.build());
connectionManagerBuilder.setDefaultConnectionConfig(ConnectionConfig.custom()
.setConnectTimeout(Timeout.of(timeoutProperties.getConnectTimeout(), TimeUnit.MILLISECONDS))
.build());
}
return connectionManagerBuilder.build();
}

private RequestConfig buildRequestConfig() {
return RequestConfig.custom()
.setConnectTimeout(Timeout.of(timeoutProperties.getConnectTimeout(), TimeUnit.MILLISECONDS))
.setConnectionRequestTimeout(
Timeout.of(timeoutProperties.getConnectRequestTimeout(), TimeUnit.MILLISECONDS))
.build();
RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();
if (timeoutProperties != null) {
requestConfigBuilder.setConnectionRequestTimeout(
Timeout.of(timeoutProperties.getConnectRequestTimeout(), TimeUnit.MILLISECONDS));
}
requestConfigCustomizers.forEach(customizer -> customizer.customize(requestConfigBuilder));
return requestConfigBuilder.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-2025 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 All @@ -19,6 +19,8 @@
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;

import org.apache.hc.client5.http.config.RequestConfig;

import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.lang.Nullable;

Expand All @@ -38,4 +40,16 @@ public interface EurekaClientHttpRequestFactorySupplier {
*/
ClientHttpRequestFactory get(SSLContext sslContext, @Nullable HostnameVerifier hostnameVerifier);

/**
* Allows customising the {@link RequestConfig} of the underlying Apache HC5 instance.
*
* @since 4.2.1
*/
@FunctionalInterface
interface RequestConfigCustomizer {

void customize(RequestConfig.Builder builder);

}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2022 the original author or authors.
* Copyright 2017-2025 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 @@ -31,6 +31,7 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
Expand All @@ -45,6 +46,7 @@
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
Expand All @@ -59,6 +61,7 @@
*
* @author Daniel Lavoie
* @author Wonchul Heo
* @author Olga Maciaszek-Sharma
*/
@Configuration(proxyBeanMethods = false)
@RestController
Expand Down Expand Up @@ -172,7 +175,11 @@ public Applications getApplications(@PathVariable(required = false) String addre
}

@GetMapping("/apps/{appName}")
public Application getApplication(@PathVariable String appName) {
public Application getApplication(@PathVariable String appName, @RequestHeader HttpHeaders headers) {
// Used to verify that RequestConfig customizer has taken effect
if (appName.equals("upgrade") && !headers.containsKey("upgrade")) {
throw new RuntimeException("No upgrade header found");
}
return new Application();
}

Expand Down
Loading
Loading