sleuth traceId is not propagated using WebClient - spring-boot

I have this endpoint:
#GetMapping("processor")
public Flux<String> getSomeStringsAndProcessThem() {
log.info("processing some strings");
return WebClient.create().get()
.uri(uriBuilder -> uriBuilder.path("localhost:8083/emitter")
.build())
.retrieve()
.bodyToFlux(String.class)
.map(v -> {
log.info("In map operator, processing {} ", v);
return switch (v) {
case "FIRST" -> "Number One\n";
case "THIRD" -> "Number three\n";
case "FIFTH" -> "Number five\n";
case "TENTH" -> "Number ten\n";
default -> v+"\n";
};
});
}
I see from the logs that the traceId from this service is not the same with the called service. (traceId is not propagated)
Any reason why WebClient is not propagating the traceId, does there need to be more config for sleuth to work with webflux ?
All sleuth config is spring default.
Using spring version
<version>2.7.6-SNAPSHOT</version>
and cloud
<spring-cloud.version>2021.0.4</spring-cloud.version>

To make it work had to autowire
WebClient.Builder webClientBuilder;
and instead of
WebClient.create().get()
.uri(uriBuilder -> uriBuilder.path("localhost:8083/emitter")
.build()).retrieve
I used
return webClientBuilder.baseUrl("http://"localhost:8083/emitter").build().get()
.retrieve()

Related

Stackoverflow when retrieving jwt token in WebTestClient and seting it in ExchangeFilterFunction

The latest Spring Boot 2.3.1.RELEASE, Java 11.
private ExchangeFilterFunction userJwtAuthentication() {
return ExchangeFilterFunction.ofRequestProcessor(
request -> generateToken("user")
.map(jwt -> ClientRequest.from(request)
.headers(headers -> headers.setBearerAuth(jwt))
.build()
)
);
}
private Mono<String> generateToken(String username) {
return this.client
.post().uri("/auth/login")
.bodyValue(AuthenticationRequest.builder().username(username).password("password").build())
.exchange()
.returnResult(new ParameterizedTypeReference<Map<String, String>>() {
})
.getResponseBody()
.last()
.map(d -> d.get("access_token"))
.doOnSubscribe(
jwt -> log.debug("generated jwt token::" + jwt)
);
}
And use it in tests
client.mutate().filter(userJwtAuthentication()).build()
When testing my APIs, it returns.
ava.lang.StackOverflowError
at java.base/java.util.regex.Pattern$Branch.match(Pattern.java:4749)
at java.base/java.util.regex.Pattern$GroupTail.match(Pattern.java:4863)
at java.base/java.util.regex.Pattern$CharPropertyGreedy.match(Pattern.java:4306)
at java.base/java.util.regex.Pattern$GroupHead.match(Pattern.java:4804)
at java.base/java.util.regex.Pattern$Branch.match(Pattern.java:4747)
at java.base/java.util.regex.Pattern$Branch.match(Pattern.java:4747)
at java.base/java.util.regex.Pattern$Begin.match(Pattern.java:3683)
at java.base/java.util.regex.Matcher.match(Matcher.java:1756)
at java.base/java.util.regex.Matcher.matches(Matcher.java:713)
at org.springframework.web.util.UriComponentsBuilder.fromUriString(UriComponentsBuilder.java:215)
at org.springframework.web.util.DefaultUriBuilderFactory$DefaultUriBuilder.initUriComponentsBuilder(DefaultUriBuilderFactory.java:242)
at org.springframework.web.util.DefaultUriBuilderFactory$DefaultUriBuilder.<init>(DefaultUriBuilderFactory.java:233)
at org.springframework.web.util.DefaultUriBuilderFactory.uriString(DefaultUriBuilderFactory.java:160)
at org.springframework.web.util.DefaultUriBuilderFactory.expand(DefaultUriBuilderFactory.java:153)
at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultRequestBodyUriSpec.uri(DefaultWebClient.java:176)
at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultRequestBodyUriSpec.uri(DefaultWebClient.java:151)
at org.springframework.test.web.reactive.server.DefaultWebTestClient$DefaultRequestBodyUriSpec.uri(DefaultWebTestClient.java:163)
at org.springframework.test.web.reactive.server.DefaultWebTestClient$DefaultRequestBodyUriSpec.uri(DefaultWebTestClient.java:146)
at com.example.demo.IntegrationTests.generateToken(IntegrationTests.java:246)
at com.example.demo.IntegrationTests.lambda$adminJwtAuthentication$5(IntegrationTests.java:236)
at org.springframework.web.reactive.function.client.ExchangeFilterFunction.lambda$ofRequestProcessor$3(ExchangeFilterFunction.java:79)
at org.springframework.web.reactive.function.client.ExchangeFilterFunction.lambda$apply$2(ExchangeFilterFunction.java:68)
at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultRequestBodyUriSpec.lambda$exchange$0(DefaultWebClient.java:338)
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:44)
at reactor.core.publisher.Mono.subscribe(Mono.java:4219)
at reactor.core.publisher.Mono.block(Mono.java:1702)
at org.springframework.test.web.reactive.server.DefaultWebTestClient$DefaultRequestBodyUriSpec.exchange(DefaultWebTestClient.java:307)
...
If I used blockLast to retrieve token firstly, it worked., my question is how to use Reactor API to do the same work.
The complete codes is here.

