Holooly Plus Logo

Input / Question:

Input / Question:

How to read and write I/O in an asynchronous,nonblocking manner in java ?

Verified

Output/Answer

Use the Non-Blocking I/O API that is part of the Servlet 3.1 release. To use the new technology, implement the new ReadListener interface when performing nonblocking reads, and implement the WriteListener interface for performing nonblocking writes. The implementation class can then be registered to a ServletInputStream or ServletOutputStream so that reads or writes can be performed when the listener finds that servlet content can be read or written without blocking.

The following sources are those of a ReadListener implementation that reside in the source file org.javaee8recipes.chapter01.recipe01_18.AcmeReadListenerImpl.java, and they demonstrate how to implement the ReadListene :

package org.javaee8recipes.chapter01.recipe01_18;

import java.io.IOException;

import java.util.logging.Level;

import java.util.logging.Logger;

import javax.servlet.AsyncContext;

import javax.servlet.ReadListener;

import javax.servlet.ServletInputStream

public class AcmeReadListenerImpl implements ReadListener {

private ServletInputStream is = null;

private AsyncContext async = null;

public AcmeReadListenerImpl(ServletInputStream in, AsyncContext ac) {

this.is = in;

this.async = ac;

System.out.println("read listener initialized");

}

@Override

public void onDataAvailable() {

System.out.println("onDataAvailable");

try {

StringBuilder sb = new StringBuilder();

int len = -1;

byte b[] = new byte[1024];

while (is.isReady()

&& (len = is.read(b)) != -1) {

String data = new String(b, 0, len);

System.out.println(data);

}

} catch (IOException ex) {

Logger.getLogger(AcmeReadListenerImpl.class.getName()).log(Level.SEVERE, null, ex);

}

}

@Override

public void onAllDataRead() {

System.out.println("onAllDataRead");

async.complete();

}

@Override

public void onError(Throwable thrwbl) {

System.out.println("Error: " + thrwbl);

async.complete();

}

}

Next, use the listener by registering it to a ServletInputStream (in the case of the ReadListener) or a ServletOutputStream (in the case of a WriteListener). For this example, I show a servlet that utilizes the AcmeReadListenerImpl class. The sources for the following class reside in the org.javaee8recipes.

 

package org.javaee8recipes.chapter01.recipe01_18;

import java.io.IOException;

import java.io.InputStream;

import java.io.PrintWriter;

import java.util.concurrent.CountDownLatch;

import javax.servlet.AsyncContext;

import javax.servlet.ServletContext;

import javax.servlet.ServletException;

import javax.servlet.ServletInputStream;

import javax.servlet.ServletOutputStream;

import javax.servlet.annotation.WebServlet;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

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

