8

Say that my spring controller function receives a large amount of data. I want to return 200 OK, given that the data is structured right, and after that I want to perform the processing, which might take a while.

To my understanding the only way to send response is by return command. But I don't want to end the function on response send.

Are there other ways to send response to client at the middle of the function?

Creating a new thread run is obvious but other languages (JS) let you handle it more elegantly.

@RequestMapping(value = Connectors.CONNECTOR_HEARTBEAT, method = RequestMethod.POST)
public ResponseEntity<String> doSomething(@RequestBody List<Message> messages) {
    HttpStatus code = (messages!=null && !messages.isEmpty()) ? HttpStatus.OK
            : HttpStatus.NOT_FOUND;
    return new ResponseEntity<String>(res, code);
   // how do I add code here??
}
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
orshachar
  • 4,837
  • 14
  • 45
  • 68

4 Answers4

11

You can of course do processing after sending the response. The more general way would be to use the afterCompletion method of a HandlerInterceptor. By construction, it will be executed after the response have been sent to client, but it forces you to split you logic in 2 components the before part in controller, and the after part in the interceptor.

The alternative way is to forget Spring MVC machinery and manually commit the response in the controller:

@RequestMapping(value = Connectors.CONNECTOR_HEARTBEAT, method = RequestMethod.POST)
public void doSomething(@RequestBody List<Message> messages, HttpServletResponse response) {
    int code = (messages!=null && !messages.isEmpty()) ? HttpServletResponse.SC_OK
            : HttpServletResponse.SC_NOT_FOUND;
    if (code != HttpServletResponse.SC_OK) {
        response.sendError(code, res);
        return;
    }
    java.io.PrintWriter wr = response.getWriter();
    response.setStatus(code);
    wr.print(res);
    wr.flush();
    wr.close();

    // Now it it time to do the long processing
    ...
}

Note the void return code to notify Spring that the response have been committed in the controller.

As a side advantage, the processing still occurs in the same thread, so you have full access to session scoped attributes or any other thread local variables used by Spring MVC or Spring Security...

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • If request from two different users then will it share same variable or different for differnt user?? – Satish Kr Jun 23 '17 at 12:26
  • you ave used variable res, what does that variable refer to? – Sagar Shetty Apr 16 '19 at 11:51
  • I do not know. It was in the original question, so I kept it in my answer. I just assumed it was a message to feed the response with. – Serge Ballesta Apr 16 '19 at 11:55
  • Hi @SergeBallesta, I tried your solution but didn't work. After wr.close() i have put sleep method for 10sec. When i am hitting the request then it is waiting for 10 sec to send back response. PFB for code. outputStream.close(); System.out.println("Stream closed."); System.out.println("Sleeping"); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Awake"); – Vivek Garg Jul 18 '19 at 08:14
  • @SergeBallesta, Please help if I am missing something. – Vivek Garg Jul 18 '19 at 08:17
3

You can use @Async

@RequestMapping(value = Connectors.CONNECTOR_HEARTBEAT, method = 
      RequestMethod.POST)
public ResponseEntity<String> doSomething(@RequestBody List<Message> 
      messages) {
    do();
    HttpStatus code = (messages!=null && !messages.isEmpty()) ? HttpStatus.OK
        : HttpStatus.NOT_FOUND;
     return new ResponseEntity<String>(res, code);

}

@Async 
void do(){
   //your code
}

this work in java 8

  • This does not work! Using an @Async annotation in the same class is bypassing the async proxy! The async method has to lie in another class – SoulKa Aug 17 '23 at 14:40
2

I guess you mau use the async mechanism of spring Async methods have been introduced in servlet 3.0 and Spring offers some support to them Basically... you make a request; the request is handled by the server and then, in background, a new thread manages the requesta data Here a useful link (at least i hope :) ) http://spring.io/blog/2012/05/10/spring-mvc-3-2-preview-making-a-controller-method-asynchronous/

Angelo Immediata
  • 6,635
  • 4
  • 33
  • 65
0

You should use the HandlerInterceptor. But the code get a little bit more complex than expected. So, here's a code suggestion to make it simpler by putting the whole solution in a single class:

@RequestMapping(value = Connectors.CONNECTOR_HEARTBEAT, method = RequestMethod.POST)
public ResponseEntity<String> doSomething(@RequestBody List<Message> messages) {
    HttpStatus code = (messages!=null && !messages.isEmpty()) ? HttpStatus.OK
            : HttpStatus.NOT_FOUND;

    result.set(res); // Save the object to be used after response

    return new ResponseEntity<String>(res, code);
}

private static final ThreadLocal<String> result = new ThreadLocal<String>();

@Bean
public HandlerInterceptor interceptor() {
    return new HandlerInterceptor() {
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            // Get the saved object and clean for the next request
            String res = result.get();
            result.set(null);

            // TODO Your code to be executed after response.
        }
    };
}
Italo Borssatto
  • 15,044
  • 7
  • 62
  • 88