Spring WebClient - chaining async calls

I'm building a plain old Java desktop application with Swing, but needs to make Rest calls to a backend. Since we're in a modern async world, and the tools exists, i want to make async calls to the backend, display a progressbar, and hide it when the calls are done.
Using Spring WebClient seems the way to go, and it works well, until i need to chain multiple async Rest calls. In that case, the second call never respond... I see the backend really receives the 2 calls, but the client side never resumes.
public void notWorking(CurrentTool toolDto, Consumer<ToolDto> successCallback) {
webClient.post()
.uri(uriBuilder -> {
return uriBuilder
.path("/tools/{id}")
.build(toolDto.getTool().getToolId());
})
.body(BodyInserters.fromObject(toolDto.getTool()))
.retrieve()
.bodyToMono(Long.class)
.subscribe(id -> {
webClient.get()
.uri(uriBuilder -> {
return uriBuilder
.path("/tools/{id}")
.build(id);
})
.retrieve()
.bodyToMono(ToolDto.class)
.subscribe((response) -> {
successCallback.accept(response);
});
});
}
However, if I make the same calls but in a blocking way, everything works fine. (Except it's sync, so my loading bar won't work...)
public void working(CurrentTool toolDto, Consumer<ToolDto> successCallback) {
Long id = webClient.post()
.uri(uriBuilder -> {
return uriBuilder
.path("/tools/{id}")
.build(toolDto.getTool().getToolId());
})
.body(BodyInserters.fromObject(toolDto.getTool()))
.retrieve()
.bodyToMono(Long.class)
.block();
webClient.get()
.uri(uriBuilder -> {
return uriBuilder
.path("/tools/{id}")
.build(id);
})
.retrieve()
.bodyToMono(ToolDto.class)
.subscribe((response) -> {
successCallback.accept(response);
});
}
You should try to avoid nesting subscribes. You can use flatmap instead.
public void shouldBeWorking(CurrentTool toolDto, Consumer<ToolDto> successCallback) {
webClient.post()
.uri(uriBuilder -> uriBuilder
.path("/tools/{id}")
.build(toolDto.getTool().getToolId()))
.body(BodyInserters.fromObject(toolDto.getTool()))
.retrieve()
.bodyToMono(Long.class)
.flatmap(id -> webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/tools/{id}")
.build(id))
.retrieve()
.bodyToMono(ToolDto.class))
.subscribe(successCallback::accept);
}

How to set base url and query parameters for WebClient?

