Capturing response in spring Webflux - 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)

Related

Spring Cloud Gateway Custom Filter : WebClient.create().post() causes hanging when testing

So I've created a custom filter that, when accessed, will create a webflux client and post to a predetermined url. This seems to work fine when running, but when testing this code the test is hanging (until I cancel the test). So I feel there is a possible memory leak on top of not being able to complete the test to make sure this route is working properly. If I switch the WebClient method to get() then a resulting test of the filter works fine. Something with a post() I am not sure what is missing.
#Component
class ProxyGatewayFilterFactory: AbstractGatewayFilterFactory<ProxyGatewayFilterFactory.Params>(Params::class.java) {
override fun apply(params: Params): GatewayFilter {
return OrderedGatewayFilter(
GatewayFilter { exchange, chain ->
exchange.request.mutate().header("test","test1").build()
WebClient.create().post()
.uri(params.proxyBasePath)
.body(BodyInserters.fromDataBuffers(exchange.request.body))
.headers { it.addAll(exchange.request.headers) }
.exchange()
.flatMap {
println("the POST statusCode is "+it.statusCode())
Mono.just(it.statusCode().is2xxSuccessful)
}
.map {
exchange.request.mutate().header("test", "test2").build()
println("exchange request uri is " + exchange.request.uri)
println("exchange response statusCode is "+ exchange.response.statusCode)
exchange
}
.flatMap(chain::filter)
}, params.order)
}
Taken from the documentation, if using exchange you have an obligation to consume the body.
Unlike retrieve(), when using exchange(), it is the responsibility of the application to consume any response content regardless of the scenario (success, error, unexpected data, etc). Not doing so can cause a memory leak. The Javadoc for ClientResponse lists all the available options for consuming the body. Generally prefer using retrieve() unless you have a good reason for using exchange() which does allow to check the response status and headers before deciding how to or if to consume the response.
Spring framework 5.2.9 Webclient
This api has been changed in the latest version of the spring framework 5.3.0 now spring will force you to consume the body, because developers didn't actually read the docs.

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?

Common processing/validation of response body in WebClient

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.

Flutter WCF Service Calling

i want to call our wcf services from flutter with dart without configuration of any addition settings. It is possible to access directly to wcf service method? I didn't find any approach to this.There is no package to implement soap services or wcf services in flutter pub web page. I am waiting suggestions or approaches.
I have never used Flutter personaly, but I am going to borrow a few answers from SO to answer your question,
According to How to make HTTP POST request with url encoded body in flutter? , flutter should have the ability to create HttpPosts/gets
Future<HttpClientResponse> foo() async {
Map<String, dynamic> jsonMap = {
'homeTeam': {'team': 'Team A'},
'awayTeam': {'team': 'Team B'},
};
String jsonString = json.encode(jsonMap); // encode map to json
String paramName = 'param'; // give the post param a name
String formBody = paramName + '=' + Uri.encodeQueryComponent(jsonString);
List<int> bodyBytes = utf8.encode(formBody); // utf8 encode
HttpClientRequest request =
await _httpClient.post(_host, _port, '/a/b/c');
// it's polite to send the body length to the server
request.headers.set('Content-Length', bodyBytes.length.toString());
// todo add other headers here
request.add(bodyBytes);
return await request.close();
}
With the above Code taken from the linked post, now you can modify your WCF service a bit to now use REST instead of only SOAP,
then just send a simple HTTP Request to the service and you will get your intended response
You can check https://www.codeproject.com/Articles/571813/A-Beginners-Tutorial-on-Creating-WCF-REST-Services on how to get started with WCF Rest

How to read the request body with spring webflux

I'm using Spring 5, Netty and Spring webflux to develop and API Gateway. Sometime I want that the request should be stopped by the gateway but I also want to read the body of the request to log it for example and return an error to the client.
I try to do this in a WebFilter by subscribing to the body.
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if (enabled) {
logger.debug("gateway is enabled. The Request is routed.");
return chain.filter(exchange);
} else {
logger.debug("gateway is disabled. A 404 error is returned.");
exchange.getRequest().getBody().subscribe();
exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND);
return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().allocateBuffer(0)));
}
}
When I do this it works when the content of the body is small. But when I have a large boby, only the first element of the flux is read so I can't have the entire body. Any idea how to do this ?
1.Add "readBody()" to the post route:
builder.routes()
.route("get_route", r -> r.path("/**")
.and().method("GET")
.filters(f -> f.filter(myFilter))
.uri(myUrl))
.route("post_route", r -> r.path("/**")
.and().method("POST")
.and().readBody(String.class, requestBody -> {return true;})
.filters(f -> f.filter(myFilter))
.uri(myUrl))
2.Then you can get the body string in your filter:
String body = exchange.getAttribute("cachedRequestBodyObject");
Advantages:
No blocking.
No need to refill the body for further process.
Works with Spring Boot 2.0.6.RELEASE + Sring Cloud Finchley.SR2 + Spring Cloud Gateway.
The problem here is that you are subscribing manually within the filter, which means you're disconnecting the reading of the request from the rest of the pipeline. Calling subscribe() gives you a Disposable that helps you manage the underlying Subscription.
So you need to turn connect the whole process as a single pipeline, a bit like:
Flux<DataBuffer> requestBody = exchange.getRequest().getBody();
// decode the request body as a Mono or a Flux
Mono<String> decodedBody = decodeBody(requestBody);
exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND);
return decodedBody.doOnNext(s -> logger.info(s))
.then(exchange.getResponse().setComplete());
Note that decoding the whole request body as a Mono means your gateway will have to buffer the whole request body in memory.
DataBuffer is, on purpose, a low level type. If you'd like to decode it (i.e. implement the sample decodeBodymethod) as a String, you can use one of the various Decoder implementations in Spring, like StringDecoder.
Now because this is a rather large and complex space, you can use and/or take a look at Spring Cloud Gateway, which does just that and way more.