How to set base url and query parameters for WebClient? - spring

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

Related

Keycloak API using admin user

I'm working with keycloak API to access offline user's sessions; I noticed a strange behavior and thus my question:
a. When I use postman, I get the access token with this url: http://localhost:8080/realms/master/protocol/openid-connect/token
b. From the above, I use said token in postman to retrieve the offline sessions:
http://localhost:8080/admin/realms/master/clients/5729288b-c789-45ac-8915-da32b7b9fe49/offline-sessions
where '5729288b-c789-45ac-8915-da32b7b9fe49' is the admin-cli ID; username and password are all the defaults of the admin user and the client is 'admin-cli'
Everything works fine in postman, and I'm able to retrieve the offline sessions. However, when I do the same with the Keycloak API using the springboot webclient I get 403 Forbidden
a. Get the token from the below:
private String getToken(){
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("username", username);
map.add("password", password);
map.add("client_id", clientId);
map.add("grant_type", grantType);
map.add("scope", "openid");
ResponseEntity<LoginResponse> loginResponse = webclient.post()
.uri(uriBuilder -> UriBuilder.fromUri(tokenEndpoint).build())
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromFormData(map))
.retrieve()
.toEntity(LoginResponse.class)
.block();
return loginResponse.getBody().getAccess_token();
}
b. Try to retrieve offline sessions with the above access-token
public UserSessionRepresentation[] getMasterOfflineSessions(){
UserSessionRepresentation[] response = webclient.get()
.uri(uriBuilder -> UriBuilder.fromUri(offlineSessionsUrl)
.build(cliId))
.headers(h -> h.setBearerAuth(getToken()))
.retrieve()
.bodyToMono(UserSessionRepresentation[].class)
.block();
return response;
}
offlineSessionsUrl is: http://localhost:8080/admin/realms/master/clients/5729288b-c789-45ac-8915-da32b7b9fe49/offline-sessions
5729288b-c789-45ac-8915-da32b7b9fe49:is the id for the admin-cli client
What I don't understand is that I can retrieve the sessions in postman, but I can't do so using the API and the springboot webclient with all configurations being equal.
Please help
Answering my own question; the issue here was was the: webclient spring property
In springboot, it was using the definition within the configuration that pointed to another client. To make it work for the admin-cli client, I had to use a clean object of webclient as illustrated in the below code:
public UserSessionRepresentation[] getMasterOfflineSessions(){
UserSessionRepresentation[] response = WebClient.create().get()
.uri(uriBuilder -> UriBuilder.fromUri(offlineSessionsUrl)
.build(cliId))
.headers(h -> h.setBearerAuth(getToken()))
.retrieve()
.bodyToMono(UserSessionRepresentation[].class)
.block();
return response;
}
The WebClient.create() is the piece of code I changed to resolve the issue

Call REST endpoint using webclient which has both PathVariable and RequestParam

I have the following endpoint, which needs to be called from webclient. date is red using #PathVariable and name is red using #RequestParam annotations.
/api/v1/names/officialNameByDate/{date}?name=empName
Is the following usage correct?
String response = webclient.get()
.uri(uriBuilder -> uriBuilder
.path("/api/v1/names/officialNameByDate/{date}")
.queryParam("name", empName)
.build(date))
.retrieve()
.bodyToMono(String.class).block();
Yes, you can use both Path variable and Request Param.
webClient.get()
.uri(uriBuilder - > uriBuilder
.path("/products/{id}/attributes/{attributeId}")
.queryParam("name", "AndroidPhone")
.queryParam("color", "black")
.build(2, 13))
.retrieve()
.bodyToMono(String.class)
.block();
verifyCalledUrl("/products/2/attributes/13?color=black");
See above code as an example.

Spring boot - WebFlux - WebTestClient - convert response to responseEntity

I have a Reactive controller which returns:
#ResponseBody
#GetMapping("/data")
public Mono<ResponseEntity<Data>> getData() {
//service call which returns Mono<Data> dataMono
Mono<ResponseEntity<Data>> responseEntityMono = dataMono
.map(data -> ResponseEntity.ok(data))
.doFinally(signalType -> {});
return responseEntityMono;
}
I am using WebTestClient to test this endpoint, but I want to extract the response entity for cucumber to validate further.
I tried this:
#Autowired private WebTestClient webTestClient;
public ResponseEntity get() {
EntityExchangeResult < ResponseEntity > response = webTestClient.get()
.uri(uriBuilder ->
uriBuilder
.path(VISUALIZATION_URL)
.build())
.header("Accepts", "application/json")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON_VALUE)
.expectBody(ResponseEntity.class)
.returnResult();
return response.getResponseBody();
}
but I am getting an error. I can get the JSON by doing:
public String get() {
BodyContentSpec bodyContentSpec = webTestClient.get()
.uri(uriBuilder ->
uriBuilder
.path(URL)
.build())
.header("Accepts", "application/json")
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON_VALUE)
.expectBody();
return new String(bodyContentSpec.returnResult().getResponseBody());
}
But I am trying to see if I can get the whole ResponseEntity so that I can validate headers, caching headers, and body.
You will never be able to get a ResponseEntity in your test. WebTestClient.ResponseSpec returned from exchange() is the only way to check the answer from your Controller. ResponseEntity is just the object you return in your method but once Jackson serializes it, it is converted to a regular HTTP response (in your case with JSON in his body and regular HTTP headers).

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