X

Non-blocking I/O using Servlet 3.1: Scalable applications using Java EE 7 (TOTD #188)



Servlet 3.0 allowed asynchronous request processing but only
traditional I/O was permitted. This can restrict scalability of your
applications. In a typical application, ServletInputStream is read
in a while loop.
public class TestServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
         throws IOException, ServletException {    
ServletInputStream input = request.getInputStream();
       byte[] b = new byte[1024];
       int len = -1;
       while ((len = input.read(b)) != -1) {
          . . .
       }
   }
}

If the incoming data is blocking or streamed slower than the server
can read then the server thread is waiting for that data. The same
can happen if the data is written to ServletOutputStream.



This is resolved in Servet 3.1 (href="http://jcp.org/en/jsr/detail?id=340">JSR 340, to be
released as part Java EE 7) by adding href="http://docs.oracle.com/javase/7/docs/api/java/util/EventListener.html">event
listeners - ReadListener and WriteListener
interfaces. These are then registered using ServletInputStream.setReadListener
and ServletOutputStream.setWriteListener. The
listeners have callback methods that are invoked when the content is
available to be read or can be written without blocking.



The updated doGet in our case will look like:


AsyncContext context = request.startAsync();
ServletInputStream input = request.getInputStream();
input.setReadListener(new MyReadListener(input, context));



Invoking setXXXListener methods indicate that
non-blocking I/O is used instead of the traditional I/O. At most one
ReadListener can be registered on ServletIntputStream
and similarly at most one WriteListener can be
registered on ServletOutputStream. ServletInputStream.isReady
and ServletInputStream.isFinished are new methods to
check the status of non-blocking I/O read. ServletOutputStream.canWrite
is a new method to check if data can be written without blocking.



 MyReadListener implementation looks like:


@Override
public void onDataAvailable() {
try {
StringBuilder sb = new StringBuilder();
int len = -1;
byte b[] = new byte[1024];
while (input.isReady()
&& (len = input.read(b)) != -1) {
String data = new String(b, 0, len);
System.out.println("--> " + data);
}
} catch (IOException ex) {
Logger.getLogger(MyReadListener.class.getName()).log(Level.SEVERE, null, ex);
}
}

@Override
public void onAllDataRead() {
System.out.println("onAllDataRead");
context.complete();
}

@Override
public void onError(Throwable t) {
t.printStackTrace();
context.complete();
}



This implementation has three callbacks:
  • onDataAvailable callback method is called
    whenever data can be read without blocking

  • onAllDataRead callback method is invoked data for
    the current request is completely read.
  • onError callback is invoked if there is an error
    processing the request.


Notice, context.complete() is called in onAllDataRead
and onError to signal the completion of data read.



For now, the first chunk of available data need to be read in the doGet
or service method of the Servlet. Rest of the data can
be read in a non-blocking way using ReadListener after
that. This is going to get cleaned up where all data read can happen
in ReadListener only.



The sample explained above can be downloaded href="//cdn.app.compendium.com/uploads/user/e7c690e8-6ff9-102a-ac6d-e4aebca50425/f4a5b21d-66fa-4885-92bf-c4e81c06d916/File/5eb844250bb11b6092f7cdd3c559ae50/totd188_nonblocking.zip">from
here and works with href="http://dlc.sun.com.edgesuite.net/glassfish/4.0/promoted/glassfish-4.0-b64.zip">GlassFish
4.0 build 64 and href="http://dlc.sun.com.edgesuite.net/glassfish/4.0/promoted/">onwards.



The slides and a complete re-run of href="https://oracleus.activeevents.com/connect/sessionDetail.ww?SESSION_ID=6793">What's
new in Servlet 3.1: An Overview session at JavaOne is
available href="https://oracleus.activeevents.com/connect/sessionDetail.ww?SESSION_ID=6793">here.



Here are some more references for you:
  • href="https://wikis.oracle.com/display/GlassFish/PlanForGlassFish4.0#PlanForGlassFish4.0-SpecificationStatus">Java
    EE 7 Specification Status
  • Servlet
    Specification Project
  • href="http://java.net/projects/servlet-spec/lists/users/archive">JSR
    Expert Group Discussion Archive
  • href="http://java.net/projects/servlet-spec/downloads/download/Early%20Draft%20Review/javax.servlet-api-3.0.99-SNAPSHOT-javadoc.jar">Servlet
    3.1 Javadocs



Join the discussion

Comments ( 9 )
  • guest Wednesday, November 28, 2012

    Nice.


  • Amit Phaltankar Wednesday, November 28, 2012

    Thanks Arun,

    Nice post!!

    excited enough to get started with servlet 3.1


  • Akshay Ransing Wednesday, November 28, 2012

    when I read this first time first thought was "are they trying to do something similar to nodejs ? is this something like acknowledgement of nodejs way of doing IO" :)

    I think java really will be amazing when Lambda Project a.k.a. JSR 335 will be available.


  • Jim Cheesman Wednesday, November 28, 2012

    When you say the first chunk needs to be read in the doGet method, what does this (temporary) implementation look like?


  • Arun Gupta Wednesday, November 28, 2012

    Jim,

    It'll be similar to the implementation in onDataAvailable. But that does not follow DRY.

    The EG has agreed that this code need to be specified at one place and that would be onDataAvailable method only. So this is only an interim work around, probably for the next few builds of GlassFish only.


  • guest Thursday, November 29, 2012

    The title reads '...Scalable applications using Java EE 7', does it mean that jee7 will be made to run on multiple core when the app is deployed? Just like the other dynamic languages(Scala,Groovy etc)


  • Arun Gupta Monday, December 3, 2012

    Scalability comes from the non-blocking I/O as compared to blocking I/O earlier. No special configuration is required in the application server to run across multiple cores.


  • joel Wednesday, December 19, 2012

    Regarding:

    For now, the first chunk of available data need to be read in the doGet or service method of the Servlet. Rest of the data can be read in a non-blocking way using ReadListener after that. This is going to get cleaned up where all data read can happen in ReadListener only.

    Might be tidier to read the first block some time between the ReadListener being created and the first callback being made. I.e. MyReadListener.create(input, context); have create instantiate and do the first blocking read. That way it's all outside of doGet and to adapt for when it's all async, all the code is in one place (instead of across all your servlets)


  • guest Sunday, March 24, 2013

    I'm a bit confused about the claim that the servlet 3 spec requires blocking io.

    ServletInputStream input = request.getInputStream();

    byte[] b = new byte[1024];

    int len = -1;

    while ((len = input.read(b)) != -1) {

    . . .

    }

    where does it say in the spec that input.read(b) has to block (as in wait for data to arrive in the socket's receive buffer)? InputStream is essentially an interface, why can't the ServletInputStream returned be "backed" by e.g. a byte[] or a ByteBuffer or some other abstraction which has been filled from the original http request by a non-blocking read(s) of the client socket?

    This would not change the semantics in any way.

    I think you're confusing non-blocking with asynchronous reads (which is what your example code demonstrates).

    Could you point to a specific section of the Servlet 3.0 spec that proscribes what I've suggested?


Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.Captcha
Oracle

Integrated Cloud Applications & Platform Services