Get Spring Default Feign Options on programmatically created feign client - spring

I am trying to get the default feign options/config to use it in feign clients that I create programatically using Feign.Builder:
This is the config (application.yaml) that I want to get:
feign:
client:
config:
default:
connect-timeout: 5000
read-timeout: 5000
What I tried to do is to do is to add #EnableFeignClients and try to get the FactoryBean<Request.Options> or it's implementation OptionsFactoryBean but I don't see it being injected by Spring anywhere.
I've also tried searching in StackOverflow and other websites to see if there are other people that have tried what I am trying to do, but I wasn't able to find any information hence why I am creating this question.
public MyClientFactory(Client client,
ObjectMapper objectMapper,
FactoryBean<Request.Options> optionsFactory,
ErrorDecoder errorDecoder) throws Exception {
this.builder = Feign.builder()
.client(client)
.decoder(new JacksonDecoder(objectMapper))
.encoder(new JacksonEncoder(objectMapper))
.options(optionsFactory.getObject())
.errorDecoder(errorDecoder);
}
Can someone please let me know how I can get the default spring feign configs? Maybe my approach is incorrect?
Thanks!

Haven't tried it but I assume you'll need a bean from the FeignClientProperties class.

I ended up creating a Request.Options #Bean based on FeignClientProperties as #Arnold Galovics suggested.
#Bean
Request.Options options(FeignClientProperties feignClientProperties) {
FeignClientProperties.FeignClientConfiguration feignClientConfiguration = feignClientProperties.getConfig().get(feignClientProperties.getDefaultConfig());
Request.Options defaultOptions = new Request.Options();
return new Request.Options(
Optional.ofNullable(feignClientConfiguration.getConnectTimeout()).orElse(defaultOptions.connectTimeoutMillis()), TimeUnit.MILLISECONDS,
Optional.ofNullable(feignClientConfiguration.getReadTimeout()).orElse(defaultOptions.readTimeoutMillis()), TimeUnit.MILLISECONDS,
Optional.ofNullable(feignClientConfiguration.isFollowRedirects()).orElse(defaultOptions.isFollowRedirects())
);
}
And then I injected this bean in my Feign Client Factory:
public MyClientFactory(Client client,
ObjectMapper objectMapper,
Request.Options options,
ErrorDecoder errorDecoder) {
this.builder = Feign.builder()
.client(client)
.decoder(new JacksonDecoder(objectMapper))
.encoder(new JacksonEncoder(objectMapper))
.options(options)
.errorDecoder(errorDecoder);
}

Related

Is there support for multiple feign.Client beans within Spring

