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

Customize load balanced requests according to the chosen ServiceInstance #735

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
27 changes: 27 additions & 0 deletions docs/src/main/asciidoc/spring-cloud-openfeign.adoc
Expand Up @@ -815,6 +815,33 @@ Sometimes, when load balancing is enabled for Feign clients, you may want to use
spring.cloud.openfeign.oauth2.load-balanced=true
----

=== Transform the load-balanced HTTP request

You can use the selected `ServiceInstance` to transform the load-balanced HTTP Request.

For `Request`, you need to implement and define `LoadBalancerFeignRequestTransformer`, as follows:

[source,java,indent=0]
----
@Bean
public LoadBalancerFeignRequestTransformer transformer() {
return new LoadBalancerFeignRequestTransformer() {

@Override
public Request transformRequest(Request request, ServiceInstance instance) {
Map<String, Collection<String>> headers = new HashMap<>(request.headers());
headers.put("X-ServiceId", Collections.singletonList(instance.getServiceId()));
headers.put("X-InstanceId", Collections.singletonList(instance.getInstanceId()));
return Request.create(request.httpMethod(), request.url(), headers, request.body(), request.charset(),
request.requestTemplate());
}
};
}
----

If multiple transformers are defined, they are applied in the order in which beans are defined.
Alternatively, you can use `LoadBalancerFeignRequestTransformer.DEFAULT_ORDER` to specify the order.

== Configuration properties

To see the list of all Spring Cloud OpenFeign related configuration properties please check link:appendix.html[the Appendix page].
Expand Up @@ -16,6 +16,8 @@

package org.springframework.cloud.openfeign.loadbalancer;

import java.util.List;

import feign.Client;

import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
Expand All @@ -36,6 +38,7 @@
* that uses {@link Client.Default} under the hood.
*
* @author Olga Maciaszek-Sharma
* @author changjin wei(魏昌进)
* @since 2.2.0
*/
@Configuration(proxyBeanMethods = false)
Expand All @@ -46,9 +49,10 @@ class DefaultFeignLoadBalancerConfiguration {
@ConditionalOnMissingBean
@Conditional(OnRetryNotEnabledCondition.class)
public Client feignClient(LoadBalancerClient loadBalancerClient,
LoadBalancerClientFactory loadBalancerClientFactory) {
LoadBalancerClientFactory loadBalancerClientFactory,
List<LoadBalancerFeignRequestTransformer> transformers) {
return new FeignBlockingLoadBalancerClient(new Client.Default(null, null), loadBalancerClient,
loadBalancerClientFactory);
loadBalancerClientFactory, transformers);
}

@Bean
Expand All @@ -58,9 +62,10 @@ public Client feignClient(LoadBalancerClient loadBalancerClient,
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
matchIfMissing = true)
public Client feignRetryClient(LoadBalancerClient loadBalancerClient,
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory) {
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory,
List<LoadBalancerFeignRequestTransformer> transformers) {
return new RetryableFeignBlockingLoadBalancerClient(new Client.Default(null, null), loadBalancerClient,
loadBalancedRetryFactory, loadBalancerClientFactory);
loadBalancedRetryFactory, loadBalancerClientFactory, transformers);
}

}
Expand Up @@ -19,6 +19,8 @@
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import feign.Client;
Expand Down Expand Up @@ -49,6 +51,7 @@
* {@link ServiceInstance} to use while resolving the request host.
*
* @author Olga Maciaszek-Sharma
* @author changjin wei(魏昌进)
* @since 2.2.0
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
Expand All @@ -62,23 +65,41 @@ public class FeignBlockingLoadBalancerClient implements Client {

private final LoadBalancerClientFactory loadBalancerClientFactory;

private final List<LoadBalancerFeignRequestTransformer> transformers;

/**
* @deprecated in favour of
* {@link FeignBlockingLoadBalancerClient#FeignBlockingLoadBalancerClient(Client, LoadBalancerClient, LoadBalancerClientFactory)}
* {@link FeignBlockingLoadBalancerClient#FeignBlockingLoadBalancerClient(Client, LoadBalancerClient, LoadBalancerClientFactory, List)}
*/
@Deprecated
public FeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient,
LoadBalancerProperties properties, LoadBalancerClientFactory loadBalancerClientFactory) {
this.delegate = delegate;
this.loadBalancerClient = loadBalancerClient;
this.loadBalancerClientFactory = loadBalancerClientFactory;
this.transformers = Collections.emptyList();
}

