问题
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 ?
回答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.
回答2:
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
回答3:
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
来源:https://stackoverflow.com/questions/38572481/how-to-execute-code-after-response-in-javaee