Add query parameter in WebClient request - java

I am using Spring WebFlux where I am taking request and using same request to call another service. But I am not sure how to add query parameters. This is my code
#RequestMapping(value= ["/ptta-service/**"])
suspend fun executeService(request: ServerHttpRequest): ServerHttpResponse {
val requestedPath = if (request.path.toString().length > 13) "" else request.path.toString().substring(13)
return WebClient.create(this.dataServiceUrl)
.method(request.method ?: HttpMethod.GET)
.uri(requestedPath)
.header("Accept", "application/json, text/plain, */*")
.body(BodyInserters.fromValue(request.body))
.retrieve()
.awaitBody()
// But how about query parameters??? How to add those
}
Though code is in Kotlin, Java will help as well.

You can add the query parameters using lambda expression in uri, for more information see WebClient.UriSpec
return WebClient.create(this.dataServiceUrl)
.method(request.method ?: HttpMethod.GET)
.uri(builder -> builder.path(requestedPath).queryParam("q", "12").build())
.header("Accept", "application/json, text/plain, */*")
.body(BodyInserters.fromValue(request.body))
.retrieve()
.awaitBody()

Related

Can't get Boolean as BodyParameter from RouterFunction<ServerResponse> with BodyToMono

I'm trying to get a boolean parameter from an Angular 6 app and Spring can't handle it. I got the following error :
org.springframework.web.server.UnsupportedMediaTypeStatusException: 415 UNSUPPORTED_MEDIA_TYPE "Content type 'text/plain;charset=UTF-8' not supported for bodyType=java.lang.Boolean"
That's my front end code :
updateConfirmationDate(reportInfo: number, isItTrue: boolean): Observable<Boolean> {
const url = this.url;
return this.httpClient.patch(url, isItTrue).pipe(first(), catchError(err => console.log(err)));
}
And that's how i handle it on back :
Router :
public RouterFunction<ServerResponse> router() {
return RouterFunctions.route()
.path(apiPrefix, builder -> builder
.PATCH("/patchBooleanEndpoint", diagnosticHandler::updateBooleanEndpoint)
)
)
.build();
}
Handle :
#NonNull
public Mono<ServerResponse> updateMyBoolean(ServerRequest request) {
final Long id = Long.parseLong(request.pathVariable("id"));
return request.bodyToMono(Boolean.class)
.flatMap(myBoolean -> service.updateBooleanById(id, myBoolean))
.flatMap(savedBoolean ->
status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(Mono.just(savedBoolean), Boolean.class));
}
Thank you very much and have a nice day
You have to set content-Type to 'application/json' when you call the backend server from your Angular app.
something like
const headers = new HttpHeaders()
.set("Content-Type", "application/json");
And then set it to your patch request.
Based on the stack trace, the fronted call the backend server with content-Type = 'text/plain;charset=UTF-8' so it (the backend server) fails to parse the request body.

How to proxy payload using reactive Spring WebClient?

I have a webservice endpoint that should just proxy the received payload from another internal endpoint.
My goal is to neither having to read input body I receive, nor the response the I want to return. I just want to proxy it.
The following works, but it's probably suboptimal converting the response to a Mono<String>. But how could I do better?
#RestController
public class ProxyController {
#PostMapping("/proxy")
public Mono<Object> proxy(InputStream payload) {
return webClient.post().uri(url).bodyValue(payload).retrieve().bodyToMono(String.class);
}
}
This is what I used to do using rest template
#RequestMapping("/pass-to-service/**")
fun passThroughPostRequest(request: HttpServletRequest, #RequestBody body: Any?): ResponseEntity<String> {
val method = HttpMethod.resolve(request.method)!!
val requestEntity = RequestEntity(body, method, URI.create(myServiceUrl))
val responseEntity = restTemplate.exchange(requestEntity, String::class.java)
// response entity might have crazy headers, so add some decent/needed and ship back
val httpHeaders = HttpHeaders()
httpHeaders.contentType = MediaType.APPLICATION_JSON
return ResponseEntity(responseEntity.body, httpHeaders, responseEntity.statusCode)
}
In above example, I avoided as much of serialisation and deserialisation that I could. Kept it a passthrough from servlet.
Similarly, I am trying something like this using webclient:
#PostMapping("/v1/cars/{carId}/details")
fun ingestCarInfo(
#PathVariable("carId") carId: UUID,
request: HttpServletRequest, response: HttpServletResponse, #RequestBody body: Mono<CarDetailsReqDto>
) {
/** Step 1: I wanted to do some activity here */
/** Step 2: return a success response immediately, as my client do not care about the data processed.
* What I am unsure here? Does this return immediately without getting to next step
* */
response.setStatus(HttpStatus.OK.value())
/** Step 3: Fire & Forget request */
val uri = UriComponentsBuilder
.fromUriString("http://localhost:8080")
.path("/v3/cars/{carId}/details")
.build().encode().toUri()
webClient.method(HttpMethod.POST).uri(uri)
.body(BodyInserters.fromValue(body))
.header("OnewayRequest", "true")
.retrieve()
.toBodilessEntity()
.block()
}
Here, I haven't made everything generic, my body still has a shape defined but reactive. If it is a wildcard pass-through I would type it Mono<Any?>
NOTE: Still I am working on this. Will update once I find a better solution, also I need to check the performance and speed in realtime.

