How to implement sync communication when Server site handle request asynchronously?
I mean how client site handle this communication model?
Similar communication handled in XMPP messaging protocol when sending IQ message.
IQ message is handled at backend site asynchronously, in other words when request comes into server it is quesued to be processed , after that response is send back from available channels.
Client site waits for response as if communication was like http-rest call
You might look into DeferredResult or the new java 8 CompletableFuture.
The code would look something like this in Spring:
#RequestMapping(path = "/test", method = RequestMethod.GET)
public CompletableFuture<String> getAsyncTest() {
return CompletableFuture.supplyAsync(this::processReq);
}
private String processReq() {
log.info("Dummy work");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "test";
}
Here is a good read on this topic: https://nickebbitt.github.io/blog/2017/03/22/async-web-service-using-completable-future
[Client side example]
You could use RXJS with Observables:
getTest().subscribe((response) => {
// Do something on success
}, (error) => {
console.error(error);
});
getTest(): Observable<any> {
return this.http.get<>('/test');
}
Related
The overview of the project is this:
Upload file to Springboot server via an endpoint
The endpoint sends an OK response when the file is received, but continues to process the file in the background, running tests on the file.
So, since the endpoint of the controller has already returned a response, how can I send info from the backend to the frontend outside of using the Controller.
Here is what is running after the Controller returns the response:
CompletableFuture.runAsync(() -> {
int count = 0;
boolean stillProcessing = true;
while (stillProcessing) {
stillProcessing = !test.isTestComplete();
if (test.getNumberOfInstancesComplete() > count) {
count = test.getNumberOfInstancesComplete();
log.info("{}/{} instances completed so far", count, test.getInstances().size());
}
}
});
The log.info line is what I need to return to the frontend React side of things.
The end goal is to basically have a loading bar shown to users using the values printed in log.info().
You can use websockets to notify frontend without a controller. Here is the example code to send a message to the client from backend using STOMP at any time.
#Component
public class PushMessage {
#Autowired
SimpMessagingTemplate simpMessagingTemplate;
public <T> void invokeWebSocketEndpoint(String endpoint, T payload) {
this.simpMessagingTemplate.convertAndSend(endpoint, payload);
}
}
For more info on STOMP websockets, check out this link
https://spring.io/guides/gs/messaging-stomp-websocket/
If you do not want bi-directional communication between the client and the server and just want to push messages to client from the server you can also make use of Server sent events. Here's a simple example.
#GetMapping(value = "/test")
public SseEmitter test() {
SseEmitter emitter = new SseEmitter();
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(() -> {
try {
Process p = Runtime.getRuntime().exec("ping -c 10 www.google.com");
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
String line;
while ((line = bufferedReader.readLine()) != null) {
emitter.send(line);
}
emitter.complete();
}
} catch (IOException e) {
emitter.completeWithError(e);
e.printStackTrace();
}
});
executorService.shutdown();
return emitter;
}
For more information on Server sent events see this
https://www.baeldung.com/spring-server-sent-events
You can consume server sent events from your frontend using the EventSource API
https://developer.mozilla.org/en-US/docs/Web/API/EventSource
Currently I have an application with Spring Integration DSL that has AMQP inbound gateway with different service activators, each service activator has kind of logic to decide, transform and call external web services (currently with CXF), but all this logic is in code without Spring Integration components.
These service activators are monitored, in the output channel that returns data from this application is an AMQP adapter that sends headers to a queue (after that, all headers are processed and saved in a database for future analysis). This works well, these service activators even have elapsed time in headers.
Now, the problem is, that I need to monitor external web service calls, like elapsed time in each operation, which service endpoint and operation was called, if an error occurred.
I've been thinking that logic code in each service activator should be converted into a Spring Integration flow, in each service activator, would call a new gateway with the name of the operation of the web service in a header, and monitoring every flow as currently I had been doing.
So, I'm not sure if this manual approach is the better approach, so I wonder if there is a way to get the name of the service operation with some kind of interceptor or something similar with CXF or Spring WS to avoid setting the name of the operation in headers in a manual way? What would be your recommendation?
To have more context here is the Spring Integration configuration:
#Bean
public IntegrationFlow inboundFlow() {
return IntegrationFlows.from(Amqp.inboundGateway(simpleMessageListenerContainer())
.mappedReplyHeaders(AMQPConstants.AMQP_CUSTOM_HEADER_FIELD_NAME_MATCH_PATTERN)
.mappedRequestHeaders(AMQPConstants.AMQP_CUSTOM_HEADER_FIELD_NAME_MATCH_PATTERN)
.errorChannel(gatewayErrorChannel())
.requestChannel(gatewayRequestChannel())
.replyChannel(gatewayResponseChannel())
)
.enrichHeaders(new Consumer<HeaderEnricherSpec>() {
#Override
public void accept(HeaderEnricherSpec t) {
t.headerExpression(AMQPConstants.START_TIMESTAMP, "T(java.lang.System).currentTimeMillis()");
}
})
.transform(getCustomFromJsonTransformer())
.route(new HeaderValueRouter(AMQPConstants.OPERATION_ROUTING_KEY))
.get();
}
#Bean
public MessageChannel gatewayRequestChannel() {
return MessageChannels.publishSubscribe().get();
}
#Bean
public MessageChannel gatewayResponseChannel() {
return MessageChannels.publishSubscribe().get();
}
private IntegrationFlow loggerOutboundFlowTemplate(MessageChannel fromMessageChannel) {
return IntegrationFlows.from(fromMessageChannel)
.handle(Amqp.outboundAdapter(new RabbitTemplate(getConnectionFactory()))
.exchangeName(LOGGER_EXCHANGE_NAME)
.routingKey(LOGGER_EXCHANGE_ROUTING_KEY)
.mappedRequestHeaders("*"))
.get();
}
And here is a typical service activator, as you can see, all this logic could be an integration flow:
#ServiceActivator(inputChannel="myServiceActivator", outputChannel = ConfigurationBase.MAP_RESPONSE_CHANNEL_NAME)
public Message<Map<String, Object>> myServiceActivator(Map<String, Object> input, #Header(AMQPConstants.SESSION) UserSession session) throws MyException {
Message<Map<String, Object>> result = null;
Map<String, Object> mapReturn = null;
ExternalService port = serviceConnection.getExternalService();
try {
if (input.containsKey(MappingConstants.TYPE)) {
Request request = transformer
.transformRequest(input, session);
Response response = port
.getSomething(request);
utils.processBackendCommonErrors(response.getCode(), response.getResponse());
mapReturn = transformer.convertToMap(response);
} else {
Request request = transformer
.transformRequest(input, session);
Response response = port
.getSomethingElse(request);
utils.processBackendCommonErrors(response.getCode(),
response.getResponse());
mapReturn = transformer.convertToMap(response);
}
} catch (RuntimeException e) {
String message = "unexcepted exception from the back-end";
logger.warn(message, e);
throw MyException.generateTechnicalException(message, null, e);
}
result = MessageBuilder.withPayload(mapReturn)
.build();
return result;
}
So far so good. Or I don't understand the problem, or you are not clear where it is.
Anyway you always can proxy any Spring Service with the AOP, since it looks like you are pointing to the code:
Response response = port
.getSomething(request);
When this (or similar) method is called, some MethodInterceptor can perform desired tracing logic and send result to some MessageChannel for further analysis or anything else to do:
public Object invoke(MethodInvocation invocation) throws Throwable {
// Extract required operation name and start_date from the MethodInvocation
Object result = invocation.proceed();
// Extract required data from the response
// Build message and send to the channel
return result;
}
I have a controller which receives a request to check status of a game, I want the whole thing to be done asynchronously. The controller seems to be working asynchronous, however postman hangs when I do the post request.
Here is my controller:
#RequestMapping(value = "/status", method = RequestMethod.POST, consumes="application/json")
private Callable<Byte> getStatus(#RequestBody final Matrix board){
System.out.println("Entering controller");
Callable<Byte> asyncResult = new Callable<Byte>() {
#Override
public Byte call() throws Exception {
return status.checkWinner(board);
}
};
System.out.println("Leaving controller");
return asyncResult ;
}
and here is my method in Status class:
public Byte checkWinner(Matrix board) {
System.out.println("start Slow work");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("finish Slow work");
return 0;
}
the program output shows this:
Entering controller
Leaving controller
start Slow work
finish Slow work
So i tried to put #Async on top of checkWinner method, postman shows 200 status ok, without showing the result 0 later on. And without the #Async annotation, postman (webpage) freezes for 5 seconds and the result 0 is shown.
This is an expected behaviour. The Callable returned is executed in a separate thread pool and the request thread is free for another processing. However, the connection to that specific client will be open and the client will be responded to only after callable is complete. Client will wait for the response or will timeout if the response is not obtained in the expected time.
The above model is only for server side load handling not client side. So this model will not work for you.
If you want the client to return, then you should return a jobid as the return value. Provide that client with a poll url to check for response. When the job is complete, return the response.
I am trying to understand the Bayeux protocol. I haven't found a web-resource explaining how the bayeux client will technically work, in detail.
From this resource,
The Bayeux protocol requires that the first message a new client sends
be a handshake message (a message sent on /meta/handshake channel).
The client processes the handshake reply, and if it is successful,
starts – under the covers – a heartbeat mechanism with the server, by
exchanging connect messages (a message sent on a /meta/connect
channel).
The details of this heartbeat mechanism depend on the client
transport used, but can be seen as the client sending a connect
message and expecting a reply after some time.
Connect messages continue to flow between client and server until
either side decides to disconnect by sending a disconnect message (a
message sent on the /meta/disconnect channel).
I have written in Java methods to first do a handshake, then subscribe to a particular channel. I made use of the Apache HttpClient library to do the HTTP POST requests.
Now comes the part of connect.
My understanding is that, I need to keep a request open to the bayeux server and whenever I receive a response, make another request.
I have the written the below code. Is my understanding correct and does this bayeux client exhibit the correct connect functionality? (please ignore the missing disconnect, unsubscribe methods)
Also, I have tested the code against a bayeux server and it works correctly.
/* clientId - Unique clientId returned by bayeux server during handshake
responseHandler - see interface below */
private static void connect(String clientId, ResponseHandler responseHandler)
throws ClientProtocolException, UnsupportedEncodingException, IOException {
String message = "[{\"channel\":\"/meta/connect\","
+ "\"clientId\":\"" + clientId + "\"}]";
CloseableHttpClient httpClient = HttpClients.createDefault();
Thread t = new Thread(new Runnable() {
#Override
public void run() {
while (!doDisconnect) {
try {
CloseableHttpResponse response = HttpPostHelper.postToURL(ConfigurationMock.urlRealTime,
message, httpClient, ConfigurationMock.getAuthorizationHeader());
responseHandler.handleResponse(response);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
httpClient.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
t.start();
}
/*Simple interface to define what happens with the response when it arrives*/
private interface ResponseHandler {
void handleResponse(CloseableHttpResponse httpResponse);
}
public static void main(String[] args) throws Exception{
String globalClientId = doHandShake(); //assume this method exists
subscribe(globalClientId,"/measurements/10500"); //assume this method exists
connect(globalClientId, new ResponseHandler() {
#Override
public void handleResponse(CloseableHttpResponse httpResponse) {
try {
System.out.println(HttpPostHelper.toStringResponse(httpResponse));
} catch (ParseException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
Your code is not correct.
Messages on the /meta/connect channel do not have the subscription field.
Subscriptions must be sent on the /meta/subscribe channel.
You want to study the Bayeux Specification for further details, in particular the meta messages section and the event messages section.
A suggestion is to launch the CometD Demo and look at the messages exchanged by the client, and mimic those in your implementation.
I try to understand the way asynchronous responses work with Jersey. I read chapter 10 of the Jersey documentation (https://jersey.java.net/documentation/latest/async.html) but it doesn't help with my problem. Also research here on stackoverflow didn't result in satisfying answers (that I can understand).
What I'm trying to do is similar to the one question in this post (Use http status 202 for asynchronous operations). I want to upload a large file to the server using a HTML form document. After the request is send to the server the web service should immediately response with status 202 and a URI where the file can be found after the request has finished.
After reading the post abive it seems possible but sadly no hints how to implement such a behavior where given.
I wrote a small web service to test the functionality:
#Path("/test/async/")
public class TestAsyncResponse {
#GET
#Path("get")
public Response asyncGet(#Suspended final AsyncResponse response) {
new Thread(new Runnable() {
#Override
public void run() {
DateFormat df = new SimpleDateFormat("dd/MM/yy HH:mm:ss");
System.out.println("#### thread started: "
+ df.format(new Date()) + " ####");
String result = veryExpensiveOperation();
System.out.println("#### thread finished: "
+ df.format(new Date()) + " ####");
response.resume(result);
}
private String veryExpensiveOperation() {
try {
Thread.sleep(10000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
return "Woke up!";
}
}).start();
return Response.status(202).entity("Request accepted. " +
"Long running operation started")
.build();
}
}
The service works but as a response I get the "Woke Up!" message after the 10 second wait rather than the 202 response which seems logical because the AsyncResponse is the one that handles the response (as I understand it).
After reading the documentation I got the impression that this is suppose to happen because all Jersey does with the asynchronous server response is to outsource the thread from the response thread pool to another one to free processing time for more responses to the service.
So my two questions would be: Is my understanding correct and can I use the asynchronous server response to get the desired behavior?
I tried to start a new thread without the AsyncResponse and I get a NullPointerExceptionbecause Jersey already closed the response and thus closed the InputStream that contains the file data. Is this the expected behavior? This post (https://stackoverflow.com/a/17559684/1416602) seems to indicate that it might work.
Any response is greatly appreciated.
Greetings
Your question is mixing two topics.
From HTTP perspective, 202 is technically a completed request. And the result of the request is 202, server telling you it will do it on the side. You will have to make another HTTP request to get updated status.
From the perspective of your application, async means that you will execute the request in a separate thread (or other async way). But also, this means that you will not return a result, not even 202, until the other "veryExpensiveOperation" finishes. The whole point in jumping through this hoop is to free up the calling thread. Your web server has a limited number, e.g. 20, and if each of your requests took a very long time, all 20 would be hanging. Using #Suspended you transfer execution from the web server thread to some other means, (another thread in your case). This is really only the first step. The idea behind async servers is that even the veryExpensiveOperation is implemented in some async way so that waiting for a DB or a file does not occupy a whole thread.
I have been through the same pain recently. Jersey keeps claiming it supports Asynchronous REST calls, but I think it's being disingenuous.
And in fact, once I started to work out the correct way of doing this, Jersey actually got in the way.
private static ExecutorService executorService = Executors.newFixedThreadPool( Integer.valueOf( numberOfThreads ) );
#POST
#Path("async")
#Consumes(MediaType.MULTIPART_FORM_DATA)
public Response async( #FormDataParam("file") InputStream inputStream,
#FormDataParam("file") FormDataContentDisposition des ) throws Throwable {
String uniqueID = UUID.randomUUID().toString();
executorService.execute( new Runnable() {
#Override
public void run() {
try {
// do long performing action
} catch (Exception ex) {
}
}
} );
return Response.accepted().location( getResultsURI( uniqueID ) ).build();
}
#GET
#Path("results/{uniqueID}")
#Produces("application/zip")
public Response results( #PathParam(value = "uniqueID ") String uniqueID ) {
// Check status of job
// If not finished...
if (notFinished) {
return Response.status( 202 ).location( getResultsURI( uniqueID ) )
.entity( status ).build();
}
return Response.ok( FileUtils.readFileToByteArray( zip.toFile() ) ).type( "application/zip" )
.header( "Content-Disposition", "attachment; filename=\"filename.zip\"" ).build();
}
protected URI getResultsURI( String uniqueID ) throws URISyntaxException {
return new URI( Constants.WS_VERSION + "/results/" + uniqueID );
}
The biggest pain was that when you set Response.location(), even if you set it to "./results" or "/results", Jersey expands it to the full URL. Which would be fine, except that it ignores any class-level #Path:
#Path(Constants.WS_VERSION)
public class MyEndpoint {
So instead of fighting it, I used the above code to at least make it correct. Ideally I'd like Jersey to leave the "Location" header alone.
Anyway - the above code is what I used (excluding the business logic bits ;) )