Free Online Courses for Software Developers - MrBool
× Please, log in to give us a feedback. Click here to login
×

You must be logged to download. Click here to login

×

MrBool is totally free and you can help us to help the Developers Community around the world

Yes, I'd like to help the MrBool and the Developers Community before download

No, I'd like to download without make the donation

×

MrBool is totally free and you can help us to help the Developers Community around the world

Yes, I'd like to help the MrBool and the Developers Community before download

No, I'd like to download without make the donation

Live Updates in Web Applications: implementing Comet with Servlet 3.0

In this article we will learn about implementing Comet with Servlet 3.0 and also how to implement a web page that displays live updating data.

Introduction

In another article, I showed how a typical web application using the Model-View-Controller can be updated to support the framework required for handling live updates: I added an update timestamp field to each object in one of the application’s database tables, and modified its data access object to support publication of changes to the objects it manages via the Observer (or Listener) pattern.

In this article, I take this groundwork and expand on it, using it to implement a web page that displays live updating data, specifically the balance in each of an account operated by a single customer. In order to simplify this process, I am using the jQuery javascript library (version 1.9.1, which can be downloaded from the official website).

What is this Comet, anyway?

Comet is an unlikely name for a family of related but simple processes (the name is actually a joke based on the brand name of a popular US cleaning product). The technique I will be using is also known as “long polling”.

Immediately after the page finishes loading, it sends a new request to the server requesting details of updates. If any updates have occurred, the server sends them immediately, but in the more common case where there are no updates, it shelves the query, and does not send a response until there are updates available. While you could implement this by waiting inside a servlet’s doGet() method, doing so would be extremely inefficient: it would block the service thread until the update occurred, which if done for many clients would result in the server running out of available threads and no longer being able to service additional requests. Fortunately, the Servlet 3.0 API added a new feature to allow you to do it more efficiently.

Comet

Figure 1: Comet

Preparing the Spring servlet configuration for asynchronous requests

Unfortunately, the Servlet 3.0 API requires servlets to be explicitly configured to allow asynchronous operation, but as we are registering them dynamically by fetching them from a Spring dependency injection container this configuration cannot be done in the usual way (either in web.xml or with a field in an annotation on the servlet class). In order to allow a servlet to configure itself, I modify my SpringServletConfig class like this:

Listing 1: new code in SpringServletConfig.contextInitialized

 
for (Map.Entry<String,HttpServlet> entry : 
	((Map<String,HttpServlet>)springContext.getBean("servlets")).entrySet())
{
	Dynamic reg = servletContext.addServlet (entry.getKey (), entry.getValue ());
	if (entry.getValue() instanceof ServletSelfConfig)
		((ServletSelfConfig)entry.getValue()).configure(servletContext, reg);
	reg.addMapping (entry.getKey ());
}

The code in italic is original code for context: I have added a new interface ServletSelfConfig (containing only the method configure()), and if the servlet object implements it I call it passing it the servlet context and the dynamic registration object, to allow it to set configuration options on either.

Implementing long polling

I start by creating a servlet that configures itself to allow asynchronous requests. Then, in its doGet() method, I call HttpServletRequest.startAsync():

Listing 2: Setting up an asynchronous request

public class AccountChangeNotifier extends HttpServlet implements ServletSelfConfig
{
	AccountDAO accountDAO;
	ObjectWriter objectWriter;
	
	public void setAccountDAO(AccountDAO accountDAO)
	{
		this.accountDAO = accountDAO;
	}
	public void setObjectWriter(ObjectWriter objectWriter)
	{
		this.objectWriter = objectWriter;
	}
	@Override
	public void configure(ServletContext servletContext, Dynamic reg)
	{
		reg.setAsyncSupported(true);
	}
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException
	{
		AsyncContext asyncContext = req.startAsync();
		asyncContext.setTimeout(0);	// do not time requests out
		try
		{
			new RequestProcessor(asyncContext).start ();
		} 
		catch (SQLException e)
		{
			asyncContext.complete();
			throw new ServletException (e);
		}
	}
}

I tell the async context not to time the connection out; we’ll take care of ensuring the connection is terminated. I then start processing it by creating a RequestProcessor object (which I’ll describe in a second) and telling it to start. If we catch an exception, I tell the asynchronous context that we’ve finished handling the request and throw an exception up to the container.

RequestProcessor is an inner class, which looks like this:

Listing 3: RequestProcessor

 
private class RequestProcessor implements AccountChangeListener 
{
	private AsyncContext asyncContext;
	private int customerID;
	private Date since;
	private AtomicBoolean responseSent = new AtomicBoolean(false);
	
	public RequestProcessor(AsyncContext asyncContext)
	{
		this.asyncContext = asyncContext;
		customerID = Integer.parseInt(asyncContext.getRequest().getParameter("customer"));
		since = new Date(Long.parseLong(asyncContext.getRequest().getParameter("since")));
	}

	public void start() throws SQLException
	{
		accountDAO.addListener(this);
		List<Account> accounts = accountDAO.getByCustomerUpdatedSince(customerID, since);
		if (accounts.size() > 0)
			finish (accounts);
	}

