@WebServlet(urlPatterns={"/dukeetf"}, asyncSupported=true)
public class DukeETFServlet extends HttpServlet {
...
}
Java Platform, Enterprise Edition (Java EE) 8 The Java EE Tutorial |
Previous | Next | Contents |
The dukeetf
example application, located in the
tut-install`/examples/web/dukeetf/` directory, demonstrates how to use
asynchronous processing in a servlet to provide data updates to web
clients. The example resembles a service that provides periodic updates
on the price and trading volume of an electronically traded fund (ETF).
The following topics are addressed here:
The dukeetf
example application consists of a servlet, an enterprise
bean, and an HTML page.
The servlet puts requests in asynchronous mode, stores them in a queue, and writes the responses when new data for price and trading volume becomes available.
The enterprise bean updates the price and volume information once every second.
The HTML page uses JavaScript code to make requests to the servlet for new data, parse the response from the servlet, and update the price and volume information without reloading the page.
The dukeetf
example application uses a programming model known as long
polling. In the traditional HTTP request and response model, the user
must make an explicit request (such as clicking a link or submitting a
form) to get any new information from the server, and the page has to be
reloaded. Long polling provides a mechanism for web applications to push
updates to clients using HTTP without the user making an explicit
request. The server handles connections asynchronously, and the client
uses JavaScript to make new connections. In this model, clients make a
new request immediately after receiving new data, and the server keeps
the connection open until new data becomes available.
The DukeETFServlet
class uses asynchronous processing:
@WebServlet(urlPatterns={"/dukeetf"}, asyncSupported=true)
public class DukeETFServlet extends HttpServlet {
...
}
In the following code, the init
method initializes a queue to hold
client requests and registers the servlet with the enterprise bean that
provides the price and volume updates. The send
method gets called
once per second by the PriceVolumeBean
to send updates and close the
connection:
@Override
public void init(ServletConfig config) {
/* Queue for requests */
requestQueue = new ConcurrentLinkedQueue<>();
/* Register with the enterprise bean that provides price/volume updates */
pvbean.registerServlet(this);
}
/* PriceVolumeBean calls this method every second to send updates */
public void send(double price, int volume) {
/* Send update to all connected clients */
for (AsyncContext acontext : requestQueue) {
try {
String msg = String.format("%.2f / %d", price, volume);
PrintWriter writer = acontext.getResponse().getWriter();
writer.write(msg);
logger.log(Level.INFO, "Sent: {0}", msg);
/* Close the connection
* The client (JavaScript) makes a new one instantly */
acontext.complete();
} catch (IOException ex) {
logger.log(Level.INFO, ex.toString());
}
}
}
The service method puts client requests in asynchronous mode and adds a
listener to each request. The listener is implemented as an anonymous
class that removes the request from the queue when the servlet finishes
writing a response or when there is an error. Finally, the service
method adds the request to the request queue created in the init
method. The service method is the following:
@Override
public void doGet(HttpServletRequest request,
HttpServletResponse response) {
response.setContentType("text/html");
/* Put request in async mode */
final AsyncContext acontext = request.startAsync();
/* Remove from the queue when done */
acontext.addListener(new AsyncListener() {
public void onComplete(AsyncEvent ae) throws IOException {
requestQueue.remove(acontext);
}
public void onTimeout(AsyncEvent ae) throws IOException {
requestQueue.remove(acontext);
}
public void onError(AsyncEvent ae) throws IOException {
requestQueue.remove(acontext);
}
public void onStartAsync(AsyncEvent ae) throws IOException {}
});
/* Add to the queue */
requestQueue.add(acontext);
}
The PriceVolumeBean
class is an enterprise bean that uses the timer
service from the container to update the price and volume information
and call the servlet’s send
method once every second:
@Startup
@Singleton
public class PriceVolumeBean {
/* Use the container's timer service */
@Resource TimerService tservice;
private DukeETFServlet servlet;
...
@PostConstruct
public void init() {
/* Initialize the EJB and create a timer */
random = new Random();
servlet = null;
tservice.createIntervalTimer(1000, 1000, new TimerConfig());
}
public void registerServlet(DukeETFServlet servlet) {
/* Associate a servlet to send updates to */
this.servlet = servlet;
}
@Timeout
public void timeout() {
/* Adjust price and volume and send updates */
price += 1.0*(random.nextInt(100)-50)/100.0;
volume += random.nextInt(5000) - 2500;
if (servlet != null)
servlet.send(price, volume);
}
}
See Using the Timer Service in Chapter 37, "Running the Enterprise Bean Examples" for more information on the timer service.
The HTML page consists of a table and some JavaScript code. The table contains two fields referenced from JavaScript code:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>...</head>
<body onload="makeAjaxRequest();">
...
<table>
...
<td id="price">--.--</td>
...
<td id="volume">--</td>
...
</table>
</body>
</html>
The JavaScript code uses the XMLHttpRequest
API, which provides
functionality for transferring data between a client and a server. The
script makes an asynchronous request to the servlet and designates a
callback method. When the server provides a response, the callback
method updates the fields in the table and makes a new request. The
JavaScript code is the following:
var ajaxRequest;
function updatePage() {
if (ajaxRequest.readyState === 4) {
var arraypv = ajaxRequest.responseText.split("/");
document.getElementById("price").innerHTML = arraypv[0];
document.getElementById("volume").innerHTML = arraypv[1];
makeAjaxRequest();
}
}
function makeAjaxRequest() {
ajaxRequest = new XMLHttpRequest();
ajaxRequest.onreadystatechange = updatePage;
ajaxRequest.open("GET", "http://localhost:8080/dukeetf/dukeetf",
true);
ajaxRequest.send(null);
}
The XMLHttpRequest
API is supported by most modern browsers, and it is
widely used in Ajax web client development (Asynchronous JavaScript and
XML).
See The dukeetf2 Example Application in Chapter 19, "Java API for WebSocket" for an equivalent version of this example implemented using a WebSocket endpoint.
This section describes how to run the dukeetf
example application
using NetBeans IDE and from the command line.
The following topics are addressed here:
Make sure that GlassFish Server has been started (see Starting and Stopping GlassFish Server).
From the File menu, choose Open Project.
In the Open Project dialog box, navigate to:
tut-install/examples/web/servlet
Select the dukeetf
folder.
Click Open Project.
In the Projects tab, right-click the dukeetf
project and select
Run.
This command builds and packages the application into a WAR file
(dukeetf.war
) located in the target
directory, deploys it to the
server, and launches a web browser window with the following URL:
http://localhost:8080/dukeetf/
Open the same URL in a different web browser to see how both pages get price and volume updates simultaneously.
Make sure that GlassFish Server has been started (see Starting and Stopping GlassFish Server).
In a terminal window, go to:
tut-install/examples/web/servlet/dukeetf/
Enter the following command to deploy the application:
mvn install
Open a web browser window and type the following address:
http://localhost:8080/dukeetf/
Open the same URL in a different web browser to see how both pages get price and volume updates simultaneously.
Previous | Next | Contents |