11. Asynchronous web applications



Note: new with Java EE 6. The Java EE 6 tutorial does not cover the topic well. The Java EE 7 tutorial is better.

11.1 Understand the interactions that are essential to asynchronous web pages

Sections from spec:

  • 2.3.3.3 Asynchronous processing p. 10 (other than this section, the spec is poorly written on this topic)

A typical sequence of events for asynchronous processing is:

  1. The request is received and passed via normal filters for authentication etc. to the servlet.
  2. The servlet processes the request parameters and/or content to determine the nature of the request.
  3. The servlet issues requests for resources or data, for example, sends a remote web service request or joins a queue waiting for a JDBC connection.
  4. The servlet returns without generating a response.
  5. After some time, the requested resource becomes available, the thread handling that event continues processing either in the same thread or by dispatching to a resource in the container using the AsyncContext.


11.2 Understand the role of AJAX-style client side programming

  • User-responsive due to not loading the whole page again
  • Lighter load on the server, as more functionality implemented on client side through JavaScript, etc.


11.3 Implement asynchronous servlets using the facilities of Java EE 6

Notes:

  • By default asynchronous processing is disabled for a servlet.
  • Dispatching from a synchronous servlet to an asynchronous servlet is illegal. However, an exception will not be thrown unless you actually use an asynchronous method.
  • A servlet that is marked with asyncSupported=true can choose not to defer the processing and finish the processing in the same thread effectively working as a synchronous processor.

To enable asynchronous processing in a servlet, you must have either asyncSupported=true in annotation:

@WebServlet(name="AsyncTestServlet", urlPatterns={"/AsyncTestServlet"},   asyncSupported=true)

or specify it in servlet definition web.xml:

<servlet>
        <servlet-name>AsyncTestServlet</servlet-name>
        <servlet-class>com.enthuware.AsyncTestServlet</servlet-class>
        <async-supported>true</async-supported>
</servlet>

Dispatch methods

It is illegal to use more than one asynchronous dispatch from the same AsyncContext. A runtime exception will be thrown.

Method Description
dispatch() Dispatches the request and response objects of this AsyncContext to the servlet container. If the request was previously forwarded, this method will send it back to the URL of the original request
dispatch(ServletContext context, java.lang.String path) Dispatches the request and response objects of this AsyncContext to the given path scoped to the given context.
dispatch(java.lang.String path) Dispatches the request and response objects of this AsyncContext to the given path.
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
 
@WebServlet(urlPatterns = "/foo/*", name = "NullServlet", asyncSupported = true)
public class NullServlet extends HttpServlet {
    public void doGet(HttpServletRequest req, HttpServletResponse resp) {
 
        final AsyncContext ac = req.startAsync();
 
        ac.start(new Runnable() {
            public void run() {
                ac.dispatch("/page.html");
                // ac.complete(); // will cause IllegalStateException, as request already dispatched.
            }
        });
    }
}

Dispatching from asynchronous to synchronous

It is fine to dispatch from an asynchronous servlet to the synchronous (but not vice-versa). By using req.startAsync() the original request and response objects are passed to the asynchronous thread, so the "Howdy from NullServlet1" will not be lost and will be included in the response.

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
 
@WebServlet(urlPatterns = "/foo/*", name = "NullServlet", asyncSupported = true)
public class NullServlet extends HttpServlet {
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
 
        resp.getWriter().println("Howdy from NullServlet1!");
 
        final AsyncContext ac = req.startAsync();
 
        ac.start(new Runnable() {
            public void run() {
                ac.dispatch("/baz");
            }
        });
    }
}
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
 
@WebServlet(urlPatterns = "/baz/*", name = "NullServle2", asyncSupported = false)
public class NullServlet2 extends HttpServlet {
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
 
        resp.getWriter().println("Howdy from NullServlet2!");
    }
}

timeout

If the AsyncContext#setTimeout(-) argument is <= 0, it means that the asynchronous operation will never be timed out.

In this example, the "Async! Value of Hello is: World" will be included in the response:

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
 
@WebServlet(urlPatterns = "/foo/*", name="NullServlet", asyncSupported=true)
public class NullServlet extends HttpServlet {
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
 
        req.setAttribute("Hello", "World");
 
        final AsyncContext ac = req.startAsync();
        ac.setTimeout(-2);
 
        ac.start(new Runnable() {
            public void run() {
                String att = (String)ac.getRequest().getAttribute("Hello");
 
                try {
                    PrintWriter pw = ac.getResponse().getWriter();
                    pw.println("Async! Value of Hello is: " + att);
                } catch (IOException e) {
                    e.printStackTrace();
                }
 
                ac.complete();
            }
        });
    }
}

Other methods