/**
* @deprecated in favour of
* {@link FeignBlockingLoadBalancerClient#FeignBlockingLoadBalancerClient(Client, LoadBalancerClient, LoadBalancerClientFactory, List)}
*/
@Deprecated
public FeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient,
LoadBalancerClientFactory loadBalancerClientFactory) {
this.delegate = delegate;
this.loadBalancerClient = loadBalancerClient;
this.loadBalancerClientFactory = loadBalancerClientFactory;
this.transformers = Collections.emptyList();
}

public FeignBlockingLoadBalancerClient(Client delegate, LoadBalancerClient loadBalancerClient,
LoadBalancerClientFactory loadBalancerClientFactory,
List<LoadBalancerFeignRequestTransformer> transformers) {
this.delegate = delegate;
this.loadBalancerClient = loadBalancerClient;
this.loadBalancerClientFactory = loadBalancerClientFactory;
this.transformers = transformers;
}

@Override
Expand Down Expand Up @@ -109,7 +130,7 @@ public Response execute(Request request, Request.Options options) throws IOExcep
.body(message, StandardCharsets.UTF_8).build();
}
String reconstructedUrl = loadBalancerClient.reconstructURI(instance, originalUri).toString();
Request newRequest = buildRequest(request, reconstructedUrl);
Request newRequest = buildRequest(request, reconstructedUrl, instance);
return executeWithLoadBalancerLifecycleProcessing(delegate, options, newRequest, lbRequest, lbResponse,
supportedLifecycleProcessors);
}
Expand All @@ -119,6 +140,16 @@ protected Request buildRequest(Request request, String reconstructedUrl) {
request.charset(), request.requestTemplate());
}

protected Request buildRequest(Request request, String reconstructedUrl, ServiceInstance instance) {
Request newRequest = buildRequest(request, reconstructedUrl);
if (transformers != null) {
for (LoadBalancerFeignRequestTransformer transformer : transformers) {
newRequest = transformer.transformRequest(newRequest, instance);
}
}
return newRequest;
}

// Visible for Sleuth instrumentation
public Client getDelegate() {
return delegate;
Expand Down
Expand Up @@ -16,6 +16,8 @@

package org.springframework.cloud.openfeign.loadbalancer;

import java.util.List;

import feign.Client;
import feign.hc5.ApacheHttp5Client;
import org.apache.hc.client5.http.classic.HttpClient;
Expand All @@ -40,6 +42,7 @@
* that uses {@link ApacheHttp5Client} under the hood.
*
* @author Nguyen Ky Thanh
* @author changjin wei(魏昌进)
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttp5Client.class)
Expand All @@ -53,9 +56,11 @@ class HttpClient5FeignLoadBalancerConfiguration {
@ConditionalOnMissingBean
@Conditional(OnRetryNotEnabledCondition.class)
public Client feignClient(LoadBalancerClient loadBalancerClient, HttpClient httpClient5,
LoadBalancerClientFactory loadBalancerClientFactory) {
LoadBalancerClientFactory loadBalancerClientFactory,
List<LoadBalancerFeignRequestTransformer> transformers) {
Client delegate = new ApacheHttp5Client(httpClient5);
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancerClientFactory);
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancerClientFactory,
transformers);
}

@Bean
Expand All @@ -65,10 +70,11 @@ public Client feignClient(LoadBalancerClient loadBalancerClient, HttpClient http
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
matchIfMissing = true)
public Client feignRetryClient(LoadBalancerClient loadBalancerClient, HttpClient httpClient5,
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory) {
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory,
List<LoadBalancerFeignRequestTransformer> transformers) {
Client delegate = new ApacheHttp5Client(httpClient5);
return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancedRetryFactory,
loadBalancerClientFactory);
loadBalancerClientFactory, transformers);
}

}
Expand Up @@ -16,6 +16,8 @@

package org.springframework.cloud.openfeign.loadbalancer;

import java.util.List;