Some context
The project is a spring boot application (v 2.3.12) using spring-cloud-openfeign-core (v 2.2.8) to make REST requests.
The service has 2 clients
one that needs to make REST requests using a proxy
one that needs to make REST request without a proxy
I'm unable to get both client to work simultaneously, i.e. have requests work for both proxied and non-proxied resources.
Is it possible to use different configuration files to support this, I have tried something like this
Configuration class X
#bean
public Client client1() {
return new Client.Default(null, null);
}
Configuration class Y
#bean
public Client client2() {
return new Client.Proxied(null, null,
new Proxy(Proxy.Type.HTTP, new InetSocketAddress("10.0.0.1", 8080)));
}
Feign interface code looks something like this
#Service
#FeignClient(name = "service1", url = "internal-url", configuration = X.class)
#Headers("Content-Type: application/json")
public interface serviceClient1 {
#PostMapping(value = "/v1/path}")
Response1 update(#RequestBody Request1 request1);
}
#Service
#FeignClient(name = "service2", url = "external-url", configuration = Y.class)
#Headers("Content-Type: application/json")
public interface serviceClient2 {
#PostMapping(value = "/v1/path}")
Response2 update(#RequestBody Request2 request2);
}
It seems only one of the client beans is used when making requests.

Duplicate config code problem Spring Boot microservices

Is there a way to create a spring configuration class and use it to all my microservices?
For example, I have to copy the following configuration class and paste it through all my microservices which means that when I want to make a small change I have to go through all the microservices editing the same class.
I have investigated and the most I could find is to create a module with all the classes in common and import it in my microservices by the pom, what happens with this is that when I want to get the SecurityContextHolder.getContext() this is null for a context issue and I do not know very well how to give solution to this or what other alternatives there are.
#Configuration
public class FeignGlobalConfiguration {
#Bean
public ErrorDecoder errorDecoder() {
return new RetrieveMessageErrorDecoder();
}
#Bean
public RequestInterceptor requestInterceptor(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return requestTemplate -> {
requestTemplate.header(JwtClaims.USERNAME, authentication.getPrincipal().toString());
requestTemplate.header(JwtClaims.CLIENT, authentication.getDetails().toString());
requestTemplate.header(JwtClaims.TOKEN, authentication.getCredentials().toString());
requestTemplate.header(JwtClaims.ROLES, authentication.getAuthorities().toString());
};
}
}
The problem is your bean definition.
The line Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); is called when the bean is constructed so only once. After that the reference is used (which is probably null at the time of construction.
To solve move tha tline inside the lambda so it gets evaluated each time a request is processed.
#Bean
public RequestInterceptor requestInterceptor(){
return requestTemplate -> {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
requestTemplate.header(JwtClaims.USERNAME, authentication.getPrincipal().toString());
requestTemplate.header(JwtClaims.CLIENT, authentication.getDetails().toString());
requestTemplate.header(JwtClaims.TOKEN, authentication.getCredentials().toString());
requestTemplate.header(JwtClaims.ROLES, authentication.getAuthorities().toString());
};
}

How do I make spring boot RestTemplate client metrics not create a new tag for query parameters

I've got a spring boot application that is defining a RestTemplate bean as follows:
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
Also, pulling in spring-boot-starter-actuator and io.micrometer:micrometer-registry-prometheus.
When I use the injected RestTemplate as follows:
#Autowired
private RestTemplate restTemplate;
private String uriTemplate = "http://my.domain.com/bookstore-api/books";
public List<Book> getBooksByAuthor(String author) {
// create URI for "http://my.domain.com/bookstore-api/books?author={authorId}"
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder
.fromUriString(uriTemplate)
.queryParam("author", author);
// make the GET
ResponseEntity<Book[]> responseEntity = restTemplate.getForEntity(uriComponentsBuilder.toUriString(),Book[].class);
// rest ommitted for brevity
}
When getBooksByAuthor("Tolkien") is called, we can then hit /metrics/prometheus and see the following:
http_client_requests_seconds_count{clientName="my.domain.com",method="GET",status="200",uri="/bookstore-api/books?author=Tolkien",} 2.0
http_client_requests_seconds_sum{clientName="my.domain.com",method="GET",status="200",uri="/bookstore-api/books?author=Tolkien",} 0.253227898
This would be fine, except that there are lots of authors out there, and eventually I will get the "too many tags" exception.
I would prefer to have the following (similar to how path variables get templated):
http_client_requests_seconds_count{clientName="my.domain.com",method="GET",status="200",uri="/bookstore-api/books?author={author}",} 2.0
http_client_requests_seconds_sum{clientName="my.domain.com",method="GET",status="200",uri="/bookstore-api/books?author={author}",} 0.253227898
Is this possible to achieve by modifying the way I use UriComponentsBuilder? The closest thing I've found is to define my own RestTemplateExchangeTagsProvider, and override the default to do some crazy regex replacement.
Just fixed same issue in SpringBoot 2.4.5 using:
responseEntity = restTemplate.exchange(
config.getDataUrl(),
HttpMethod.GET,
httpEntity,
new ParameterizedTypeReference<String>() {},
rowId);
where getDataUrl resolves to:
https://data-service-dev.apps.cloud.net/api/hbase/getData?rowId={rowId}
metrics:
http_client_requests_seconds_count{clientName="data-service-dev.apps.cloud.net",method="GET",outcome="SUCCESS",status="200",uri="/api/hbase/getData?rowId={rowId}",} 1.0
...
I have had a same problem. Maybe this information will help you.
In my case restTemplate.setUriTemplateHandler(handler) had overwritten annonymous
MetricsClientHttpRequestInterceptor#createUriTemplateHandler.
And the original url templates had not been stored into memory for prometheus.
DefaultUriBuilderFactory builderFactory = new DefaultUriBuilderFactory();
builderFactory.setEncodingMode(DefaultUriBuilderFactory.EncodingMode.NONE);
customizer.customize(restTemplate);
restTemplate.setUriTemplateHandler(handler);
So,
I changed order of the commands:
restTemplate.setUriTemplateHandler(handler);
customizer.customize(restTemplate);
Please check that there are no setting commands for restTemplate after MetricsClientHttpRequestInterceptor.customize(restTemplate).

How to set a custom Feign RequestInterceptor for specific clients?

I need to add custom Authorization header to some new feign clients. So I write an RequestInterceptor and it worked but the point is I don't want this custom RequestInterceptor affect my old clients. I tried to filter using template.url() method but it doesn't give me the entire url of the request and it only contains the client method url (not url and path which is announced above the client class).
My Question is that how can I target the interceptor?
This is my configuration:
#Configuration
open class FeignCustomConfiguration {
private fun generateToken(): String { ... }
#Bean
open fun requestInterceptor(): RequestInterceptor {
return RequestInterceptor {
it.header("Authorization", generateToken())
}
}
}
I found the solution.
For each FeignClient there is a configuration option which accepts an array of classes. The syntax of assigning a class to configuration in kotlin is as follow:
#FeignClient(
name = "feign-client",
path = "/path",
url = "https://example.com",
configuration = [FeignCustomConfiguration::class]
)
interface FeignCustomClient {
...
}
With this assignment, each FeignClient has its own configuration and RequestInterceptor doesn't deal with other clients.

Spring RestTemplate with Jackson throws "Can not resolve BeanPropertyFilter" when using #JsonFilter

Can I specify the Jackson ObjectMapper that Spring's RestTemplate uses?
I'm not 100% that's what I need to do but see below for details.
Background:
With help from this StackOverflow post I added #JsonFilter to my domain class and edited my jax-rs web service (implemented in CXF). I'm now successfully able to dynamically select which domain class fields to return in my RESTful API. So far so good.
I'm using Spring's RestTemplate in my JUnit tests to test my RESTful API. This was working fine until I added #JasonFilter to my domain class. Now I'm getting the following exception:
org.springframework.web.client.ResourceAccessException: I/O error: Can not resolve BeanPropertyFilter with id 'apiFilter'; no FilterProvider configured; nested exception is org.codehaus.jackson.map.JsonMappingException: Can not resolve BeanPropertyFilter with id 'apiFilter'; no FilterProvider configured
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:453)
rest of stack trace omitted for brevity
Caused by: org.codehaus.jackson.map.JsonMappingException: Can not resolve BeanPropertyFilter with id 'apiFilter'; no FilterProvider configured
at org.codehaus.jackson.map.ser.BeanSerializer.findFilter(BeanSerializer.java:252)
I was getting a similar problem on the server side and was able to resolve it (with help from this post) by giving a FilterProvider to the Jackson ObjectMapper as follows:
ObjectMapper mapper = new ObjectMapper();
FilterProvider filters = new SimpleFilterProvider().addFilter("apiFilter", SimpleBeanPropertyFilter.filterOutAllExcept(filterProperties));
Can I do something similar on the RestTemplate side? Any ideas of how to solve this issue are appreciated.
Just to be clear, on the client RestTemplate side I do not want to filter the domain object properties at all.
Can I specify the Jackson ObjectMapper that Spring's RestTemplate uses?
I was able to force RestTemplate to use a customized ObjectMapper by doing the following:
ObjectMapper mapper = new ObjectMapper();
// set a custom filter
Set<String> filterProperties = new HashSet<String>();
FilterProvider filters = new SimpleFilterProvider().addFilter("apiFilter", SimpleBeanPropertyFilter.serializeAllExcept(filterProperties));
mapper.setFilters(filters);
MappingJacksonHttpMessageConverter messageConverter = new MappingJacksonHttpMessageConverter();
messageConverter.setObjectMapper(mapper);
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
messageConverters.add(messageConverter);
restTemplate.setMessageConverters(messageConverters);
This website provided example for part of the above code.
Just adding to the answer. If you are using TestRestTemplate then you can actually get the underlying RestTemplate class and then modify its MappingJackson2HttpMessageConverter to include your filter:
var jackson2HttpMessageConverter = testRestTemplate.getRestTemplate().getMessageConverters().stream()
.filter(mc -> mc instanceof MappingJackson2HttpMessageConverter)
.map(mc -> (MappingJackson2HttpMessageConverter) mc)
.findFirst()
.orElseThrow();
jackson2HttpMessageConverter.getObjectMapper().setFilterProvider(
new SimpleFilterProvider().addFilter("MyFilterName", SimpleBeanPropertyFilter.serializeAll())
);

Resources