I can't find how to pass a Map of query parameters to uriBuilder even with a method found on Google which explains that we need to pass a LinkedMultiValueMap.
This is my method :
public <T> Mono<T> get(String uri, LinkedMultiValueMap params) {
return this.webClient
.get()
.uri(builder -> builder
.path(uri)
.queryParam(params)
.build())
.retrieve()
.bodyToMono(new ParameterizedTypeReference<T>() {
});
}
But I always have the following error :
queryParam
(java.lang.String,
Object...)
in UriBuilder cannot be applied
to
(org.springframework.util.LinkedMultiValueMap)
It's probably really simple but I can't figured it out :/
Thanks for your help.
UPDATE :
Finally found the solution a few minutes after sending my request here ! In order to use a map for queryParam I must use .queryParams instead of .queryParam ^^
Related
Scenario: need to get an access token from a service and pass it to a webclient call as below.
return someservice
.getToken() //returns token as Mono<String>,this itself is another webclient call
.flatMap(token -> {
return customWebclient.delete() //observe the delete method here
.uri(uri -> uri.path(/users)
.queryParam("id", id)
.build())
.headers(headers -> headers.setBearerAuth(token))
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
.header("Accept", MediaType.APPLICATION_JSON_VALUE)
.retrieve()
.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {
}).log();
}); // this return a Mono<Map<String, Object>>
I then need to block this final result and so I am using .toFuture().get() to get Map<String, Object>.
Now the issue is .get() call here is waiting indefinitely and the call to customWebClient call is never happening and if I get use .get(3000, TimeUnit.SECONDS), get() is throwing a TimedOutException and then calling the customWebClient call.
From what I understand, get() method should wait for Mono<Map<String, Object>> to resolve i.e, customWebclient call to happen and then return the result.
Using spring-boot-starter-webflux
Please help me with a solution.
I have also tried not nesting these calls and used toFuture().get() for both token and the Map, get() for token is waiting forever.
Other important point is that the same customWebclient call for get() method in same way as example is working fine.
Try with adding ".subscribeOn(Schedulers.boundedElastic())" before .toFuture().get(30L, TimeUnit.SECONDS).
i'm trying to send a POST request with body data as described here: https://scrapyrt.readthedocs.io/en/stable/api.html#post.
Here's what i've tried to do but it gives me HTTP code 500
String uri = "http://localhost:3000";
WebClient webClient = WebClient.builder()
.baseUrl(uri)
.build();
LinkedMultiValueMap map = new LinkedMultiValueMap();
String q = "\"url\": \"https://blog.trendmicro.com/trendlabs-security-intelligence\",\"meta\":{\"latestDate\" : \"18-05-2020\"}}";
map.add("request", q);
map.add("spider_name", "blog");
BodyInserter<MultiValueMap<String, Object>, ClientHttpRequest> inserter2
= BodyInserters.fromMultipartData(map);
Mono<ItemsList> result = webClient.post()
.uri(uriBuilder -> uriBuilder
.path("/crawl.json")
.build())
.body(inserter2)
.retrieve()
.bodyToMono(ItemsList.class);
ItemsList tempItems = result.block();
Here's what i've tried to do but it gives me HTTP code 500
Most likely because you're sending the wrong data in a mixture of wrong formats with the wrong type:
You're using multipart form data, not JSON
You're then setting the request parameter as a JSON string (q)
The JSON string you're using in q isn't even valid (it's at least missing an opening curly brace) - and handwriting JSON is almost universally a bad idea, leverage a framework to do it for you instead.
Instead, the normal thing to do would be to create a POJO structure that maps to your request, so:
public class CrawlRequest {
private CrawlInnerRequest request;
#JsonProperty("spider_name")
private String spiderName;
//....add the getters / setters
}
public class CrawlInnerRequest {
private String url;
private String callback;
#JsonProperty("dont_filter")
private String dontFilter;
//....add the getters / setters
}
...then simply create a CrawlRequest, set the values as you wish, then in your post call use:
.body(BodyInserters.fromValue(crawlRequest))
This is a rather fundamental, basic part of using a WebClient. I'd suggest reading around more widely to give yourself a better understanding of the fundamentals, it will help tremendously in the long run.
For me following code worked:
public String wcPost(){
Map<String, String> bodyMap = new HashMap();
bodyMap.put("key1","value1");
WebClient client = WebClient.builder()
.baseUrl("domainURL")
.build();
String responseSpec = client.post()
.uri("URI")
.headers(h -> h.setBearerAuth("token if any"))
.body(BodyInserters.fromValue(bodyMap))
.exchange()
.flatMap(clientResponse -> {
if (clientResponse.statusCode().is5xxServerError()) {
clientResponse.body((clientHttpResponse, context) -> {
return clientHttpResponse.getBody();
});
return clientResponse.bodyToMono(String.class);
}
else
return clientResponse.bodyToMono(String.class);
})
.block();
return responseSpec;
}
hoping someone can steer me in the right direction in turning my code into a more reactive service call. for background I have a preceding function that will generate a list of users, will then use that list to call this getUserDetails function for each user in the list, and return a map or list of user + details.
#HystrixCommand(commandKey = "getUserDetails")
public getUserResponse getUserDetails(userRequest request) {
getUserResponse response = webClient.post()
.uri(uri)
.body(BodyInserters.fromObject(request))
.retrieve()
.onStatus(HttpStatus::isError, resp -> resp.bodyToMono(getUserError.class).map(errorHandler::mapRequestErrors))
.bodyToMono(getUserResponse.class).block();
return response;
}
Ideally I would also replace/remove the error mapping as only concerned with logging the returned error response and continuing.
so far I have thought something along the lines of this but I'm not sure the webflux/hystrix will play nice?
#HystrixCommand(commandKey = "getUserDetails", fallbackMethod = "getUserFallback")
public Mono<getUserResponse> getUserDetails(userRequest request) {
return = webClient.post()
.uri(uri)
.body(BodyInserters.fromObject(request))
.retrieve()
.bodyToMono(getUserResponse.class);
}
#HystrixCommand
public Mono<getUserResponse> getUserFallback(userRequest request, Throwable throwable) {
log.error(//contents of error message returned)
return mono.empty();
}
public Flux<UserMap> fetchUserDetails(List<Integer> userIds) {
return Flux.fromIterable(userIds)
.parallel()
.runOn(Schedulers.elastic())
.flatMap(userDetailsRepository::getUserDetails);
}
Hystrix is deprecated. If you have a chance, move to resilience4j which has support for Webflux/Reactor.
Spring also has dedicated support for resilience4j.
Regarding error handling you can leverage the rich set of operators from the Mono/Flux API like onErrorReturn or onErrorResume.
I'm trying to replace a resttemplate implementation with a webclient one. The tricky stuff here is that I need to modify a property from an input object, when the response resolves. I don't find the way to achieve it...
This is the resttemplate code:
public Instance login(final Instance instancia, final LoginDTO dto) {
String url = instancia.getBalancer() + API_AUTHENTICATE_PATH;
HttpEntity<LoginDTO> request = generateRequest(dto);
ResponseEntity<JWTToken> token = restTemplate.postForEntity(url, request, JWTToken.class);
instancia.setToken(token.getBody().getIdToken());
return instancia;
}
And this is what I have until now:
#Override
public Mono<Instance> login(Instance instancia, LoginDTO dto) {
Mono<JWTToken> monoToken=webClient.post().uri(url).body((BodyInserters.fromObject(dto))).retrieve()
.bodyToMono(JWTToken.class);
return {....};
}
I'm stucked in that part, because I don't find the way to alter the Instance object...
And there is another point: This is injected in another class, because I need to run this request in parallel against multiple targets. So, a block call is not enough.
Does someone have an idea about how to do it?
Thanks a lot in advance!
It can be achieved easily as following:
#Override
public Mono<Instance> login(Instance instancia, LoginDTO dto) {
return webClient
.post()
.uri(url)
.body((BodyInserters.fromObject(dto)))
.retrieve()
.bodyToMono(JWTToken.class)
.map(token -> {
instancia.setToken(token.getBody().getIdToken());
return instancia;
});
}
I am using WebClient to do an API post but it is not returning anything. I'm assuming that the thread is staying open and not completing since I can use a block to get what I want but I'm still pretty new to WebClient and asynchronous stuff so I'm not 100% sure.
Specifically I have this method:
public Mono<AppDto> applicationPost(AppDto dto){
return webClient.post()
.uri("/api/doStuff")
.contentType(MediaType.APPLICATION_JSON)
.body(MonoConverter.appDtoToMono(dto), String.class)
.exchange()
.flatMap(clientResponse -> clientResponse.bodyToMono(Map.class))
.map(MonoConverter::mapValueToAppDto);
}
Where MonoConverter does some conversion for mapping values so this should be irrelevant. The above returns a 202 Accepted but it does not return a value or hit my mapValueToAppDto method. The below however, does work:
public Mono<AppDto> applicationPost(AppDto dto){
Map map = webClient.post()
.uri("/api/doStuff")
.contentType(MediaType.APPLICATION_JSON)
.body(MonoConverter.appDtoToMono(dto), String.class)
.exchange()
.flatMap(clientResponse -> clientResponse.bodyToMono(Map.class))
.block();
return Mono.just(MonoConverter.mapValueToAppDto(map));
}
I'm assuming that this works since it uses block but then a get method I have that is in a similar fashion works:
public Mono<AppDto> applicationGetOne(String appId){
return webClient.get()
.uri("/api/getOne/{0}",appId)
.exchange()
.flatMap(clientResponse -> clientResponse.bodyToMono(Map.class))
.map(MonoConverter::mapValueToAppDto);
}
I would prefer to use the first snippet since it does not use block and it's simpler and in the same format as my other methods.
Does anyone have any idea why the first one isn't working or know how I could get it to work?
I found the reason why I was having this issue. It actual had to do with my controller
(D'oh!). For the post method, I have validation that binds errors so I was just returning a ResponseEntity without giving it a type. So I added a typing to the ResponseEntity and that fixed the issue.
e.g.:
#PostMapping(value="/post")
public ResponseEntity addThing(#Validated #RequestBody AppDto dto, BindingResult result){
...
}
And what fixed it:
#PostMapping(value="/post")
public ResponseEntity<Mono<?>> addThing(#Validated #RequestBody AppDto dto, BindingResult result){
...
}
I'm assuming that since at first the typing wasn't specified it wasn't using the thread the mono response was on and so I was never getting a response but by declaring the type, Spring knows to use the Mono thus allowing it to complete.