import feign.Client;
import feign.httpclient.ApacheHttpClient;
import org.apache.http.client.HttpClient;
Expand All @@ -42,6 +44,7 @@
*
* @author Olga Maciaszek-Sharma
* @author Nguyen Ky Thanh
* @author changjin wei(魏昌进)
* @since 2.2.0
*/
@Configuration(proxyBeanMethods = false)
Expand All @@ -57,9 +60,11 @@ class HttpClientFeignLoadBalancerConfiguration {
@ConditionalOnMissingBean
@Conditional(OnRetryNotEnabledCondition.class)
public Client feignClient(LoadBalancerClient loadBalancerClient, HttpClient httpClient,
LoadBalancerClientFactory loadBalancerClientFactory) {
LoadBalancerClientFactory loadBalancerClientFactory,
List<LoadBalancerFeignRequestTransformer> transformers) {
ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancerClientFactory);
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancerClientFactory,
transformers);
}

@Bean
Expand All @@ -69,10 +74,11 @@ public Client feignClient(LoadBalancerClient loadBalancerClient, HttpClient http
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
matchIfMissing = true)
public Client feignRetryClient(LoadBalancerClient loadBalancerClient, HttpClient httpClient,
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory) {
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory,
List<LoadBalancerFeignRequestTransformer> transformers) {
ApacheHttpClient delegate = new ApacheHttpClient(httpClient);
return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancedRetryFactory,
loadBalancerClientFactory);
loadBalancerClientFactory, transformers);
}

}
@@ -0,0 +1,46 @@
/*
* Copyright 2013-2022 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 feign.Request;

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.core.annotation.Order;

/**
* Allows applications to transform the load-balanced {@link Request} given the chosen
* {@link org.springframework.cloud.client.ServiceInstance}.
*
* @author changjin wei(魏昌进)
*/
@Order(LoadBalancerFeignRequestTransformer.DEFAULT_ORDER)
public interface LoadBalancerFeignRequestTransformer {

/**
* Order for the {@link LoadBalancerFeignRequestTransformer}.
*/
int DEFAULT_ORDER = 0;

/**
* Allows transforming load-balanced requests based on the provided {@link ServiceInstance}.
* @param request Original request.
* @param instance ServiceInstance returned from LoadBalancer.
* @return New request or original request
*/
Request transformRequest(Request request, ServiceInstance instance);
galaxy-sea marked this conversation as resolved.
Show resolved Hide resolved

}
Expand Up @@ -16,6 +16,8 @@

package org.springframework.cloud.openfeign.loadbalancer;

import java.util.List;

import feign.Client;
import feign.okhttp.OkHttpClient;

Expand All @@ -39,6 +41,7 @@
* that uses {@link OkHttpClient} under the hood.
*
* @author Olga Maciaszek-Sharma
* @author changjin wei(魏昌进)
* @since 2.2.0
*/
@Configuration(proxyBeanMethods = false)
Expand All @@ -53,9 +56,11 @@ class OkHttpFeignLoadBalancerConfiguration {
@ConditionalOnMissingBean
@Conditional(OnRetryNotEnabledCondition.class)
public Client feignClient(okhttp3.OkHttpClient okHttpClient, LoadBalancerClient loadBalancerClient,
LoadBalancerClientFactory loadBalancerClientFactory) {
LoadBalancerClientFactory loadBalancerClientFactory,
List<LoadBalancerFeignRequestTransformer> transformers) {
OkHttpClient delegate = new OkHttpClient(okHttpClient);
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancerClientFactory);
return new FeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancerClientFactory,
transformers);
}

@Bean
Expand All @@ -65,10 +70,11 @@ public Client feignClient(okhttp3.OkHttpClient okHttpClient, LoadBalancerClient
@ConditionalOnProperty(value = "spring.cloud.loadbalancer.retry.enabled", havingValue = "true",
matchIfMissing = true)
public Client feignRetryClient(LoadBalancerClient loadBalancerClient, okhttp3.OkHttpClient okHttpClient,
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory) {
LoadBalancedRetryFactory loadBalancedRetryFactory, LoadBalancerClientFactory loadBalancerClientFactory,
List<LoadBalancerFeignRequestTransformer> transformers) {
OkHttpClient delegate = new OkHttpClient(okHttpClient);
return new RetryableFeignBlockingLoadBalancerClient(delegate, loadBalancerClient, loadBalancedRetryFactory,
loadBalancerClientFactory);
loadBalancerClientFactory, transformers);
}

}