BadRequest: 400 Bad Request

I want to make a Authorization request using Spring Webflux to Paysafe test environment. I tried this:
Mono<AuthorizeRequest> transactionMono = Mono.just(transaction);
return client.post().uri("https://api.test.paysafe.com/cardpayments/v1/accounts/{id}/auths", "123456789")
.header(HttpHeaders.CONTENT_TYPE, "application/json")
.header(HttpHeaders.AUTHORIZATION, "Basic dGVz.......")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.body(transactionMono, AuthorizeRequest.class)
.retrieve()
.bodyToMono(AuthorizeResponse.class);
But I get:
org.springframework.web.reactive.function.client.WebClientResponseException$BadRequest: 400 Bad Request
at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:174)
at org.springframework.web.reactive.function.client.DefaultWebClient$DefaultResponseSpec.lambda$createResponseException$15(DefaultWebClient.java:512)
Do you know how I can solve this?
The server you sent the request to answered with http status code 400. That means, that your request seems to be malformed.
For example, you could have forgotten to include a parameter.
400 Bad Request could be of many reasons. To find out what exactly causing issue try printing the response body as below:
.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();
This helps you to find the right reason and then you can fix

Send POST request to server, Server's response is null, Angular - Spring-boot

I want to send a POST request with my angular client to my spring-boot server.
The server receive sucessfully the data and anwser with 200 Code and body with a string that contains "ok".
But the client receive null as anwser.
Angular code :
httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Accept': 'application/json',
'Access-Control-Allow-Methods': 'POST, GET'
})
};
this.http.post(this.URL, JSON.stringify(this.alert), this.httpOptions)
.subscribe((result: string) => {
if (result == null || result === 'error') {
//if fail;
} else {
//if success;
}
console.log('result :' + result);
})
Spring-boot code :
#CrossOrigin(origins = "http://localhost:9000")
#PostMapping(value = "/url")
public ResponseEntity objDataController(#RequestBody ObjData objData) {
HttpHeaders resHeader = new HttpHeaders();
resHeader.set("origin", "http://localhost:8080");
resHeader.set("Content-Type", "application/json");
resHeader.set("Accept", "application/json");
//some code
return ResponseEntity.ok()
.headers(resHeader)
.body("ok");
}
At console.log get :
result :null
Thanks
Had a similar problem and it helped specifying the response-type:
const headers = new HttpHeaders({'Content-Type': 'application/json'});
this.httpClient.delete(url, {headers: headers, responseType: 'text'});
It's the chrome console messing with you, when it comes to observables, it can't interpret the value correctly. I guess if you use the result inside the 'If success' block, it is bound to work
Ok, I fix my problem, I change this:
return ResponseEntity.ok()
.headers(resHeader)
.body("ok");
By:
Gson gson = new Gson();
return ResponseEntity.ok()
.headers(resHeader)
.body(gson.toJson("ok", String.class));
Thanks you for your anwsers !

How to override any HttpHeader in response of WebClient?

WebClient.builder().baseUrl("/").filter(contentTypeInterceptor()).build();
How can I modify the Content-Type of the received response (because I'm receiving a response from a webserver that emits the wrong content type. As I'm not in control of the external server, I'd like to correct the content type for further correct processing (eg with jackson library etc).
private ExchangeFilterFunction contentTypeInterceptor() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
org.springframework.web.reactive.function.client.ClientResponse.Headers headers = clientResponse.headers();
//TODO how to headers.setContentType("myval) or headers.set("Content-Type", "myval");
//headers.asHttpHeaders(); cannot be used as it is readonly
});
}
The question could be answered in general how to override any http header.
The root cause in my case is that I receive text/html, but the response body is actually a application/xml. And jackson rejects parsing that response due to:
org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'text/html' not supported for bodyType=MyResponse
I had similar issue and the accepted answer didn't work with me.
I done this instead, in order to override an invalid content-type that i was receiving.
/**
* webclient interceptor that overrides the response headers ...
* */
private ExchangeFilterFunction contentTypeInterceptor() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse ->
Mono.just(
ClientResponse
.from(clientResponse) //clientResponse is immutable, so,we create a clone. but from() only clones headers and status code
.headers(headers -> headers.remove(HttpHeaders.CONTENT_TYPE)) //override the content type
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE)
.body(clientResponse.body(BodyExtractors.toDataBuffers()) ) // copy the body as bytes with no processing
.build()));
}
Ahmed's response is technically correct. However, I believe that at the time of my posting this, that ClientResponse.from() is deprecated, and you should use the .mutate() method to create a new Builder.
private ExchangeFilterFunction contentTypeInterceptor() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse ->
Mono.just(clientResponse.mutate()
.headers(headers -> headers.remove(HttpHeaders.CONTENT_TYPE))
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build()));
}
maybe something like this?
private ExchangeFilterFunction contentTypeInterceptor() {
return ExchangeFilterFunction.ofRequestProcessor(clientRequest ->
Mono.just(ClientRequest.from(clientRequest)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE)
.build()));
}

Categories