Webflux Router Immediately return 202 and continue processing - reactive-programming

I am new to Webflux and trying to build an application using spring webflux. I am exposing an http post end point which takes a json and immediately return 202 and start processing the request in background.
This is not working as expected, I am getting immediate 202 but Mono Processing is not happening
#Configuration
public class StreamingRouter {
#Bean
RouterFunction<ServerResponse> streamProcessor(StreamingHandler handler) {
RouterFunction<ServerResponse> streamRouter = RouterFunctions.route(
RequestPredicates.POST("/stream").and(RequestPredicates.accept(MediaType.APPLICATION_JSON)),
handler::process);
return streamRouter;
}
}
#Log4j2
#Component
public class StreamingHandler {
Mono<ServerResponse> process(ServerRequest serverRequest){
serverRequest.bodyToMono(Content.class)
.log
.subscribe(log::info);
return ServerResponse.accepted().build();
}
}
2021-06-27 09:21:44.576 INFO 14076 --- [ctor-http-nio-3] reactor.Mono.OnErrorResume.1 : onSubscribe(FluxOnErrorResume.ResumeSubscriber)
2021-06-27 09:21:44.577 INFO 14076 --- [ctor-http-nio-3] reactor.Mono.OnErrorResume.1 : request(unbounded)
onNext is not happening after calling the subscribe.
If I change to return in the same statement it executes but 202 is happening in same thread after the processing.
return serverRequest.bodyToMono(Content.class)
.log()
.map(content -> doSynchronousAPICall)
.then(ServerResponse.accepted().build());
I also tried subscribeOn(Schedulers.boundedElastic()) but not luck.
I think I am missing something here. Please help
Update
As suggested I tried doOnSuccess but same result as map.
return serverRequest.bodyToMono(Content.class)
.log()
.doOnSuccess(content -> doSynchronousAPICall)
.then(ServerResponse.accepted().build());
The problem I am having returning inside the same statement as its taking same time as the blocking call[doSynchronousAPICall] for sending a response as 202/Accepted. ie say 3 secs for responding 202. Rather what I am looking for is return immediately and continue with processing. Client is only wanted to know request reached.

Related

Vertx - threads are stuck while sending response back to client