In my service, I have to get response from some different urls with parameters.
get from http://a.com:8080/path1?param1=v1
get from http://b.com:8080/path2?param2=v2
get from http://c.com:8080/path3?param3=v3
I am using WebClient to do the job as following.
public class WebClientTest {
private WebClient webClient = WebClient.builder().build();
#Test
public void webClientTest() {
Mono<String> a = webClient.get()
.uri(uriBuilder -> uriBuilder.scheme("http").host("a.com").port(8080).path("/path1")
.queryParam("param1", "v1")
.build())
.retrieve()
.bodyToMono(String.class);
Mono<String> b = webClient.get()
.uri(uriBuilder -> uriBuilder.scheme("http").host("b.com").port(8080).path("/path2")
.queryParam("param2", "v2")
.build())
.retrieve()
.bodyToMono(String.class);
Mono<String> c = webClient.get()
.uri(uriBuilder -> uriBuilder.scheme("http").host("c.com").port(8080).path("/path3")
.queryParam("param3", "v3")
.build())
.retrieve()
.bodyToMono(String.class);
//zip the result
}
}
As you can see, I have to set scheme, host, port separately again and again.
So my questions are:
1. Am I using WebClient in a right way?
2. Is it possible to set scheme, host, port in a method together? I know that webClient.get().uri("http://a.com:8080/path1?param1=v1").retrieve() works, but what I am expecting is something like:
webClient.get()
.uri(uriBuilder -> uriBuilder/*.url("http://a.com:8080/path1")*/
.queryParam("param1", "v1")
.build())
.retrieve()
.bodyToMono(String.class);
As of Spring Framework 5.2, there is an additional method that can help with your specific situation:
Mono<String> response = this.webClient
.get()
.uri("http://a.com:8080/path1", uri -> uri.queryParam("param1", "v1").build())
.retrieve()
.bodyToMono(String.class);
I wouldn't advise creating one WebClient per host as a general rule. It really depends on your use case. Here it seems your client might send requests to many hosts, and creating many HTTP clients can be a bit wasteful here.
The way I solved this was to have a WebClient for each different url.
So you would have
private WebClient aClient = WebClient.create("a.com")
private WebClient bClient = WebClient.create("b.com")
private WebClient cClient = WebClient.create("c.com")
Then interact with each WebClient depending on what you're calling.
https://docs.spring.io/spring/docs/5.0.7.RELEASE/spring-framework-reference/web-reactive.html#webflux-client-retrieve

Multiple timeouts for request spring WebFLux

Hi im using webClient from spring webflux. i have some code like:
#Configuration
class WebClientConfig(
#Value("\${url}")
private val url: String
) {
#Bean
#Primary
fun webClient(): WebClient {
return createWebClient(700)
}
#Bean("more_timeout")
fun webClientMoreTimeout(): WebClient {
return createWebClient(3000)
}
private fun createWebClient(timeout: Int): WebClient{
val httpClient = HttpClient.create()
.tcpConfiguration { client -> client.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, timeout) }
return WebClient.builder()
.baseUrl(url)
.clientConnector(ReactorClientHttpConnector(httpClient))
.build()
}
}
This configuration is because i need calls with different timeout. Supose i have one service A which is very importart for my response so i want to wait for the response maximum 3 seconds, and supose y have another services B, C, etc. which are not very important for my response, i will only wait 700ms to generate the response. Who can i archive this?
The previous config is not working because webClient is inmutable.
I think you can't do it at webClient level, but you can do it at Reactor level, something like:
return webClient.post()
.uri { uriBuilder ->
uriBuilder.path(PATH)
.build()
}
.body(BodyInserters.fromObject(Request()))
.accept(MediaType.APPLICATION_JSON_UTF8)
.exchange()
.timeout(Duration.ofMillis(1000L))

Spring Boot 2 - Transforming a Mono to a rx.Observable?

I'm trying to use the HystrixObservableCommand with the Spring WebFlux WebClient and I wonder if there is a "clean" to transform a Mono to an rx.Observable. My initial code looks like this:
public Observable<Comment> getComment() {
return webClient.get()
.uri(url)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Comment.class)
// stuff missing here :(.
}
Is there an easy to do this ?
Regards
The recommended approach is to use RxJavaReactiveStreams, more specifically:
public Observable<Comment> getComment() {
Mono<Comment> mono = webClient.get()
.uri(url)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Comment.class);
return RxReactiveStreams.toObservable(mono); // <-- convert any Publisher to RxJava 1
}
You can use
Observable.fromFuture(webClient.get()
.uri(url)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Comment.class).toFuture());

Resources