Skip to main content

Non-blocking IO in Servlet 3.1 By Example

Posted by swchan2 on April 16, 2013 at 3:51 PM PDT

Update: One should not use response in AsyncListener#onComplete. Only print debug in this example.

Servlet 3.1 (JSR 340) is almost ready for the release. One of the new features is the support for non-blocking IO. ReadListener and WriteListener are introduced to allow non-blocking processing in Servlet.

Non-blocking IO can only be used in async (defined in Servlet 3.0) or the upgrade mode. We can set the async in a servlet with @WebServlet annotation.
In this blog, we will illustrate the use of the new API with an example having non-blocking read and then non-blocking write. See the comments in the code.

// enable async in the servlet
@WebServlet(urlPatterns="/test", asyncSupported=true)
public class TestServlet extends HttpServlet {
    protected void doPost(HttpServletRequest req, HttpServletResponse res)
            throws IOException, ServletException {

        // start async
        AsyncContext ac = req.startAsync();
        // set up async listener
        ac.addListener(new AsyncListener() {
            public void onComplete(AsyncEvent event) throws IOException {
                System.out.println("Complete");
            }
            public void onError(AsyncEvent event) {
                System.out.println(event.getThrowable());
            }
            public void onStartAsync(AsyncEvent event) {
            }
            public void onTimeout(AsyncEvent event) {
                System.out.println("my asyncListener.onTimeout");
            }
        });

        // set up ReadListener to read data for processing
        ServletInputStream input = req.getInputStream();
        ReadListener readListener = new ReadListenerImpl(input, res, ac);
        input.setReadListener(readListener);
    }
}

All the read operation has been delegated to ReadListenerImpl. The data will be read in non-blocking mode. After ServletInputStream#setReadListener is called, ReadListener#onDataAvailable will be invoked immediately if there is data to read. Otherwise, it will be invoked once the data is ready. The implementation is as follows:

class ReadListenerImpl implements ReadListener {
    private ServletInputStream input = null;
    private HttpServletResponse res = null;
    private AsyncContext ac = null;
    // store the processed data to be sent back to client later
    private Queue queue = new LinkedBlockingQueue();

    ReadListenerImpl(ServletInputStream in, HttpServletResponse r,
            AsyncContext c) {
        input = in;
        res = r;
        ac = c;
    }

    public void onDataAvailable() throws IOException {
        StringBuilder sb = new StringBuilder();
        int len = -1;
        byte b[] = new byte[1024];
        // We need to check input#isReady before reading data.
        // The ReadListener will be invoked again when
        // the input#isReady is changed from false to true
        while (input.isReady() && (len = input.read(b)) != -1) {
            String data = new String(b, 0, len);
            sb.append(data);
        }
        queue.add(sb.toString());
    }

    public void onAllDataRead() throws IOException {
        // now all data are read, set up a WriteListener to write
        ServletOutputStream output = res.getOutputStream();
        WriteListener writeListener = new WriteListenerImpl(output, queue, ac);
        output.setWriteListener(writeListener);
    }

    public void onError(final Throwable t) {
        ac.complete();
        t.printStackTrace();
    }
}

When all the data is read, ReadListenerImpl#onAllDataRead sets up a WriteListenerImpl for writing data in a non-blocking mode.

class WriteListenerImpl implements WriteListener {
    private ServletOutputStream output = null;
    private Queue queue = null;
    private AsyncContext ac = null;

    WriteListenerImpl(ServletOutputStream sos, Queue q, AsyncContext c) {
        output = sos;
        queue = q;
        ac = c;
    }

    public void onWritePossible() throws IOException {
        // write while there is data and is ready to write
        while (queue.peek() != null && output.isReady()) {
            String data = queue.poll();
            output.print(data);
        }
        // complete the async process when there is no more data to write
        if (queue.peek() == null) {
            ac.complete();
        }
    }

    public void onError(final Throwable t) {
        ac.complete();
        t.printStackTrace();
    }
}

Comments

I'm currently migrating an old (ee5) trigger to servlet-3.1. ...

I'm currently migrating an old (ee5) trigger to servlet-3.1. Is it thinkable to handle the WriteListener, only? The read part is not really relevant in my application.

Hi, Thank you very much for the great explanation. Could ...

Hi,

Thank you very much for the great explanation.
Could you also provide simple pom.xml for maven or know where can I find that info?

I used:

javax.servlet
javax.servlet-api
3.1.0
provided

but I am getting this error:
SEVERE: Servlet.service() for servlet [Upload] in context with path [/upcent] threw exception [Servlet execution threw an exception] with root cause
java.lang.ClassNotFoundException: javax.servlet.ReadListener

When adding more dependencies (e.g javaee) I am getting other errors.

Thank you.
Oz.

Thanks for your code. I tried it and it works with a ...

Thanks for your code.

I tried it and it works with a form which contains a file upload HTML5 component but it was impossible to read anything if the form contains only <input type="text"> or <textarea> (with a POST method for the form).

I don't understand why. Can you explain it to me?

Strangely, if I add a file upload I can read the content of the file but also the text typed by the user (the text which is not read without the file upload component).