How to allow param with curly braces in Url when using WebClient? - spring

I am getting IllegalArgumentException
"Not enough variable values available to expand 'email'" when calling an endpoint with curly braces in the url. I don't want to encode it since the endpoint is throwing 500 after the url is ecoded.
#Override
public Mono<UserInfoByEmailV2> findByEmail(String env, String email) {
webClient = getTokenAndSetupWebClient(env, webClient, log);
// Param email here is like {{test}}#test.com
return webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/user/?email={email}")
.replaceQueryParam("email", email)
.build())
.retrieve()
.bodyToMono(UserInfoByEmailV2.class);
}

I found the answer;
#Override
public Mono<UserInfoByEmailV2> findByEmail(String env, String email) {
webClient = getTokenAndSetupWebClient(env, webClient, log);
return webClient.get()
.uri("/user?email={test}", email)
.retrieve()
.bodyToMono(UserInfoByEmailV2.class);
}

Related

How do I make a post / get request to a endpoint with a requestHeader?

Method in question
#GetMapping("/all")
public Mono<ResponseEntity<String>> getSomeData(#RequestHeader String someId) {
...some code
}
Tried to call the consume the endpoint with this method:
#Autowired
WebClient.Builder webClient;
String someString = webClient.
.get()
.uri(someUrl)
.header("someId", "someString")
.retrieve()
.bodyToMono(String.class)
.block();
I got a status 415 with Unsupported media type with "Content type '' not supported"
How do I use webClientBuilder to set my id header?
You just need to set the correct content-type. If your controller expects it to be "plain/text" you might have to set that explicitly within your requesting client. 415 does indicate a miss match.
As mentioned by #Alex you are autowiring builder instead look for the concrete implementation of WebClient. Please check my WebClient config bean. But that is not the actual issue.
When you are sending body with webClient you have to use
.body(...)
so for sending plain text body where controller is expecting plain body you need something like below:
.body(BodyInserters.fromProducer(Mono.just("random body"), String.class))
and when controller is expecing an object is request you need to use something like this
.body(BodyInserters.fromProducer(Mono.just(new Greet("Hello there this is the body of post request")), Greet.class))
Greet.java
public static class Greet {
String name;
public Greet() {
}
public Greet(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Configuration of WebCLient
#Configuration
class WebClientConfig {
#Bean
WebClient webClient() {
return WebClient.builder().baseUrl("http://localhost:8080/").build();
}
}
#RequestMapping("/sample")
#RestController
static class SampleComntroller {
private final WebClient webClient;
#Autowired
SampleComntroller(WebClient webClient) {
this.webClient = webClient;
}
#GetMapping(value = "/main-get")//, consumes = MediaType.APPLICATION_JSON_VALUE)
public Mono<String> helloGet(#RequestHeader(name = "someId") String someId) {
return Mono.just("Hello, Spring!, get, response with header is=>" + someId);
}
#PostMapping(value = "/main-post-plain-string", consumes = MediaType.TEXT_PLAIN_VALUE)
public Mono<String> helloPost(#RequestHeader(name = "someId") String someId, #RequestBody String body) {
return Mono.just("Hello, Spring!, post, response with header is=>" + someId + " and random body " + UUID.randomUUID().toString());
}
#PostMapping(value = "/main-post-object", consumes = MediaType.APPLICATION_JSON_VALUE)
public Mono<String> helloPostObject(#RequestHeader(name = "someId") String someId, #RequestBody Greet greet) {
return Mono.just("Hello, Spring!, post, response with header is=>" + someId + " " + greet.getName() + " " + UUID.randomUUID().toString());
}
#GetMapping("/delegate-get")
public String delegateGet() {
return webClient
.get()
.uri("/sample/main-get")
.header("someId", "178A-0E88-get")
.retrieve().bodyToMono(String.class).block();
}
#PostMapping("/delegate-post")
public String delegatePost() {
return webClient
.post()
.uri("/sample/main-post-plain-string")
.body(BodyInserters.fromProducer(Mono.just("random body"), String.class))
.header("someId", "178A-0E88-post")
.retrieve()
.bodyToMono(String.class).block();
}
#PostMapping("/delegate-post-object")
public String delegatePostObject() {
return webClient
.post()
.uri("/sample/main-post-object")
.body(BodyInserters.fromProducer(Mono.just(new Greet("Hello there this is the body of post request")), Greet.class))
.header("someId", "178A-0E88-post")
.retrieve()
.bodyToMono(String.class).block();
}
}

How to receive a Map<String, Integer> from an endpoint using Spring WebClient get?

How can I receive a Map<String, Integer> from an endpoint web service using WebClient in Spring Boot? Here is my try: (it gives syntax error: Incompatible equality constraint: Map<String, Integer> and Map). How can I fix it?
public Flux<Map<String, Integer>> findAll(String param1, String param2) {
return webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/url")
.queryParam("param1", param1)
.queryParam("param2", param2)
.build())
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToFlux(Map.class);
}
For generic types, like the Map, you should use ParameterizedTypeReference instead of a class in the call to the bodyToFlux method:
public Flux<Map<String, Integer>> findAll(String param1, String param2) {
return webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/url")
.queryParam("param1", param1)
.queryParam("param2", param2)
.build())
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToFlux(new ParameterizedTypeReference<>() {});
}
In practice, probably you would like to define a constant for the type reference:
private static final ParameterizedTypeReference<Map<String, Integer>> MAP_TYPE_REF = new ParameterizedTypeReference<>() {};
public Flux<Map<String, Integer>> findAll(String param1, String param2) {
return webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/url")
.queryParam("param1", param1)
.queryParam("param2", param2)
.build())
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToFlux(MAP_TYPE_REF);
}

