Common processing/validation of response body in WebClient - spring-webflux

I have a REST-like service I POST requests to using WebFlux WebClient. The service returns response in a common JSON format, something like:
{
"status": "OK",
"data": []
}
Now for each WebClient invocation for each endpoint I would like to perform common validation to check if status == "OK". Do I need to invoke the validation separately for each endpoint, e.g.
myClient.post().uri("/myEndpoint1")
//..
.retrieve()
.bodyToMono(MyResponse.class)
.map(this::validateResponse)
//..
Or is there a way to add some common processing while creating the WebClient. I tried using a filter
this.myClient = WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(HttpClient.create().wiretap()))
.filter(ExchangeFilterFunction.ofResponseProcessor(this::validateMyResponseAsFilter))
.baseUrl(mybaseUrl)
.build();
where validateMyResponseAsFilter is
private Mono<ClientResponse> validateMyResponseAsFilter(ClientResponse resp) {
return resp.bodyToMono(MyResponse.class)
.flatMap(myResponse -> "OK".equals(myResponse.getStatus()) ? Mono.just(resp) : Mono.error(new RuntimeException()));
}
but this results in
org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'application/octet-stream' not supported for bodyType=my.package.MyResponse

Turned out that the service I connected to did not return Content-Type header. After fixing the service, the code works correctly.

Related

Wiremock proxying to different Url like Apache ProxyPass

I am trying to achieve something very simple:
Proxy a request to
mock.com/foo?paramA=valueA&paramB=valueB
to
backend.com/bar?paramA=valueA&paramB=valueB
And I would like to do this with a json config.
The problem is that proxyBaseUrl always takes the FULL Url from the input and appends it, so
{
"request": {
"method": "GET",
"urlPattern": "/foo/.*"
},
"response": {
"proxyBaseUrl": "http://backend.com/bar"
}
}
I get a request to
http://backend.com/bar/foo?paramA=valueA&paramB=valueB
which is obviously not what I need.
I need some way to grab part of the request url with a capture group, e.g.
"urlPattern": "/foo/(.*)"
and then a way to insert the captured group - and only that - into the target url path.
How can this be done, with a JSON config?
I have checked the wiremock documentation and browsed a dozen discussions but it's still not clear to me.
These two postings had the same question and did not receive any answers:
https://groups.google.com/g/wiremock-user/c/UPO2vw4Jmhw/m/Rx0e8FtZBQAJ
https://groups.google.com/g/wiremock-user/c/EVw1qK7k8Fo/m/5iYg1SQEBAAJ
So I am wondering if this is at all possible in wiremock? (in Apache it's a 2-liner)
As far as I know, proxying isn't configurable in this way. Looking at the documentation, WireMock will only proxy the same request via the proxyBaseUrl.
Unfortunately, it looks like your best bet is going to be to write a custom response transformer that does this redirect for you. I don't think the request/response objects given in the transformer class will handle redirection on their own, so you will probably need to set up your own client to forward the requests.
Psuedo code like:
class MyCustomTransformer extends ResponseTransformer {
public String getName() {
return "MyCustomTransformer";
}
#Override
public Response transform(Request request, Response response, FileSource files, Parameters parameters) {
Pattern pattern = Pattern.compile("/regex/url/to/match/");
Matcher matcher = pattern.matcher(request.getUrl());
if (matcher.matches()) {
// Code to modify request and send via your own client
// For the example, you've saved the returned response as `responseBody`
return Response.Builder.like(response).but().body(responseBody.toJSONString()).build();
} else {
return response
}
}
}

Reactive way to Proxy request in Spring webflux

I am having a requirement in which i have to forward a request to different endpoint by adding some extra headers(usually OAuth tokens).
i tried below working one to proxying request.
fun proxy(request: ServerRequest, url:String, customHeaders: HttpHeaders = HttpHeaders.EMPTY): Mono<ServerResponse> {
val modifiedHeaders = getHeadersWithoutOrigin(request, customHeaders)
var webClient = clientBuilder.method(request.method()!!)
.uri(url)
modifiedHeaders.forEach{
val list = it.value.iterator().asSequence().toList()
val ar:Array<String> = list.toTypedArray()
webClient.header(it.key, *ar)
}
return webClient
.body(request.bodyToMono(), DataBuffer::class.java).exchange()
.flatMap { clientResponse ->
ServerResponse.status(clientResponse.statusCode())
.headers{
it.addAll(clientResponse.headers().asHttpHeaders())
}
.body(clientResponse.bodyToMono(), DataBuffer::class.java)
}
}
Incoming requests always hit one proxy endpoint at my server with target url in header. At server, i read target url and add OAuth tokens and forward request to target URL. In this scenario, i do not want to parse response body. Send the response as it is down stream.
What is the reactive way to do it?

TcpClient Only Returns First Result in Spring WebFlux Controller

I have an HTTP service exposing a GET endpoint that connects to a simple echo server via TCP. The HTTP service is running on Netty.
#RestController
public class OurTcpClient {
private Connection connection;
#GetMapping("echo1")
public Mono<String> echo(#RequestParam("value") final String value) {
this.connection.outbound()
.sendString(Mono.just(String.format("%04d", value.length()) + value)) // prepend length
.then()
.subscribe();
return this.connection.inbound()
.receive()
.asString()
.next();
}
#PostConstruct
public void init() {
this.connection = TcpClient.create()
.host("localhost")
.port(10002)
.wiretap(true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
.option(ChannelOption.SO_KEEPALIVE, true)
.connectNow();
}
}
My expectation is that I can query the service at, for example, http://localhost:8081/echo1?value=hi, as many times as I like, and receive "hi" back in each response. This works for the first request. The second request hangs indefinitely. If I then cancel the second request and attempt another, I get the following error:
{
"timestamp": "2020-04-13T18:56:40.221+0000",
"path": "/echo1",
"status": 500,
"error": "Internal Server Error",
"message": "Only one connection receive subscriber allowed."
}
Any help would be greatly appreciated.
In the example you use next()
return this.connection.inbound()
.receive()
.asString()
.next();
According to the Flux#next javadoc Emit only the first item emitted by this Flux, into a new Mono., then the subscription will be canceled.
In the context of Reactor Netty when you use next, timeout, take etc. operators that cancel the subscription this means that the connection will be closed.

Capturing response in spring Webflux

Is there a way to capture response body in spring Webflux. I understand that its against the principles of reactive, however I would need to capture the body and return response. I am using ExchangeFilterFunction.
public Optional<ExchangeFilterFunction> buildEnricher() {
return Optional.of(ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
return clientResponse.bodyToMono(String.class)
.flatMap(body -> {
System.out.println(body);
return Mono.just(clientResponse);
});
}));
This will end up consuming the body and sending an empty client
response. Is there anyway I can send the body back too ?
You can choose to clone the client response.
ClientResponse responseClone = ClientResponse.from(clientResponse)
You can now drain the body from responseClone and return Mono.just(clientResponse)

Is there a helper method to extract origin host from request (to build a link)

I receive request via a router :
#Bean
public RouterFunction<ServerResponse> routerFunction() {
return nest(path(API_PATH), route(GET("/"), indexHandler::getIndex));
}
handle by a method :
public Mono<ServerResponse> getIndex(ServerRequest request) {
...
}
I need to extract the url use to request the service, I have different cases, sometimes request are direct to service, sometimes request go through proxy (and add X-Forwarded-Path,X-Forwarded-Path headers).
Is there a helper method, to extract this details from ServerRequest object ?