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
Related
Let's see a test, which is using MockServer (org.mock-server:mockserver-netty:5.10.0) for mocking responses.
It is expected that the response body will be equal to string "something".
Nevertheless, this test fails, because the response body is an empty string.
#Test
void test1() throws Exception {
var server = ClientAndServer.startClientAndServer(9001);
server
.when(
request().withMethod("POST").withPath("/checks/"),
exactly(1)
)
.respond(
response()
.withBody("\"something\"")
.withStatusCode(205)
.withHeader("Content-Type", "application/json")
);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://localhost:9001/checks/"))
.POST(BodyPublishers.noBody())
.build();
HttpResponse<String> response =
HttpClient.newHttpClient().send(request, BodyHandlers.ofString());
assertEquals(205, response.statusCode());
assertEquals("something", response.body()); // fails
}
How to make the response body be equal to the string provided in response().withBody(...)?
The problem is on the client side. It drops content.
Why!?
Because, HTTP 205 is RESET_CONTENT.
This status was chosen accidentally for test as "somenthing different from HTTP 200", and unfortunately caused this behaviour.
Looks like it is very popular "accidental" mistake (i.e. here), although it is strictly in accordance with the HTTP spec.
I am trying to log the error response that is coming from web client using onStatus or exchange.I am able to throw exception based on error code, but unable to log response.In Success scenario I can successfully log message.
Below is my Code
I tried with both .retreive method and also .exchange method.But instead of printing response coming from client following code is printing
Response Printing currently;
checkpoint("Body from POST *****Printing Backend Url ******[DefaultClientResponse]")
Expected Response:
{errorCd:404, errorMsg:"Not Found",errorDetails"Person Not Found for Given Request"}
The Above Message is being returned from client, when I use postman or soapui
Using .retrieve() Method
webClient
.post()
.uri(url)
.header(ACCEPT, APPLICATION_JSON_VALUE)
.bodyValue(request)
.retrieve()
.onStatus({ httpStatus -> HttpStatus.NOT_FOUND == httpStatus }, {
logger.info { "Client Response :${it.bodyToMono(String::class.java)}" }
;Mono.error(MyCustomException))
})
.bodyToMono(Person::class.java)
Using .exchange() Method
webClient
.post()
.uri(url)
.header(ACCEPT, APPLICATION_JSON_VALUE)
.body(BodyInserters.fromObject(request))
.exchange()
.flatMap {
clientResponse ->
if (clientResponse.statusCode().is4xxClientError) {
clientResponse.body { clientHttpResponse, _ -> clientHttpResponse.body}
logger.info { "Error Response:"+clientResponse.bodyToMono(String::class.java)}; Mono.error(MyCustomException()))
} else clientResponse.bodyToMono(Person::class.java)
}
AnyHelp Would be Appreciated.
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()
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()));
}
I have a Spring app acting as a passthrough from one app to another, making a http request and returning a result to the caller using WebClient. If the client http call returns a 500 internal server error I want to be able to catch this error and update the object that gets returned rather than re-throwing the error or blowing up the app.
This is my Client:
final TcpClient tcpClient = TcpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectionTimeout)
.doOnConnected(connection -> connection.addHandlerLast(new ReadTimeoutHandler(readTimeout))
.addHandlerLast(new WriteTimeoutHandler(writeTimeout)));
this.webClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient)))
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.filter(logRequest())
.filter(logResponse())
.filter(errorHandler())
.build();
And this is my error handler. I've commented where I want to modify the result, where ResponseDto is the custom object that is returned from the client call happy path
public static ExchangeFilterFunction errorHandler(){
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
ResponseDto resp = new ResponseDto();
if(nonNull(clientResponse.statusCode()) && clientResponse.statusCode().is5xxServerError()){
//this is where I want to modify the response
resp.setError("This is the error");
}
//not necessarily the correct return type here
return Mono.just(clientResponse);
});
}
How can I achieve this? I can't find any tutorials or any information in the docs to help explain it.
Disclaimer, I'm new to webflux. We're only starting to look at reactive programming