I'm using vertx-4.2.6 to build a proxy service which takes requests from clients (for ex: browser, standalone apps etc), invoke a single thirdparty server, gets the response and send the same response back to client who initiated the request.
In this process, I'm using shared Webclient across multiple requests, i'm getting response from thirdparty quickly (mostly in milli seconds) but sometimes the response is not returned back to client and stucks at ctx.end(response).
Whenever i restart my proxy server, it serves requests sometimes without any issues but time goes on, lets say by EOD, for new requests client seeing 503 error -service unavailable I'm using one MainVerticle with 10 instances. I'm not using any worker threads.
Below is the pseudo code:
MainVerticle
DeploymentOptions depOptions = new DeploymentOptions();
depOptions.setConfig(config);
depOptions.setInstances(10);
vertx.deployVerticle(MainVerticle.class.getName(), depOptions);
.....
router.route("/api/v1/*")
.handler(new HttpRequestHandler(vertx));
HttpRequestHandler
public class HttpRequestHandler implements Handler<RoutingContext> {
private final Logger LOGGER = LogManager.getLogger( HttpRequestHandler.class );
private WebClient webClient;
public HttpRequestHandler(Vertx vertx) {
super(vertx);
this.webClient=createWebClient(vertx);
}
private WebClient createWebClient(Vertx vertx) {
WebClientOptions options=new WebClientOptions();
options.setConnectTimeout(30000);
WebClient webClient = WebClient.create(vertx,options);
return webClient;
}
#Override
public void handle(RoutingContext ctx) {
ctx.request().bodyHandler(bh -> {
ctx.request().headers().remove("Host");
StopWatch sw=StopWatch.createStarted();
LOGGER.info("invoking CL end point with the given request details...");
/*
* Invoking actual target
*/
webClient.request(ctx.request().method(),target_port,target_host, "someURL")
.timeout(5000)
.sendBuffer(bh)
.onSuccess(clResponse -> {
LOGGER.info("CL response statuscode: {}, headers: {}",clResponse.statusCode(),clResponse.headers());
LOGGER.trace("response body from CL: {}",clResponse.body());
sw.stop();
LOGGER.info("Timetaken: {}ms",sw.getTime()); //prints in milliseconds
LOGGER.info("sending response back to client...."); //stuck here
/*
* prepare the final response and return to client..
*/
ctx.response().setStatusCode(clResponse.statusCode());
ctx.response().headers().addAll(clResponse.headers());
if(clResponse.body()!=null) {
ctx.response().end(clResponse.body());
}else {
ctx.response().end();
}
LOGGER.info("response SENT back to client...!!"); //not getting this log for certain requests and gives 503 - service unavailable to clients after 5 seconds..
}).onFailure(err -> {
LOGGER.error("Failed while invoking CL server:",err);
sw.stop();
if(err.getCause() instanceof java.net.ConnectException) {
connectionRefused(ctx);
}else {
invalidResponse(ctx);
}
});
});
Im suspecting issue might be due to shared webclient. But i'm not sure. I'm new to Vertx and i'm not getting any clue what's going wrong. Please suggest if there are any options to be set on WebClientOptions to avoid this issue.

How to return the processing result of a signal?

Especially if the signal processing needs to invoke an/some activities, how can I achieve that?
I tried to return data or exception but it doesn't work.
Data cannot be returned from signal method. Throwing exception will block workflow execution.
Common mistakes
It's wrong to return data in a signal method, or throw an exception -- because signal method is meant to be Asynchronous. The processing must be like Kafka processing messages and you can't return the result via the method returning.
So below code will NOT work:
public class SampleWorkflow{
public Result mySignalMethod(SignalRequest req){
Result result = activityStub.execute(req)
if(...){
throw new RuntimeException(...)
}
return result
}
}
What should you do
What you must do:
Make sure signal don't return anything
Use a query method to return the results
In signal method processing, store the results into workflow state so that query can return the states
A bonus if you also use the design pattern to store signal request into a queue, and let workflow method to process the signal. This will give you some benefits
Guarantee FIFO ordering of signal processing
Make sure reset workflow won't run into issues -- after reset, signals will be preserved and moved to earlier position of the workflow history. Sometimes workflow are not initialized to replay the signals.
Also make exception handling easier
See this design pattern in sample code: Cadence Java sample/Temporal java sample
If we applied all above, the sample code should be like below :
public class SampleWorkflow{
private Queue<SignalRequest> queue = new Queue<>();
private Response<Result> lastSignalResponse;
public void myWorkflowMethod(){
Async.procedure(
() -> {
while (true) {
Workflow.await(() -> !queue.isEmpty());
final SignalRequest req =
queue.poll();
// alternatively, you can use async to start an activity:
try{
Result result = activityStub.execute(req);
}catch (ActivityException e){
lastSignalResponse = new Response( e );
}
if(...){
lastSignalResponse = new Response( new RuntimeException(...) );
}else{
lastSignalResponse = new Response( result);
}
}
});
...
}
public Response myQueryMethod(){
return lastSignalResponse;
}
public Result mySignalMethod(SignalRequest req){
queue.add(req)
}
}
And in the application code, you should signal and then query the workflow to get the result:
workflowStub.mySignalMethod(req)
Response response = workflowStub.myQueryMethod()
Follow this sample-Cadence / sample-Temporal if you want to use aysnc activity
Why
Signal is executed via Workflow decision task(Workflow task in Temporal). A decision task cannot return result. In current design, there is no mechanism to let a decision task return result to application code.
Throw exception in workflow code will either block the decision task or fail the workflow).
Query method is designed to return result. -- However, query cannot schedule activity or modify workflow states.
It's a missing part to let app code to make a synchronous API call to update and return data. It needs a complicated design: https://github.com/temporalio/proposals/pull/53

Vertx event bus slow consuming issue

We have a non clustered vertx application, and we use the event bus to internally communicate between verticles.
Verticle A consumes from the bus, performs a HTTP request, and sends the response back through the bus.
Verticle B just request to perform that HTTP request.
The problem appears when a "high" request volume is performed by Verticle B. Then, the consumer starts receiving the events slower and slower (presumably because they are getting queued in the event bus). For 8 requests/second the bus takes up to 3-4 seconds to consume the event. When the requests/second are elevated, it can take more than 30 seconds to consume it, so the bus timeout is triggered.
The thing is, Verticle A is really fast performing the HTTP operation (~200ms) so I don't really understand why the requests get stuck in the bus.
We've tried many solutions but none ot then worked:
Deploy multiple instances of Verticle A as workers
Use vertx.executeBlocking() to perform the HTTP request
The only thing that worked was commenting the HTTP request and returning a mock object through the bus. But again, the HTTP request doesn't take more than 200ms, so it shouldn't be blocking the bus.
Additional information: We use an autogenerated rest client that uses Retrofit + OkHttpClient. Due to company policy, we cannot use Vertx WebClient, so I didn't try this solution.
EXAMPLE
This is a really simplified version of our code so you can check if I'm missing something.
VERTICLE A
// Instantiated in Verticle A
public class EmailSender {
private final Vertx vertx;
private final EmailApiClient emailApiClient;
public EmailSender(Vertx vertx) {
this.vertx = vertx;
emailApiClient = ClientFactory.createEmailApiClient();
}
public void start() {
vertx.eventBus().consumer("sendEmail", this::sendEmail);
}
public void sendEmail(Message<EmailRequest> message) {
EmailRequest emailRequest = message.body();
emailApiClient.sendEmail(emailRequest).subscribe(
response -> {
if (response.code() == 200) {
EmailResponse emailResponse = response.body();
message.reply(emailResponse);
} else {
message.fail(500, "Error sending email");
}
});
}
}
VERTICLE B
// Instantiated in Verticle B
public class EmailCommunications {
private final Vertx vertx;
public EmailCommunications(Vertx vertx) {
this.vertx = vertx;
}
public Single<EmailResponse> sendEmail(EmailRequest emailRequest) {
SingleSubject<EmailResponse> emailSent = SingleSubject.create();
vertx.eventBus().request(
"sendEmail",
emailRequest,
busResult -> {
if (busResult.succeded()) {
emailSent.onSuccess(busResult.result().body())
} else {
emailSent.onError(busResult.cause())
}
}
);
return emailSent;
}
}
We fixed the issue changing our OkHttpClient configuration so HTTP requests won't get stuck
default void configureOkHttpClient(OkHttpClient.Builder okHttpClientBuilder) {
ConnectionPool connectionPool = new ConnectionPool(40, 5, TimeUnit.MINUTES);
Dispatcher dispatcher = new Dispatcher();
dispatcher.setMaxRequestsPerHost(200);
dispatcher.setMaxRequests(200);
okHttpClientBuilder
.readTimeout(60, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.connectionPool(connectionPool)
.dispatcher(dispatcher);
}

Why does my Spring WebFlux controller return data on first request only?

I am working on a web application where the user's connection times out after a specific time (say 20 seconds). For long running requests I have to return a default message ("your request is under process") and then send an email to the user with the actual result.
I couldn't do this with spring web because I didn't know how to specify a timeout in the controller (with customized messages per request) and at the same time let other requests come through and be processed too. That's why I used spring web-flux which has a timeout operator for both Mono and Flux types.
To make the requested process run in a different thread, I have used Sinks. One to receive requests and one to publish the results. My problem is that the response sink can only return one result and subsequent calls to the URL returns an empty response. For example the first call to /reactive/getUser/123456789 returns the user object but subsequent calls return empty.
I'm not sure if the problem is with the Sink I have used or with how I am getting data from it. In the sample code I have used responseSink.asFlux().next() but I have also tried .single(), .toMono(), .take(1). to no avail. I get the same result.
#RequestMapping("/reactive")
#RestController
class SampleController #Autowired constructor(private val externalService: ExternalService) {
private val requestSink = Sinks.many().multicast().onBackpressureBuffer<String>()
private val responseSink = Sinks.many().multicast().onBackpressureBuffer<AppUser>()
init {
requestSink.asFlux()
.map { phoneNumber -> externalService.findByIdOrNull(phoneNumber) }
.doOnNext {
if (it != null) {
responseSink.tryEmitNext(it)
} else {
responseSink.tryEmitError(Throwable("didn't find a value for that phone number"))
}
}
.subscribe()
}
#GetMapping("/getUser/{phoneNumber}")
fun getUser(#PathVariable phoneNumber: String): Mono<String> {
requestSink.tryEmitNext(phoneNumber)
return responseSink.asFlux()
.next()
.map { it.toString() }
.timeout(Duration.ofSeconds(20), Mono.just("processing your request"))
}
}

In Spring WebClient used with block(), get body on error

I am using WebClient from Spring WebFlux to communicate with a REST API backend from a Spring client.
When this REST API backend throws an exception, it answers with a specific format (ErrorDTO) that I would like to collect from my client.
What I have tried to do is to make my client throw a GestionUtilisateurErrorException(ErreurDTO) containing this body once the server answers with a 5xx HTTP status code.
I have tried several options :
I/ onStatus
#Autowired
WebClient gestionUtilisateursRestClient;
gestionUtilisateursRestClient
.post()
.uri(profilUri)
.body(Mono.just(utilisateur), UtilisateurDTO.class)
.retrieve()
.onStatus(HttpStatus::is5xxServerError,
response -> {
ErreurDTO erreur = response.bodyToMono(ErreurDTO.class).block();
return Mono.error(new GestionUtilisateursErrorException(erreur));
}
)
.bodyToMono(Void.class)
.timeout(Duration.ofMillis(5000))
.block();
This method doesn't work because webclient doesn't allow me to call the block method in the onStatus. I am only able to get a Mono object and I can't go further from here.
It seems like "onStatus" method can't be used in a WebClient blocking method, which means I can throw a custom Exception, but I can't populate it with the data from the response body.
II/ ExchangeFilterFunction
#Bean
WebClient gestionUtilisateursRestClient() {
return WebClient.builder()
.baseUrl(gestionUtilisateursApiUrl)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.filter(ExchangeFilterFunction.ofResponseProcessor(this::gestionUtilisateursExceptionFilter))
.build();
}
private Mono<ClientResponse> gestionUtilisateursExceptionFilter(ClientResponse clientResponse) {
if(clientResponse.statusCode().isError()){
return clientResponse.bodyToMono(ErreurDTO.class)
.flatMap(erreurDto -> Mono.error(new GestionUtilisateursErrorException(
erreurDto
)));
}
return Mono.just(clientResponse);
}
This method works but throw a reactor.core.Exceptions$ReactiveException that I am struggling to catch properly (reactor.core.Exceptions is not catchable, and ReactiveException is private).
This Exception contains in its Cause the exception I need to catch (GestionUtilisateurErrorException) but I need a way to catch it properly.
I also tried to use "onErrorMap" and "onErrorResume" methods but none of them worked the way I needed.
Edit 1 :
I am now using the following workaround even if I feel it's a dirty way to do what I need :
gestionUtilisateursRestClient
.post()
.uri(profilUri)
.body(Mono.just(utilisateur), UtilisateurDTO.class)
.retrieve()
.onStatus(h -> h.is5xxServerError(),
response -> {
return response.bodyToMono(ErreurDTO.class).handle((erreur, handler) -> {
LOGGER.error(erreur.getMessage());
handler.error(new GestionUtilisateursErrorException(erreur));
});
}
)
.bodyToMono(String.class)
.timeout(Duration.ofMillis(5000))
.block();
}
catch(Exception e) {
LOGGER.debug("Erreur lors de l'appel vers l'API GestionUtilisateur (...)");
if(ExceptionUtils.getRootCause(e) instanceof GestionUtilisateursErrorException) {
throw((GestionUtilisateursErrorException) e.getCause());
}
else {
throw e;
}
}
Here, it throws the expected GestionUtilisateursErrorException that I can handle synchronously.
I might implement this in a global handler to avoid writing this code around each call to my API.
Thank you.
Kevin
I've encountered a similar case for accessing the response body that might be of use to you using the Mono.handle() method (see https://projectreactor.io/docs/core/release/api/index.html?reactor/core/publisher/Mono.html).
Here handler is a SynchronousSink (see https://projectreactor.io/docs/core/release/api/reactor/core/publisher/SynchronousSink.html) and can call at most next(T) one time, and either complete() or error().
In this case, I call 'handler.error()' with a new GestionUtilisateursErrorException constructed with the 'erreur'.
.onStatus(h -> h.is5xxServerError(),
response -> {
return response.bodyToMono(ErreurDTO.class).handle((erreur, handler) -> {
// Do something with erreur e.g.
log.error(erreur.getErrorMessage());
// Call handler.next() and either handler.error() or handler.complete()
handler.error(new GestionUtilisateursErrorException(erreur));
});
}
)