17.5. HeartbeatACK, Announcement, Flash, Heartbeat, Monitor, Signal Figure 17-14. Heartbeat17.5.1. Goal StoryFor auditing purposes, Frank must run the factory decision support system all day long, so the server needs to ensure that 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 that the server keeps getting requests, the browser explicitly uploads a Heartbeat message every 10 minutes. 17.5.2. ProblemHow do you know the user still has an application in the browser and is actively working with it? 17.5.3. Forces
17.5.4. SolutionHave the browser periodically upload Heartbeat messages to indicate that 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 that the user is no longer active. The objective is to help the server track which users are active. Session tracking isn't essential in Ajax Apps, where it's possible to hold all session data as JavaScript state, but, as described in the Solution of Timeout, there are still reasons to do it. Heartbeat messages are uploaded to a special "Heartbeat service" using XMLHttpRequest Calls. Because 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 to 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 only works if the application is still sitting in the browser and working fine. That's why we use Heartbeat messages, which are, in a sense, the inverse of Timeouts, and therefore a good supplement. Whereas a Timeout message has the browser announce when a timeout has occurred (Figure 17-15), a Heartbeat message has it continuously announced that a Timeout hasn't occurred (Figure 17-16). As soon as the server detects a missed Heartbeat, it can assume the application is no longer running. Figure 17-15. Heartbeat sequenceFigure 17-16. Timeout sequenceAs an alternative to Heartbeat, you can maintain a "Last Seen" or "Last Request" field to track the last time the user issued a request. Thus, not only Heartbeat messages but any other messages will refresh the user's record. The Heartbeat is just a backup. Using these fields 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. 17.5.5. Decisions17.5.5.1. How will you maintain user records?There are two main ways to maintain user records:
17.5.5.2. What, if anything, will cause the browser application to stop sending Heartbeats?One option is for the browser application to always send Heartbeats. This lets the server track whether 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 preceding Solution, the Heartbeat is effectively a message that says "the user has not yet timed out." 17.5.5.3. How much time between Heartbeats? How much delay until a user is declared inactive?You need to decide on the time 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 browser and server performance. The appropriate figure could vary from subseconds to up to 30 minutes or more depending on the following factors:
17.5.6. Real-World ExamplesAs this pattern is speculative, there are no real-world examples at this time. 17.5.7. Code Refactoring: AjaxPatterns Heartbeat WikiThe Timeout pattern (see earlier in this chapter) refactored the wiki to produce the Timeout Wiki Demo (http://ajaxify.com/run/wiki/timeout), and two further refactorings from there. The present refactoring (http://ajaxify.com/run/wiki/timeout/heartbeat), as seen in Figure 17-17, 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. Figure 17-17. Showing user status via HeartbeatTo illustrate Heartbeat, I'll show one of its main applicationsan application that lets 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. 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); } A periodic loop is set up to access the Heartbeat service. 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 to do because we're integrating it into Timeout. We want to stop sending Heartbeats when a timeout has occurred in the browser. In the preceding code, 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 (http://ajaxify.com/run/wiki/timeout/heartbeat/session.phtml?current). 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 ></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 upon timeout, so no more Periodic Refreshes will occur: function onTimeout( ) { ... clearInterval(currentlyOnlineTimer); ... } 17.5.8. Related Patterns17.5.8.1. TimeoutTimeout (see earlier) is a companion pattern, as discussed in the preceding "Solution." 17.5.8.2. Submission ThrottlingHeartbeat resembles Submission Throttling and Periodic Refresh (Chapter 6) insofar as all have a continuous XMLHttpRequest Call cycle. However, Heartbeat has a more specific aimnamely, informing the server of browser state. 17.5.9. MetaphorThe name "Heartbeat" is, of course, taken from a metaphor with the biological heart. 17.5.10. Want to Know More?Go to http://www.mindspring.com/~mgrand/pattern_synopses3.htm#Heartbeat for a brief summary of Enterprise Java Heartbeat pattern. See Mark Grand's "Java Enterprise Design Patterns" for the full pattern. |