public class AcmeReaderServlet extends HttpServlet {

protected void processRequest(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

response.setContentType("text/html;charset=UTF-8");

try (PrintWriter output = response.getWriter()) {

String filename = "test.txt";

ServletContext context = getServletContext();

InputStream in = context.getResourceAsStream(filename);

output.println("<html>");

output.println("<head>");

output.println("<title>Acme Reader</title>");

output.println("</head>");

output.println("<body>");

output.println("<h1>Welcome to the Acme Reader Servlet</h1>");

output.println("<br/><br/>");

output.println("<p>Look at the server log to see data that was read

asynchronously from a file<p>");

AsyncContext asyncCtx = request.startAsync();

ServletInputStream input = request.getInputStream();

input.setReadListener(new AcmeReadListenerImpl(input, asyncCtx));

output.println("</body>");

output.println("</html>");

} catch (Exception ex){

System.out.println("Exception Occurred: " + ex);

}

}

// Http Servlet Methods ...

...

}

The last piece of code that we need is the servlet that invokes the AcmeReaderServlet, passing the message that needs to be processed. In this example, a file from the server is passed to the AcmeReaderServlet as input, which then is asynchronously processed via the AcmeReadListenerImpl class. The following code is taken from org.javaee8recipes.chapter01.recipe01_18.ReaderExample.java.

package org.javaee8recipes.chapter01.recipe01_18;

import java.io.BufferedReader;

import java.io.BufferedWriter;

import java.io.IOException;

import java.io.InputStream;

import java.io.InputStreamReader;

import java.io.OutputStreamWriter;

import java.io.PrintWriter;

import java.net.HttpURLConnection;

import java.net.URL;

import java.util.logging.Level;

import java.util.logging.Logger;

import javax.servlet.ServletContext;

import javax.servlet.ServletException;

import javax.servlet.annotation.WebServlet;

import javax.servlet.http.HttpServlet;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

@WebServlet(name = "ReaderExample", urlPatterns = {"/ReaderExample"})

public class ReaderExample extends HttpServlet {

protected void processRequest(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

response.setContentType("text/html;charset=UTF-8");

String filename = "/WEB-INF/test.txt";

ServletContext context = getServletContext();

InputStream in = context.getResourceAsStream(filename);

try (PrintWriter out = response.getWriter()) {

String path = "http://"

+ request.getServerName()

+ ":"

+ request.getServerPort()

+ request.getContextPath()

+ "/AcmeReaderServlet";

out.println("<html>");

out.println("<head>");

out.println("<title>Intro to Java EE 7 - Servlet Reader Example</title>");

out.println("</head>");

out.println("<body>");

out.println("<h1>Servlet ReaderExample at " + request.getContextPath() + "</h1>");

out.println("Invoking the endpoint: " + path + "<br>");

out.flush();

URL url = new URL(path);

HttpURLConnection conn = (HttpURLConnection) url.openConnection();

conn.setChunkedStreamingMode(2);

conn.setDoOutput(true);

conn.connect();

if (in != null) {

InputStreamReader inreader = new InputStreamReader(in);

BufferedReader reader = new BufferedReader(inreader);

String text = "";

out.println("Beginning Read");

try (BufferedWriter output = new BufferedWriter(new OutputStreamWriter(conn.

getOutputStream()))) {

out.println("got the output...beginning loop");

while ((text = reader.readLine()) != null) {

out.println("reading text: " + text);

out.flush();

output.write(text);

Thread.sleep(1000);

output.write("Ending example now..");

out.flush();

}

output.flush();

output.close();

}

}

out.println("Review the Glassfish server log for messages...");

out.println("</body>");

out.println("</html>");

} catch (InterruptedException | IOException ex) {

Logger.getLogger(ReaderExample.class.getName()).log(Level.SEVERE, null, ex);

}

}

@Override

protected void doGet(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

processRequest(request, response);

}

@Override

protected void doPost(HttpServletRequest request, HttpServletResponse response)

throws ServletException, IOException {

processRequest(request, response);

}

@Override

public String getServletInfo() {

return "Short description";

}

}

When the servlet is visited, the asynchronous, nonblocking read of the test.txt file will occur, and its text will be displayed in the server log

How It Works

Servlet technology has allowed only traditional (blocking) input/output during request processing since its inception. In the Servlet 3.1 release, the new Non-Blocking I/O API makes it possible for servlets to read or write without any blocking. This means other tasks can be performed at the same time that a read or write is occurring, without any wait. Such a solution opens a new realm of possibilities for servlets, making them much more flexible for use along with modern technologies such as the WebSockets protocol.

To implement a nonblocking I/O solution, new programming interfaces have been added to ServletInputStream and ServletOutputStream, as well as two event listeners: ReadListener and WriteListener. ReadListener and WriteListener interfaces make the servlet I/O processing occur in a nonblocking manner via callback methods that are invoked when servlet content can be read or written without blocking. Use the ServletInputStream.setReadListener(ServletInputStream, AsyncContext) method to register a ReadListener with a ServletInputStream, and use the I/O read ServletInputStream.setWriteLis tener(ServletOutputStream, AsyncContext) method for registering a WriteListener. The following lines of code demonstrate how to register a ReadListener implementation with a ServletInputStream:

AsyncContext context = request.startAsync();

ServletInputStream input = request.getInputStream();

input.setReadListener(new ReadListenerImpl(input, context));

After a listener has been registered with a ServletInputStream, the status on a nonblocking read can be checked by calling the methods ServletInputStream.isReady and ServletInputStream.isFinished. For instance, a read can begin once the ServletInputStream.isReady method returns a true, as shown here:

while (is.isReady() && (b = input.read()) != -1)) {

len = is.read(b);

String data = new String(b, 0, len);

}

To create a ReadListener or WriteListener, three methods must be overridden: onDataAvailable, onAllDataRead, and onError. The onDataAvailable method is invoked when data is available to be read or written, onAllDataRead is invoked once all the data has been read or written, and onError is invoked if an error is encountered. The code for AcmeReadListenerImpl in the solution to this recipe demonstrates how to override these methods.

The AsyncContext.complete method is called in the onAllDataRead method to indicate that the read has been completed and to commit the response. This method is also called in the onError implementation so that the read will complete, so it is important to perform any cleanup within the body of the onError method to ensure that no resources are leaked, and so on.

To implement a WriteListener, use the new ServletOutputStream.canWrite method, which determines whether data can be written in a nonblocking fashion. A WriteListener implementation class must override a couple of methods: onWritePossible and onError. The onWritePossible method is invoked when a nonblocking write can occur. The write implementation should take place within the body of this method. The onError method is much the same as its ReadListener implementation counterpart, because it is invoked when an error occurs.

The following lines of code demonstrate how to register a WriteListener with a ServletOutputStream:

AsyncContext context = request.startAsync();

ServletOutputStream os = response.getOutputStream();

os.setWriteListener(new WriteListenerImpl(os, context));

The WriteListener implementation class must include overriding methods for onWritePossible and onError. The following is an example for a WriteListener implementation class:

import javax.servlet.AsyncContext;

import javax.servlet.ServletOutputStream;

import javax.servlet.WriteListener;

public class WriteListenerImpl implements WriteListener {

ServletOutputStream os;

AsyncContext context;

public WriteListenerImpl(ServletOutputStream out, AsyncContext ctx){

this.os = out;

this.context = ctx;

System.out.println("Write Listener Initialized");

}

@Override

public void onWritePossible() {

System.out.println("Now possible to write...");

// Write implementation goes here...

}

@Override

public void onError(Throwable thrwbl) {

System.out.println("Error occurred");

context.complete();

}

}

The new Non-Blocking I/O API helps bring the Servlet API into compliance with new web standards.

The new API makes it possible to create web-based applications that perform well in an asynchronous fashion.