
ACK, Announcement, Flash, Heartbeat, Monitor, Signal
For auditing purposes, Frank must be running the factory decision support system all day long, so the server needs to ensure it's always alive in the browser. But requests won't come in very often, because it's a fat client, with the business logic in Javascript. To ensure the server keeps getting requests, the browser explicitly uploads a Heartbeat message every 10 minutes.
How do you know the user still has an application in the browser, and is actively working with it?
It's useful for the server to track if the user is active, with several applications highlighted in the Solution of “Timeout”.
Due to the stateless nature of HTTP, the server doesn't know when the user has quit the browser, experienced a browser crash, or surfed away to a different URL. Note that you could use the Javascript onunload event to catch the last of these, but it won't work for the first two. In tracking the user's activity, the server may be assuming the user's still active when they've in fact abandoned the application.
The user sometimes spends a long time working in the browser, but in a manner which yields no calls the server. For example, filling out a long form or playing a game. The server will have no way to know the user's still around if no calls are made.
Have the browser periodically upload Heartbeat messages to indicate the application is still loaded in the browser and the user is still active. The server keeps track of each user's "last Heartbeat". If the Heartbeat interval is 10 minutes and the user's last Heartbeat was longer than 10 minutes ago, the server knows the user is no longer active. The objective is to help the server track which users are active. Note that session tracking isn't essential in Ajax Apps, where it's possible to hold all session data as Javascript state, but there are still reasons to do it, as described in the “Timeout” solution.
Heartbeat messages are uploaded to a special "Heartbeat service" using “XMLHttpRequest Call”s. As they affect server state, they should be POSTed in. The Heartbeat service will update the user's last Heartbeat record, but how does it associate a Heartbeat message with a user? Heartbeat relies on some form of session management. One approach is to use cookies, either directly or via a cookie-based session framework. However, if you do that, the conversation will be stateful, thus violating a fundamental “RESTful Service” principle. A cleaner approach is to explicitly include the session ID in the Heartbeat body. Note that you shouldn't just upload the user ID, as others could easily fake the user ID.
Heartbeat is closely related “Timeout”, but they work in different ways. You can use either independently, but Heartbeat works best as a supplement to “Timeout”. The point of “Timeout” is to stop the browser application after an idle period, for security and bandwidth reduction. Notifying the server of Timeout events yields extra benefits, but it's optional. Moreover, it won't work especially well anyway, because it relies on the application still sitting in the browser and working fine. That's why we use Heartbeat messages, which are in a sense the inverse of “Timeout”s, and therefore a good supplement. Whereas a “Timeout” message has the browser announce when a timeout has occurred (Figure 1.95, “Heartbeat Sequence”), a Heartbeat message has it continuously announce that a timeout hasn't occurred (Figure 1.96, “Timeout Sequence”). As soon as the server detects a missed Heartbeat, it can assume the application is no longer running.
As a variant of Heartbeat, you can maintain a "Last Seen" or "Last Request" field, to track the last time the user issued a request. Thus, it's not just Heartbeat messages, but any other messages that will refresh the user's record. The Heartbeat is just a backup Doing so might paint a more meaningful picture if you're showing the timestamp to other users or feeding it into analysis.
This pattern is purely speculative in the context of Ajax applications. However, Heartbeats are commonplace in enterprise messaging systems, where they are used to monitor the status of components throughout a network.
There are two main options for maintaining user records:
In memory, which means the data is not persisted and will be lost once the user is timed out. In most environments, this is what will happen if you rely on standard session objects.
Directly in the database, against persistent user records. There's some extra storage and data maintenance involved, but the benefit is that you always know when the user was last seen.
One option is for the browser application to always send Heartbeats. This lets the server track if the application is still sitting in the browser, which may be useful for analysis purposes. More likely, though, you probably want to send Heartbeats only if the user is actively working with the browser. Thus, as mentioned in the Solution, the Heartbeat is effectively a message that says "the user has not yet timed out".
You need to decide on the period between Heartbeats. If it's too long, the information will not be very useful. If it's too short, you'll be placing a strain on the network, as well as impacting on browser and server performance.
The right figure could vary from sub-second up to 30 minutes or more, depending on the following factors:
Some applications make up-to-the-second information more critical. In a multi-user system, for example, users' work may be dictated by whoever else is present. If you wait 10 minutes to tell Alice that Bob has quit the chess game, she might be annoyed that she's wasted the last 10 minutes thinking about her next move.
Ideally, the period should be as short as possible, but you can't always justify it. For an Intranet application, you're likely to use a shorter period in recognition of better resources per user.
The “Timeout” pattern refactored the wiki to produce the Timeout Wiki Demo, and two further refactorings from there. The present refactoring (Figure 1.97, “Showing User Status via Heartbeat”) creates a third version of the basic Timeout demo, introducing a Heartbeat. Note that Heartbeat can work independently of “Timeout”, but works better in tandem, as this pattern demonstrates.
To illustrate Heartbeat, one of its main applications is shown: to let users see who else is "currently online", i.e. who else has a recent Heartbeat registered. Thus, each user is assigned a random ID and a Heartbeat mechanism is used to maintain the "Currently Online" list.
But first, let's look at the Heartbeat mechanism. Server-side, the Author class contains a lastRequest property:
class Author {
private $id;
...
private $lastRequest;
...
}
The Heartbeat service accepts a message with an author ID and updates the user's lastRequest. Note that a production system should accept a session ID, rather than a user ID. The service extracts the author from the database and updates its lastRequest to the present. If the author does not yet exist, a new record is created with lastRequest set to the present. Finally, the record is persisted:
if (isset($_POST['renew']) && $_POST['renew']=="true") {
$authorId = $_POST['author'];
$now = time();
$author = $authorDAO->fetch($authorId);
logInfo("Got author record for $authorId");
if ($author) {
logInfo("Updating timestamp for user $authorId");
$author->setLastRequest($now);
} else {
logInfo("Adding author $authorId");
$author = new Author($authorId, $now, $now);
}
$authorDAO->persist($author);
}
To access the Heartbeat service, a periodic loop is set up. Because the Heartbeat call is "fire-and-forget", an empty callback function is used:
window.onload = function() {
...
heartbeatTimer = setInterval(sendHeartbeat, HEARTBEAT_PERIOD);
...
}
function sendHeartbeat() {
vars = {
renew: true,
author: authorId
};
ajaxCaller.postForPlainText("session.phtml", vars, function() {});
}
And that's the basic Heartbeat pattern. But there's a bit more here because we're integrating it into “Timeout”. We want to stop sending Heartbeats when a timeout has occurred in the browser. Above, we created a variable, heartbeatTimer, to track the periodic Heartbeat calls. Since we have a handle on the periodic process, we are able to cancel it upon timeout:
function onTimeout() {
...
clearInterval(heartbeatTimer);
...
}
The other feature here is the list of online users, which illustrates one way to use the Heartbeat data. session.phtml exposes an XML list of currently online users. The fetchRecentlyActive method accepts a parameter indicating just how recently active the records should be. In this case, we ask for users with a request in the past 10 seconds:
if ($_GET="current") {
header("Content-type: text/xml");
echo "<authors>";
foreach ($authorDAO->fetchRecentlyActive(10) as $author) {
echo "<author>{$author->getId()}</author>";
}
echo "</authors>";
In the browser, there's a div to contain the list:
<h1>Who's Online?</h1>
<div id="currentlyOnline"></div>
A timer is established to perform a “Periodic Refresh” on the current data. Every few seconds, the XML is requested and the callback function transforms it into HTML for display:
currentlyOnlineTimer = setInterval(updateCurrentlyOnline, CURRENTLY_ONLINE_PERIOD);
function updateCurrentlyOnline() {
ajaxCaller.getXML("session.phtml?current", function(xml) {
$("currentlyOnline").innerHTML = "<ul>";
var authors = xml.getElementsByTagName("author");
for (var i=0; i<authors.length; i++) {
var authorName = authors[i].firstChild.nodeValue;
$("currentlyOnline").innerHTML += "<li> " + authorName + "</li>";
}
$("currentlyOnline").innerHTML += "</ul>";
});
}
As with the Heartbeat timer, the "currently online" timer is cancelled on timeout, so no more “Periodic Refresh”es will occur:
function onTimeout() {
...
clearInterval(currentlyOnlineTimer);
...
}
Timeout is a companion pattern, as discussed in the Solution above.
Heartbeat resembles “Submission Throttling” and “Periodic Refresh” insofar as all have a continuous “XMLHttpRequest Call” cycle. However, Heartbeat has a more specific aim, namely informing the server of browser state.
Brief summary of Enterprise Java "Heartbeat" pattern - see Mark Grand's "Java Enterprise Design Patterns" for the full pattern.