Spring WebClient : Implement Fallback method

I want to call my fall-back API when my actual API is taking more than 1 second
#GetMapping("/{id}")
public String getDetailsById(#PathVariable Long id) {
var url = getAPIUrl(id);
var response = webClient.get()
.uri(url)
.retrieve()
.onStatus(HttpStatus::isError,this::myFallBackMethod)
.bodyToMono(String.class)
.timeout(Duration.ofSeconds(1))
.block();
return response;
}
private Mono<? extends Throwable> myFallBackMethod(ClientResponse clientResponse) {
return Mono.just("");
}
I get two compile exceptions
Incompatible types
and
cannot resolve methoe myFallBackMethod
How to handle fall backs and return the String ?
I was able to do that my calling the function onErrorResume
#GetMapping("/{id}")
public String getDetailsById(#PathVariable Long id) {
var url = getAPIUrl(id);
var response = webClient.get()
.uri(url)
.retrieve()
.bodyToMono(String.class)
.timeout(Duration.ofSeconds(1))
.onErrorResume(throwable -> myFallBackMethod(id,throwable))
.block();
return response;
}
private Mono<? extends String> myFallBackMethod(Long id, Throwable throwable) {
return Mono.just("test sample");
}

SpringBoot webflux authenticate using query parameter

In Springboot webflux, I can get the current principle using this code
Object principal = ReactiveSecurityContextHolder.getContext().getAuthentication().getPrincipal();
If the user is authenticated. But I have a case in which the JWT token will be sent as a query paramenter not as the authorization header, I know how to convert the token into Authentication object
How i can inject that Authentication object into the current ReactiveSecurityContextHolder
You can set your own Authentication and take the token from query params as follows:
#Component
public class CustomAuthentication implements ServerSecurityContextRepository {
private static final String TOKEN_PREFIX = "Bearer ";
#Autowired
private ReactiveAuthenticationManager authenticationManager;
#Override
public Mono<Void> save(ServerWebExchange serverWebExchange, SecurityContext securityContext) {
throw new UnsupportedOperationException("No support");
}
#Override
public Mono<SecurityContext> load(ServerWebExchange serverWebExchange) {
ServerHttpRequest request = serverWebExchange.getRequest();
String authJwt = request.getQueryParams().getFirst("Authentication");
if (authJwt != null && authJwt.startsWith(TOKEN_PREFIX)) {
authJwt = authJwt.replace(TOKEN_PREFIX, "");
Authentication authentication =
new UsernamePasswordAuthenticationToken(getPrincipalFromJwt(authJwt), authJwt);
return this.authenticationManager.authenticate(authentication).map((authentication1 -> new SecurityContextImpl(authentication)));
}
return Mono.empty();
}
private String getPrincipalFromJwt(String authJwt) {
return authJwt;
}
}
This is a simple code block demonstrating how can you achieve your goal. You can improve getPrincipalFromJwt() method to return a different object that you would like to set as principal. Or you can use a different implementation of Authentication (as opposed to UsernamePasswordAuthenticationToken in this example) altogether.

Spring 5 WebClient only making Http call when using .block() after .exchange()

This call works as expected and makes the POST successfully:
public class MyService implements IMyService {
private final WebClient webClient;
private final String url;
MyService(#Qualifier("web-client") WebClient webClient,
String url) {
this.webClient = webClient;
this.url = url;
}
#SneakyThrows
#Override
public void execute(Long jobId) {
MultiValueMap<String, String> requestParms = new LinkedMultiValueMap<>();
requestParms.add("arguments", "--batchJobId=" + jobId.toString());
HttpEntity<MultiValueMap<String, String>> requestEntity =
new HttpEntity<>(requestParms, null);
final WebClient.ResponseSpec responseSpec = webClient.post()
.uri(new URI(url + "/tasks/executions"))
.body(BodyInserters.fromMultipartData(requestParms))
.exchange()
.block();
}
}
Inside the configuration class:
#Bean
#Qualifier("web-client")
public WebClient getWebClient() {
return WebClient.builder()
.filter(basicAuthentication("user", "pass"))
.filter(printLnFilter())
.build();
}
private ExchangeFilterFunction printLnFilter() {
return (request, next) -> {
System.out.println("\n\n" + request.method().toString().toUpperCase() + ":\n\nURL:"
+ request.url().toString() + ":\n\nHeaders:" + request.headers().toString() + "\n\nAttributes:"
+ request.attributes() + "\n\n");
return next.exchange(request);
};
}
In the example above, we see the URL, Attributes, and Headers logged and the Http call success fully made. However, just removing the block() call results in no call ever being made, no logs:
// No call made
final WebClient.ResponseSpec responseSpec = webClient.post()
.uri(new URI(url + "/tasks/executions"))
.body(BodyInserters.fromMultipartData(requestParms))
.exchange();
That's because it's non blocking...
From Spring Docs:
Simply put, WebClient is an interface representing the main entry
point for performing web requests.
It has been created as a part of the Spring Web Reactive module and
will be replacing the classic RestTemplate in these scenarios. The new
client is a reactive, non-blocking solution that works over the
HTTP/1.1 protocol.
It's an implementation using the Reactive Streams concept through the Project Reactor implementation

Resources