3

I tried, unsuccessfully, to find a way to run code after response.

In my case, a server sends me data in order to let me do my job but this action can be long (send SMS to 5000 contacts and check who received it, for example). The server expects HTTP 204 No Content response immediately to be sure that data has been received. Then my webapp will performs the action and send the status on a REST API.

My problem is: How to send the response and then execute code ?

For now I tried:

  • doFilter
  • asyncContext
  • ExecutorService

In each case, in order to test if the connection was closing before the end of my action, I call an external URL that takes 10s on purpose to answer. Each time, my server takes 10s to answer.

My servlet is simply hanging on, waiting for the end of the code.

I was unable to make the code works with Executors (new to this), but even if I got an error during the execution of the thread, I want to send HTTP 204 and handle error on another hand.

Is there an easy way to do this ?

Clément Duveau
  • 399
  • 3
  • 14
  • This is probably what you want: https://martinsdeveloperworld.wordpress.com/2014/02/25/using-java-ees-managedexecutorservice-to-asynchronously-execute-transactions/. The google query was "JavaEE background job". – jjm Jul 25 '16 at 16:07
  • Thanks @jjm I didn't try those keywords. This information will be valuable later, I am only evaluating feasability. But I looked further to ManagedExecutorService and successfully used it on a simple example (see below). Thanks for your help, I simply did not think of the word "background" ! – Clément Duveau Jul 26 '16 at 10:53

3 Answers3

1

I use background threads to poll queue tables for things that don't have to be done before the response is sent and will take longer than about 0.1 seconds. For example, an email sender thread handles sending emails in response to a user's action, like a "thanks for your order" message. In this case the servlet generates the email and appends it as a record to an Email table with a status of "ready to send", then lets the email sender thread know there's a new email ready to be sent. The email sender thread grabs the next oldest "ready to send" message, sends it, updates the table and waits for the next one. The cool part is sending an email from the main program is as easy as appending a record to the table and moving on without all that mucking about with the SMTP service each time. I have a separate but very similar thread for sending SMS. If the background process isn't able to handle the load and starts to fall behind, it's pretty easy to launch more than one as long as you're careful about making sure they don't try to grab the same record in the queue.

Mark Olsson
  • 320
  • 2
  • 8
  • Indeed it's a similar functionnality. How do you create your queue ? Directly from the servlet ? About Concurrent data, I already use ConcurrentHashMap, thanks for advise. – Clément Duveau Jul 26 '16 at 08:29
  • I have one servlet that is designated "load on startup" that, among lots of other initialization, creates and starts the background threads in its `init()` method. That way it gets done right when the server starts or a new WAR is deployed. Then I have a ServletContextListener that shuts down the background threads when the app is stopped or the server shuts down in the `destroy()` method. That initialization code could/should probably be moved to the listener's contextInitialized() method, but I had some timing issues that just worked better in a servlet. I can post some code if you want – Mark Olsson Jul 27 '16 at 04:48
  • I tried to do similar function using Executor and it works. I created a FixedThreadPool (or CachedThreadPool) in contextInitialized and destroy it in the destroy method successfully. Thanks for your help. – Clément Duveau Jul 27 '16 at 09:03
1

I think the simplest way is to use another bean with just @Asynchrous method called from your context. See Asynchronous Method Invocation

Another more complex approach can be usage of CDI events which you can couple with successful transaction (e.g. in case of more complex response logic). See Using Events in CDI Applications

0

Here my conclusion on this question:

"Vanilla" Thread in servlet

Here a sample code of a successful execution done after response sent:

protected void doPost(final HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    final long startTime = System.currentTimeMillis(); //Start time to compare easily

    // MY ASYNC JOB
    Thread t1 = new Thread(new Runnable() {
        public void run()
        {
            try {
                Thread.sleep(10000);
                System.out.println("Long operation done.  /   " + (System.currentTimeMillis() - startTime));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }});  
    t1.start();
    // END OF MY ASYNC

    // Servlet code here
    System.out.println("Will send the response.  /   " + (System.currentTimeMillis() - startTime));
    response.setStatus(HttpServletResponse.SC_NO_CONTENT);
}

Result: I received the response in 17ms in Postman

Will send the response. / 1

Long operation done. / 10011

Spawn thread in a servlet is against Java EE spec and EJB doesn't work inside. See here or here. Use threads this way can lead to thread starvation. This is not allowed on every server (Tomcat doesn't prevent this).

Scalability is contractual and it is really interesting to know all options for me and readers. And I do not know on what server I will host my webapp !

ManagedExecutorService

ManagedExecutorService is part of Java EE 7. In my case, the project target a Java EE 6 environment so I used ExecutorService instead. Earlier I faced an issue: I can not access body of the request in my async, and I find this:

Asynchronous Request Body read [...] concepts introduced in Servlet 3.1

But Servlet 3.1 is Java EE 7 too. So my runnable constructor ask for the request body as a String.

Here a sample code for ServletContextListener:

public void contextInitialized(ServletContextEvent event) {
    //Executor
    executor = Executors.newCachedThreadPool();

    //Init Context
    app = event.getServletContext();
    app.setAttribute("executor", executor);
}

//Do not forget to implements contextDestroyed !
public void contextDestroyed(ServletContextEvent event) {
    try {
        executor.shutdown();
        while(!executor.awaitTermination(10, TimeUnit.SECONDS)){
            System.out.println("executor.awaitTermination");
        };
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

And my servlet:

protected void doPost(final HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    final long startTime = System.currentTimeMillis(); //Start time to compare easily

    //Get my executor service and run a new async task
    ExecutorService serv = (ExecutorService) this.getServletContext().getAttribute("executor");
    serv.execute(new testAsync(startTime));

    // Servlet code here
    System.out.println("Will send the response.  /  " + (System.currentTimeMillis() - startTime));
    response.setStatus(HttpServletResponse.SC_NO_CONTENT);
}


//My runnable
private class testAsync implements Runnable{ //Use Callable for Java 7+
    private long startTime;

    //Prior to Servlet 3.1, you have to give the request body instead of using asyncContext
    public testAsync(long pstart){
        this.startTime = pstart;
    }

    @Override
    public void run(){
        try {
            Thread.sleep(10000);
            System.out.println("Long operation done.  /   " + (System.currentTimeMillis() - this.startTime));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

This example works and seems to be the best solution for me because I have to do multithreading in my async task. I use Tomcat for dev but if you use something else, you have to use ManagedExecutorService because it could prevents you to start thread in a servlet.

I am also very surprised to do not find a simple quick example on stackoverflow. I was able to code it thanks to this article.

Edit: I was not aware of JMS at this time and will work on it to see if it fit my problem

Community
  • 1
  • 1
Clément Duveau
  • 399
  • 3
  • 14