	@Override
	public void accountChanged(Account changed)
	{
		if (changed.getCustomer().getId() != customerID) return; 
		try 
		{
			finish (accountDAO.getByCustomerUpdatedSince(customerID, since));
		} 
		catch (SQLException e)
		{
			// if we get an error trying to retrieve more changes, try just 
			// sending the one we already know about:
			finish (Collections.singletonList(changed));
		}
	}
	private void finish(List<Account> updates)
	{
		// we don't need any more notifications
		accountDAO.removeListener(this);
		// attempt to send the response
		if (responseSent.compareAndSet(false, true))
		{
			// the response has not been sent yet, so send it now.
			asyncContext.getResponse().setContentType("application/json");
			try
			{
				objectWriter.writeValue(
						asyncContext.getResponse().getWriter(), 
						Result.accounts(updates));
			} 
			catch (IOException e)
			{
				try
				{
					((HttpServletResponse)asyncContext.getResponse())
						.sendError(400, e.toString());
				}
				catch (IOException nested)
				{
					// not much we can do here!
				}
			}
			asyncContext.complete();
		}
	}
}

In the constructor, we parse the request parameters and store them for later. When the processing is started we register for updates from the account DAO, then check for any objects that are already updated. If there are any, we pass the list to the finish method to send them back to the client and terminate the request.

Otherwise, we do nothing. The AccountDAO will call the accountChanged() method when a change occurs; if the change is in one of the accounts we’re watching, we attempt to fetch a list of all changed accounts (if this fails, we fall back to assuming that the one we’ve been notified of is the only one; this should be correct in any case), and pass the list to the finish method.

The finish method first removes the listener registration from the account DAO, and then checks to see if a response has already been sent (we use an AtomicBoolean to ensure the check only succeeds once, even if two threads attempt to check simultaneously; we could have used a synchronized method instead, but AtomicBoolean has a lower overhead); if no response has been sent we serialize the list of accounts as a JSON object and send it to the client, then we terminate the request. If we catch an exception, we attempt to send an error message to the client.

Implementing the client

The changes required on the client are even simpler than the server-side changes. First, I include jquery:

Listing 4: jquery script reference

 
<script src="jquery-1.9.1.js"></script>
 

Then I ensure that the table cells that contain each account’s balance have a useful id attribute:

Listing 5: the account details table

 
<h1><c:out value="${customer.name}"/>: all accounts</h1>
<table>
<c:forEach var="account" items="${accounts}">
  <tr><td>${account.id}</td><td id="balance${account.id}">${account.balance}</td></tr>
</c:forEach>	        	
</table>

And finally I add a script that performs the updates:

Listing 6: updating the values

 
<script>
	var lastModificationTime = ${lastModificationTime.time};
	function getUpdateNotification ()
	{
		$.ajax ({
			dataType: "json",
			url: "json/accountChangeNotifier?customer=${customer.id}&since=" + 
				lastModificationTime,
			success: updateNotificationReceived
		});
	}
	function updateNotificationReceived(data, textStatus, jqXHR)
	{
		lastModificationTime = data.lastModificationTime;
		for (var i = 0; i < data.accounts.length; i ++)
			$("#balance" + data.accounts[i].id).text(data.accounts[i].balance);
		getUpdateNotification ();
	}
	$(getUpdateNotification);
</script>

This update is very simple. I declare two functions: getUpdateNotification simply requests the updated items from the server, and sends them to updateNotificationReceived when they finish loading (jQuery will automatically decode the data for us). updateNotificiationReceived steps through them and changes the balance field of each account in the list, then calls getUpdateNotification to start the process again.

Finally, I arrange for getUpdateNotification to be called when the page has finished loading. To test it, open a page and then execute a request that should change a balance on the page (you may need to implement such a request - I didn’t include one in the previous article - but doing so is easy, and you will find you don’t need to consider the possibility of the live updates while doing so, as the notifications will be handled automatically).

This works, but in a high-volume server the need to repeatedly start new HTTP transactions could be too demanding. In the next article, I’ll look at the newer WebSockets API, and how to implement a WebSockets server using Tomcat 7.

This is all for this article. See you next time.



My main area of specialization is Java and J2EE. I have worked on many international projects like Recorders,Websites,Crawlers etc.Also i am an Oracle Certified java professional as well as DB2 certified

What did you think of this post?
Services
[Close]
To have full access to this post (or download the associated files) you must have MrBool Credits.

  See the prices for this post in Mr.Bool Credits System below:

Individually – in this case the price for this post is US$ 0,00 (Buy it now)
in this case you will buy only this video by paying the full price with no discount.

Package of 10 credits - in this case the price for this post is US$ 0,00
This subscription is ideal if you want to download few videos. In this plan you will receive a discount of 50% in each video. Subscribe for this package!

Package of 50 credits – in this case the price for this post is US$ 0,00
This subscription is ideal if you want to download several videos. In this plan you will receive a discount of 83% in each video. Subscribe for this package!


> More info about MrBool Credits
[Close]
You must be logged to download.

Click here to login