void complete() : This method simply allows the container to complete the request. The browser gets end of response. You cannot use it to transfer the request to any other servlet or jsp. It completes the asynchronous operation that was started on the request that was used to initialize this AsyncContext, closing the response that was used to initialize this AsyncContext.

Full example

@WebServlet(urlPatterns={"/asyncservlet"}, asyncSupported=true)
public class ServletAnnotatedAsync extends HttpServlet {
 
    @Override
    protected void doGet(HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse)
            throws ServletException, IOException {
 
        httpServletResponse.setContentType("text/html;charset=UTF-8");
 
        final AsyncContext acontext = httpServletRequest.startAsync();
 
        class Resource {
            long bigLong = 10_000_000_000L;
 
            String process(String param) {
                for(int i=0; i<bigLong; i++) {}
                return "done!";
            }
        }
 
        final Resource resource = new Resource();
 
        /*
            Create and run new thread
         */
        acontext.start(new Runnable() {
            public void run() {
                String param = acontext.getRequest().getParameter("param");
 
                if(param == null) {
                    param = "Bob";
                }
 
                /*
                    blocking operation
                 */
                String result = resource.process(param);
 
                /*
                    Once blocking operation complete, continue
                 */
                HttpServletResponse response = (HttpServletResponse)acontext.getResponse();
 
                /*
                    print to the response and close
                */
                try {
                    httpServletResponse.getWriter().println(result);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                acontext.complete();
            }
        });
    }
}



AsyncListener

The AsyncListener cannot be registered in the DD - it must be registered programmatically.

[[code type="java"]
void onComplete(AsyncEvent event)     
// Notifies this AsyncListener that an asynchronous operation has been completed.

void onError(AsyncEvent event)
// Notifies this AsyncListener that an asynchronous operation has failed to complete.

void onStartAsync(AsyncEvent event)     
// Notifies this AsyncListener that a new asynchronous cycle is being initiated via a call to one of the ServletRequest#startAsync methods.

void onTimeout(AsyncEvent event)     
//Notifies this AsyncListener that an asynchronous operation has timed out.
[[/code]]

Example:

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
 
@WebListener
public class MyListener implements AsyncListener {
    public void onComplete(AsyncEvent event) {
        System.out.println("#Async listener [onComplete]");
    }
 
    public void onError(AsyncEvent event) {
        System.out.println("#Async listener [onError]");
    }
 
    public void onStartAsync(AsyncEvent event) {
        System.out.println("#Async listener [onStartAsync]");
    }
 
    public void onTimeout(AsyncEvent event)  {
        System.out.println("#Async listener [onTimeout]");
    }
}
mport javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
 
@WebServlet(urlPatterns = "/foo/*", name="NullServlet", asyncSupported=true)
public class NullServlet extends HttpServlet {
    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException {
 
        final AsyncContext ac = req.startAsync();
 
        try {
            Class lClass = Class.forName("com.nullhaus.MyListener");
            AsyncListener al = ac.createListener(lClass);
 
            ac.addListener(al);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
 
        ac.start(new Thread() {
            public void run() {
                ac.complete();
            }
        });
    }
}

onTimeout and onError example

After the given timeout, the listener onTimeout(-) method will be invoked, as well as the onError(-). After that, the onComplete(-) method will be invoked (response for the ac.complete() invocation). This invocation will be followed by the IllegalStateException, as it's illegal to complete the asynchronous processing if the request has already been dispatched (timeout).

Listener:

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
 
public class MyListener implements AsyncListener {
    public void onComplete(AsyncEvent event) {
        System.out.println("#Async listener [onComplete]");
    }
 
    public void onError(AsyncEvent event) {
        System.out.println("#Async listener [onError]");
    }
 
    public void onStartAsync(AsyncEvent event) {
        System.out.println("#Async listener [onStartAsync]");
    }
 
    public void onTimeout(AsyncEvent event)  {
        System.out.println("#Async listener [onTimeout]");
    }
}

servlet:

mport javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
 
@WebServlet(urlPatterns = "/foo/*",
        name="NullServlet",
        asyncSupported=true)
public class NullServlet extends HttpServlet {
    public void doGet(HttpServletRequest req,
                HttpServletResponse resp)
                      throws ServletException {
 
        final AsyncContext ac = req.startAsync();
 
        try {
            Class lClass = Class.forName("com.nullhaus.MyListener");
            AsyncListener al = ac.createListener(lClass);
 
            ac.addListener(al);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
 
        ac.setTimeout(3000);
 
        ac.start(new Thread() {
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ac.complete();
            }
        });
    }
}






Comments

If you have questions, corrections, information or examples to add, please comment below.

Add a New Comment
or Sign in as Wikidot user
(will not be published)
- +
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License