Chapter 10. Browser-Server Dialogue
We’ve looked at the basic technologies for web remoting, which leads to questions about browser-server information flow. How will you deal with multiple calls? How will you keep the browser and the server synchronized? How will you access external domains?
Call Tracking is about tracking calls and dealing with the asynchronous nature of Ajax. The next three patterns concern synchronization. With Periodic Refresh, the browser keeps requesting fresh information from the server. The opposite is Submission Throttling, where the browser keeps uploading new information to the server. An alternative to Submission Throttling, Explicit Submission involves uploading only when the user performs some action.
All of those patterns help manage bandwidth, but keeping things in sync can still be quite complex when there are lots of entities in the server and a rich interface in the browser. Distributed Events help manage the complexity.
Finally, Cross-Domain Proxy is a technique for mediating the dialogue between the browser and external servers.
Call Tracking
⊙⊙⊙ Asynchronous, Follow, Monitor, Parallel, Tracking, XMLHttpRequest
Developer Story
Dave is writing an Ajax chat client, and he’s concerned the more vocal users will hog server resources, so he decides to enforce a policy of no more than three pending server calls at any time. Thus, the browser script pushes messages into a queue and tracks the progress of each XMLHttpRequest Call (Chapter 6) containing those commands. He can enforce his maximum call count policy by tracking the state of each call.
Problem
How can you control parallel XMLHttpRequest Calls (Chapter 6)?
Forces
Fast users and busy applications often stretch networks and servers to their limits.
Asynchronous interaction is the only practical approach for Ajax applications. You don’t want to block all interaction just because the network happens to be slow.
The number of simultaneous requests must be controlled, because browsers can only handle a few at a time, and also to reduce the overall load.
Solution
Track XMLHttpRequest calls as
they proceed from browser to server and back again.
XMLHttpRequest
is a fairly basic
component that needs to be augmented or wrapped for better control
over asynchronous dialogue. Furthermore, it is useful to keep all
these wrappers in a collection. Note that this pattern is fairly low
level, and the details should generally be encapsulated in a wrapper
library.
The standard mechanism for Call Tracking requires an XMLHttpRequest
wrapper. Consider the
implementation of the Ajax Client Engine (ACE) library (http://www.lishen.name/). A Requester
abstraction creates an XMLHttpRequest
object upon
construction:
function Requester( ) { var requester; if (window.XMLHttpRequest) { requester = new window.XMLHttpRequest( ); ... } ... }
Requester
’s response
handler doesn’t go directly back to the caller’s registered event
handler, but instead to a method of Requester
. This internal callback can then
perform logging and other tasks before passing control back to the
caller’s handler. There’s also a caching mechanism, so the Requester
will add and remove itself from
the cache as the call progresses.
The most important reason to track calls is to tame the
asynchronous nature of XMLHttpRequest
. There’s a potential bug in
which the programmer treats XMLHttpRequest
as a Singleton (Gamma et
al., 1995). That is, there’s only a single, global, XMLHttpRequest
instance, but parallel
calls are made (http://smokey.rhs.com/web/blog/PowerOfTheSchwartz.nsf/d6plinks/RSCZ-6CDPEX).
An initial call will be pending, when suddenly a new call steps in.
What happens here is unclear, as it’s the sort of unanticipated
behavior that will vary across browser implementations, but it
certainly won’t be a happy result. The first call will possibly be
cancelled, and it’s unlikely its event handler will be notified. The
simplest way to resolve this problem is to create a new XMLHttpRequest
instance each time, then
use a closure; i.e.:
xhReq.onreadystatechange = function( ) { if (xhReq.readyState != 4) { return; } var serverResponse = xhReq.responseText; ... };
We can consider this a limited form of Call Tracking, since
we’ve at least ensured each pending call will have its own
associated XMLHttpRequest
. Still,
we can achieve a lot more by wrapping requests and placing the
wrappers in a collection, as demonstrated by ACE and other
libraries:
- Pooling
A collection of wrappers can be used as a cache. When each object has delivered its response, it’s returned to the available pool (and its state reset in case of an error). This reduces object creation and destruction.
- Limiting
Using a fixed-size pool (see the previous point), you have the option of limiting the number of simultaneous requests to reduce network load and prevent problems in the browser; most browsers will only allow a handful of pending requests at any time.
- Detecting timeouts
As explained in XMLHttpRequest Call (Chapter 6), the framework can start a timer when the request is issued and notify the caller if a timeout occurs.
- Sequencing
With all requests coordinated by a central piece of logic, it’s possible to influence the sequence of calls going in and out of the server. While it’s best to design for calls to be completely parallel, sometimes you might have constraints; e.g., you might want the server to process calls in the same order as the user initiates them; i.e., fire a request only when the previous response has been received.
- Atomic Processing
Related to the previous point, you often need to ensure the browser will handle one response completely before moving onto the next. One scenario is appending a bunch of response information—if the responses are dealt with in parallel, you’ll end up with interleaved messages. One style of Call Tracking is to create JavaScript Command (Gamma et al.) objects from each response, push them onto a queue, and have a separate thread execute them one at a time.
- Passing in Call Context
Sometimes, a service’s response is minimal and doesn’t include any detail about the original call—for example,
false
as opposed to<spellcheck term="misspeld"> false </spellcheck>
. Indeed, sometimes there’s no response at all, and the framework will need to inform the response handler of a timeout. In these cases, it’s useful to provide some context about the original call. By tracking the call, a framework can remember a “call context” that the caller sets when the request is issued. When the framework eventually transmits the response to the caller’s response handler, it also passes in the call context. See "Code Refactoring: AjaxPatterns Predictive Fetch Sum" in Predictive Fetch (Chapter 13) to see how a calling context can be used.- Logging
A wrapper can register itself as the wrapped object’s response handler, and then log any changes. It can also poll for any new content (since there’s no guarantee the event handler will be called when new content is added).
Real-World Examples
Ajax Client Engine (ACE) library
Li Shen’s Ajax Client Engine (ACE) (http://www.lishen.name/) uses Call Tracking to ease development and harden the application in production. Among its many features are several related to Call Tracking:
Long calls are timed out.
Changes to
XMLHttpRequest
’s response state are logged.A service can be periodically polled.
The caller can declare exactly when the callback method should be invoked.
AjaxCaller library
The AjaxCaller library (http://ajaxify.com/run/Lib/js/ajaxCaller.js),
used throughout the AjaxPatterns demos for Web Remoting, uses Call
Tracking to pool Call
s, which
wrap XMLHttpRequest
objects.
libXmlRequest library
libXmlRequest (http://www.whitefrost.com/reference/2005/09/09/libXmlRequest.html)
is another XMLHttpRequest
wrapper. It keeps XMLHttpRequest
objects in a pool so they
can be reused, and tracks the response status in order to manage
the pool.
Code Example: Ajax Client Engine (ACE)
In this example, we’ll look at ACE’s internal handling
of call timeouts. The library consists of Requester
objects, which wrap XMLHttpRequest
objects. As explained
earlier in the Solution, the wrapped XMLHttpRequest
objects are created upon
construction. When the wrapper is invoked to make a call, it creates
a timer to cancel the request if it takes too long. The timer ID is
held as an attribute of the wrapper.
timeoutId = window.setTimeout(endRequest, request.callbackTimeout * 1000);
To track the call, the wrapper registers itself as a request handler:
function beginRequest( ) { ... requester.onreadystatechange = readystatechangeHandler; ... }
Its handler is therefore called upon each change, and in the case of a complete call, cancels the timer:
function readystatechangeHandler( ) ... if (requester.readyState == Ace.ReadyState.Complete) { ... if (requester.status == Ace.Status.OK) { ... if (timeoutId) { window.clearTimeout(timeoutId); timeoutId = undefined; } ... } ... }
Alternatives
Fire-and-forget
Some calls need no tracking because they are low-priority uploads of information to the server. For example, a chat app might reasonably ignore the results of uploading the user’s messages because a separate thread is continuously polling for all recent messages. Another example would be uploading information to support Predictive Fetch (Chapter 13), where the worst case is simply the lost opportunity of a performance optimization. Ajaxian.com featured an interesting article on optimizing for this kind of request (http://www.ajaxian.com/archives/2005/09/ajaxian_fire_an.html).
Global XMLHttpRequest
You may be able to get away with a single, global XMLHttpRequest
under certain conditions and with care. For example, you can use a
lock to ensure there’s only one pending call at each time (which
is really a special case of Call Tracking). However, you risk forcing the user to endure long
waiting periods.
Metaphor
Call Tracking is like tagging self-addressed envelopes before you send them away, so you can track them as they return.
Acknowledgments
This pattern was originally inspired by Richard Schwartz’s
caution against the familiar anti-pattern of working with a global
XMLHttpRequest
(http://smokey.rhs.com/web/blog/PowerOfTheSchwartz.nsf/d6plinks/RSCZ-6CDPEX).
Periodic Refresh
⊙⊙⊙ Auto-Update, Polling, Sync, Synchronise, Sychronize, Real-Time
Developer Story
Devi’s coding up a ticket sales web site. For each event, she wants to keep the browser updated with the number of tickets remaining. Thus, she introduces a timer so that every 30 seconds, it calls a web service to pull down the latest sales stats.
Problem
How can the application keep users informed of changes occurring on the server?
Forces
The state of many web apps is inherently volatile. Changes can come from numerous sources, such as other users, external news and data, results of complex calculations, and triggers based on the current time and date.
HTTP requests can only emerge from the client. When a state change occurs, there’s no way for a server to open connections to interested clients.
One way to keep the browser updated is HTTP Streaming (Chapter 6), but, as the “Alternatives” section for that pattern explains, it’s not always ideal. In particular, it’s not very scalable.
Solution
The browser periodically issues an XMLHttpRequest Call to gain new information; e.g., one call every five seconds. The solution makes use of the browser’s Scheduling (Chapter 7) capabilities to provide a means of keeping the user informed of latest changes.
In its simplest form, a loop can be established to run the refresh indefinitely, by continuously issuing XMLHttpRequest Calls (Chapter 6):
setInterval(callServer, REFRESH_PERIOD_MILLIS);
Here, the callServer
function will invoke the server, having registered a callback
function to get the new information. That callback function will be
responsible for updating the DOM according to the server’s latest
report. Conventional web apps, even most of those using
XMLHttpRequest Calls, operate under a paradigm
of one-way communication: the client can initiate communication with
the server, but not vice versa. Periodic Refresh fakes a
back-channel: it approximates a situation where the server pushes
data to the client, so the server can effectively receive new
information from the browser. Indeed, as some of the examples show,
the server can also mediate between users in almost real-time. So
Periodic Refresh can be used for peer-to-peer communication
too.
But before we get too carried away with Periodic Refresh, it’s important to note that it’s a serious compromise, for two key reasons:
The period between refreshes would ideally be zero, meaning instant updates. But that’s not realistic and the browser will always lag behind. Latency is particularly problematic when the user is interacting with a representation of volatile server-side data. For instance, a user might be editing an object without knowing that another user has already deleted it.
There is a significant cost attached to Periodic Refresh. Each request, no matter how tiny, demands resources at both ends, all the way down to operating-system level. Traffic-wise, each request also entails some bandwidth cost, which can add up if refreshes are occurring once every few seconds.
So a key design objective must be to increase the average refresh period and reduce the content per refresh, while maintaining a happy user experience. One optimization is a timeout: the refreshes cease when the system detects the user is no longer active, according to a Timeout (Chapter 17) mechanism. You also want to make sure each refresh counts; it’s wasteful to demand lots of updates if the network isn’t capable of delivering them. Thus, the browser script can do some monitoring and dynamically adjust the period so it’s at least long enough to cope with all incoming updates. Many of the Performance Optimization patterns can also be applied to Periodic Refresh—see the next section.
Decisions
How long will the refresh period be?
The refresh period can differ widely, depending on usage context. Broadly speaking, we can identify three categories of activity level:
- Real-Time interaction (milliseconds)
The user is actively interacting with the system, and his input relies on the server’s output—for example, a chat user needs to see what others are saying, a trader needs to see current prices, or a game player needs to see the state of the game. Here, the interval could be as low as a millisecond on a local machine or local network, or perhaps be 20-100 milliseconds on a global network.
- Active monitoring (seconds)
The user relies on the server state for work outside the system—for example, a security officer watches sensor displays for suspicious activity, or a manager occasionally watches the fluctuation of sales figures during a particular window of time. In some cases, timeliness may be critical, making subsecond responses desirable. In other cases, a few seconds is sufficient feedback.
- Casual monitoring (minutes)
Some applications are designed for the user to leave in a window or separate browser tab and view throughout the day. The information does not change often, and it’s no drama if the user finds out a little later. Prime candidates here are portals and RSS aggregators. Refresh periods of 10 minutes or more are often acceptable for such content. A manual “Refresh Now” mechanism is worth including where the refresh period is longer than a few seconds. It can relate to the entire application or to specific components.
Sometimes, the best solution uses multiple Periodic Refresh cycles in parallel, each with a frequency reflecting the user’s needs. An interactive wiki, for example, might update news headlines every 10 minutes, online statuses of other users every minute, and content being edited every second.
Real-World Examples
Lace Chat
Instant messaging (or “online chat”) applications pre-date the Web, and, unlike email and other services, web interfaces have never quite worked out, partly due to the fact that people don’t enjoy frequent full-page refreshes. Ajax makes it possible to avoid a complete refresh by pulling down messages with an XMLHttpRequest Call (Chapter 6). Only the new messages need to be sent in each periodic response. In fact, there are several applications under development. Lace Chat (http://socket7.net/lace/), for example, is only a proof-of-concept, but provides good evidence that web-based chat is feasible. Every few seconds the messages update to show any new messages other users may have entered. When you post a message, it’s also handled as an XMLHttpRequest Call.
Magnetic Poetry
Like a wiki, Magnetic Poetry (http://www.broken-notebook.com/magnetic/) involves a shared workspace (Figure 10-3). In this case, users move tiles through the space, and the application updates once a second to reflect the new space. As of version 1.7, the entire tile set is sent each time, but there’s the potential to compress the information by sending only recent tile positions. This enables two users to work on the area simultaneously, and one can even see a tile being dragged by a different user, like a low-frequency animation.
Claude Hussenet’s portal
Portals, by definition, display various kinds of information. And often that information is of a dynamic nature, requiring periodic updates. Claude Hussenet’s portal (http://www.claudehussenet.com/portal/Welcome.do) contains several portlets:
World news headlines (taken from Moreover—see http://moreover.com—and Yahoo! News—see http://news.yahoo.com). The server appears to generate these from an RSS feed.
New online articles appearing on TheServerSide.com and DevX.com. Again, these are taken from RSS feeds.
Customized stock portal. The server appears to maintain a record of all current stock prices and is capable of delivering those requested by the browser.
In each case, the information is volatile and needs to be periodically updated, as is typical for many portlets. Also characteristic of portal applications is the relatively long refresh period, 15 minutes in this case. Each portlet contains a manual refresh too, for immediate results.
Code Examples
Lace
Lace Chat (http://socket7.net/lace/) handles Periodic Refreshes to show all users’ chat messages.
The timer is set on startup to periodically call the
get()
function, which initiates
the XMLHttpRequest Call (Chapter 6) for new messages:
this.timerID = setInterval(function () { thisObj.get(true); }, interval);
get()
performs a
straightforward query of the server:
this.httpGetObj.open('POST', this.url, true); this.httpGetObj.setRequestHeader('Content-Type','application/x-www-form-urlencoded; charset=UTF-8'); var thisObj = this; this.httpGetObj.onreadystatechange = function ( ) { thisObj.handleGet(system); }; this.httpGetObj.send(param);
If the server has changed at all, the entire contents are returned. How does Lace know if the server has changed? Each time the server responds, it generates a hash for the contents. And when the browser next calls for a refresh, it includes the last hash as a parameter to the call. The server sends a full response only if the current hash differs from that specified by the browser:
[lib_lace.php] function getMessageHash( ) { ... return md5(implode(file(LACE_FILE))); } [lace.php] $_hash = getMessageHash( ); if ($_hash == $hash) exit; // no change exit($_hash.'||||'.getFileContentsRaw( ));
Code Refactoring: AjaxPatterns Periodic Time
The Basic Time Demo (http://ajaxify.com/run/time) requires the user to manually update the time display by clicking a button. We’ll perform a quick refactoring to re-fetch the time from the server every five seconds (http://ajaxify.com/run/time/periodicRefresh).
First, we’ll create the function that initiates the server calls. Here, we already have two functions like that, one for each display. So, let’s ensure we call both of those periodically:
function requestBothTimes() { requestDefaultTime(); requestCustomTime( ); }
Then, the Periodic Refresh is simply a matter of running the request every five seconds:
function onLoad( ) { ... setInterval(requestBothTimes, 5000); ... }
One more nicety: the function in setTimeout
begins to run only after the
initial delay period. So we’re left with empty time displays for
five seconds. To rectify that, requestBothTimes
is also called on
startup:
function onLoad() { ... requestBothTimes( ); setInterval(requestBothTimes, 5000); ... }
Now, the user sees the time almost as soon as the page is loaded.
Alternatives
HTTP Streaming
One of the forces for this pattern is that HTTP connections tend to be short-lived. That’s a tendency, not a requirement. As HTTP Streaming (Chapter 6) explains, there are some circumstances where it’s actually feasible to leave the connection open. Streaming allows for a sequence of messages to be downloaded into the browser without the need for explicit polling. (Low-level polling still happens at the operating system and network levels, but that doesn’t have to be handled by the web developer, and there’s no overhead of starting and stopping an HTTP connection for each refresh, nor of starting and stopping the web service.)
Related Patterns
Distributed Events
You can use Distributed Events (see later in this chapter) to coordinate browser activity following a response.
Fat Client, Browser-Side Cache, Guesstimate
A little work on performance issues can help make the system feel more responsive. Some of the performance optimization patterns help:
- Fat Client (Chapter 13)
Reduces the need for server processing by pushing functionality into the browser.
- Browser-Side Cache (Chapter 13)
Reduces queries by retaining query data locally.
- Guesstimate (Chapter 13)
Gives the user a sense of what’s happening without actually performing any query.
Submission Throttling
Submission Throttling (see the next pattern) also involves a periodic call to the server. The emphasis there is on uploading browser-side changes, whereas the present pattern focuses on downloading server-side changes. The two may be combined to form a general-purpose “synchronize” operation, as long as the update frequency is sufficient in both directions. This would improve performance by reducing the amount of overall traffic. The Wiki Demo (http://ajaxify.com/run/wiki) takes this approach.
Heartbeat
It’s inevitable that users will leave their browser pointing at web sites they’re not actually using. The rising popularity of tabbed browsing—now supported by all the major browsers—only exacerbates the problem. You don’t want to keep refreshing the page for idle users, so use Heartbeat (Chapter 17) to detect whether the user is still paying attention.
Metaphor
A movie is refreshed at subsecond intervals to provide the illusion of real-time activity.
Submission Throttling
⊙⊙⊙ Buffer, Queue, Performance, Throttle
Developer Story
Devi’s producing an Ajax chat web site and wants to transmit text as the user types. However, she knows some users will type faster than the system can cope with so she introduces a throttling mechanism that ensures no more than one message is uploaded every 200 milliseconds.
Problem
How can information be submitted to the server?
Forces
Information is often uploaded in bursts; e.g., a chat tool incurs many hits when a user becomes passionate about the topic, or a data entry tool incurs many hits when the user responds to some new information.
It’s difficult for the server to cope with lots of messages at once.
Browsers can only handle a limited number of pending XMLHttpRequest Calls at any moment.
Each message has overheads, such as packet headers and requires some processing at each stage of the browser/server round-trip.
Solution
Instead of submitting upon each JavaScript event, retain data in a browser-based buffer and automatically upload it at fixed intervals. As with many applications of buffering and throttling, the purpose is to strike a balance between responsiveness and resources. In most cases, it would be ideal to respond to every keystroke and mouse movement—so, for example, like a desktop application, tooltips could come directly from the server as the user mouses around. But that’s not practical due to bandwidth considerations, and possibly server constraints too. So to ease bandwidth—and to lessen server processing, call detail is accumulated and periodically uploaded.
Exactly what the buffer holds is application-specific, but there are two general styles: data buffers and command queues.
In the first buffer style, the buffer holds some data to be uploaded. Consider implementing a Suggestion (Chapter 14) interface like Google Suggest (http://www.google.com/webhp?complete=1&hl=en), which keeps showing information based on what’s been typed. The simplest thing would be to add an “onchange” listener to the text field and upload the buffer when each change occurs. However, what if you get a fast typist, one of those folks who revels in their “words per minute” metric? Banging out 100 words per minute means perhaps 10 characters per second, or a call every 100 milliseconds—feasible with a localhost web server, maybe workable on an intranet, but not scalable for most public Internet applications. So Suggestion systems, and more generally Live Command-Lines (Chapter 14), run a fixed-period timer. Every 500 milliseconds, say, the browser checks if there was a change since the last call, and if so, uploads to get some information and remembers what was uploaded to avoid doing so again. Effectively, the result for the current text field is cached, and the cache can only change on fixed periods.
A similar pattern might be used on a Live Form (Chapter 14), where the entire form is periodically uploaded to the server, even though some fields are blank, along with an indication of the user’s progress. The server can then use some intelligence to critique the form data, so as to provide live feedback.
The other style of buffer is a command queue. Here, Commands (see Gamma et al., 1995) are held in a queue, and the whole queue periodically uploaded to the server before being cleared. Some kind of serialization must take place, so the Commands must be represented as Strings and the Queue must ensure they can be pulled apart by the server; e.g., by separating them with a delimiter character. It’s up to the developers to agree on a protocol for defining how Commands are represented as Strings. For example, a wiki might use the string “Del Issues” to delete the Issues page. Another technique would be to store the commands as custom JavaScript objects and serialize them into JSON Messages (Chapter 9).
Submission Throttling might appear to optimize technical resources at the expense of usability, but can actually be a boon for users too. There’s a reason why the Windows and Apple operating systems don’t show copious logging details when booting. Technical users may appreciate the comprehensive output of a Linux boot sequence, but most users consider it overload—much more information than they actually care about. Throttling is a good way to prevent information overload in the browser, especially error messages that might come back because the user is only halfway through an edit.
Decisions
How will the server deal with incoming commands? Will all commands still be valid?
Submission Throttling is vulnerable to integrity issues, because synchronization is being deliberately downgraded. The universe will have moved on since the user submitted the original commands—the time will be different, new information may be available, existing information may have changed or been deleted, and other users may have executed commands in the meantime. All of these scenarios are unavoidable manifestations of the asynchronous nature of Ajax, and the design must take them into account.
The server needs to decide whether to process each incoming command as it’s quite possible some are no longer valid. For example, a stock purchase order might be refused on the basis that the price has just risen or because the server has since expired an initial quote it made to the client. Some of these decisions can be made by standard techniques such as atomic database transactions, but others might require some custom business logic. Furthermore, some business logic will be required to decide what to do with the rest of the Commands in a queue, should one Command in the middle fail. Sometimes, it’s okay to keep processing the rest and sometimes not.
How will buffer uploads be triggered?
The most obvious algorithm for upload sequencing is a timer. Every minute (say), the browser polls the buffer and makes the corresponding call sequence. Or, if nothing has changed, it might do nothing for another minute. If a timer is used, a decision must be made as to when it will trigger. First, the period is usually fixed, but does not have to be. It might be increased during times of known server activity, or even altered to respond to those constraints dynamically. Furthermore, it might be based on the user in question: service level can be tweaked by giving premium users shorter throttle periods.
A variant, usually more useful, is to cap the rate but immediately send the first command after a long wait. This helps a user who changes information only occasionally. Let’s say the throttle period is 30 seconds. With a standard, fixed-interval, algorithm, the following sequence occurs:
00 secs: system polls, no activity so no upload
30 secs: system polls, no activity so no upload
60 secs: system polls, no activity so no upload
65 secs: infrequent user does something; no upload yet
75 secs: infrequent user does something; still no upload
90 secs: system polls and uploads both pending commands
We can still cap the rate at 30 seconds, but upload a command immediately if there’s been no activity for the past 30 seconds:
00 secs: system polls, no activity so no upload
30 secs: system polls, no activity so no upload
60 secs: system polls, no activity so no upload
65 secs: infrequent user does something
65 secs: system notices and uploads command immediately
75 secs: infrequent user does something; this time, the command must wait since there was a recent call
95 secs: system polls, uploads if there was any further activity after 65 secs
Prioritization is another technique. A timer might be used for regular events, but with priority given to any critical commands. A Live Form (Chapter 14) might periodically upload the progress of an individual field, so the server can provide suggestions, for example. But as soon as the user proceeds to the next field, a call takes place immediately.
A further consideration is the user’s activity during upload. It’s wise to avoid uploading something the user is currently working on, especially if other users will see it and if they probably won’t be working on it much longer. So, one policy might involve uploading only after a period of idle activity.
How many buffers per browser application?
There’s no reason to have just one buffer for all commands. It’s possible to have several buffers running in parallel, providing you have considered the consequences of commands arriving in a different order to the user requesting them. Prioritization was already mentioned above, which would be one reason to have several buffers—higher-priority buffers being processed with greater frequency.
Here’s how a blog reader might use three buffers in parallel:
A low priority queue uploads comments submitted by the user. Throttle period: 15 seconds.
A medium priority queue pulls down requested feeds from the server. Throttle period: 3 seconds.
A high priority text buffer lets the user tag articles with a Suggestion mechanism. While it’s not necessary to upload upon each keystroke, any new information must be transferred quite rapidly. Throttle period: 0.2 seconds.
This example illustrates that it’s quite easy to run several buffers without threat to data integrity and without user confusion.
How long should the throttle period be?
Deciding on the throttle period requires some analysis of user needs. As the previous blog reader example demonstrates, there is a range of periods that might be required:
For background synchronization, the period can be a few minutes if resource constraints apply, although it should ideally be in the order of 10 seconds. If it is several minutes, users should be kept informed with appropriate Progress Indicators (Chapter 14), lest they quit and lose work before the upload kicks in. Ideally, there should be an Explicit Submission (later in this chapter) mechanism as well, to allow immediate saving.
At the other end of the spectrum, for low-level interaction while the user types and mouses around, the period must be in the order of 100 milliseconds.
Real-World Examples
Google Suggest
Google Suggest (http://www.google.com/webhp?complete=1) features Suggestions, so when you type “A,” the browser pops up a list of popular searches beginning with “A.” To prevent against excessive queries, Google Suggest (http://serversideguy.blogspot.com/2004/12/google-suggest-dissected.html) uses Submission Throttling.
Zuggest
Zuggest (http://www.francisshanahan.com/zuggest.aspx) is a Live Search (Chapter 14) showing Amazon results as you type (Figure 10-5). So type “Ab” and you’ll get results like “Basic Ab Workout for Dummies” and “Absolutely Fabulous.” The results are images as well as text, so it would be expensive to search for something you weren’t interested in. So, if you’re searching for “Absolutely,” it’s best to avoid searching for “Ab” and “Abso” and “Absolut” along the way, which is the kind of thing the Google Suggest algorithm would do.
To ensure searches are relevant, Zuggest applies a delay while typing. The assumption is that you’ll be typing at a rate of at least one character per second. Any time you hit a key, you’ll see a “Waiting until you’re done ...” message, and you’ll have a second to hit another key. If no key is pressed, the application assumes you were looking for the current term, and performs a remote call.
As explained in the Fat Client pattern, the wiki demo (http://ajaxify.com/run/wiki) throttles in a similar manner.
Gmail
Gmail (http://gmail.com/) has an auto-save feature that periodically uploads a message being composed.
Prototype framework
Prototype (http://prototype.conio.net/) offers a
reusable component, TimedObserver
, which performs Submission
Throttling. ListSomething (http://listsomething.com/), a search
engine for classified ads, utilizes TimedObserver
for its Live
Search.
Code Example: AjaxPatterns Assistive Search
The Assistive Search demo (http://ajaxify.com/run/assistiveSearch) throttles in a similar manner to Google Suggest and other Ajax search apps.
requestValidCategoriesLoop
runs repeatedly; the precise interval (in
milliseconds) is determined by the THROTTLE_PERIOD
constant (about 100
milliseconds). The last server query is always stored, and there’s
nothing to do if the current query remains the same as the previous
query. If there has been a change, the new query is submitted to the
server:
function requestValidCategoriesLoop( ) { if (query( )!=latestServerQuery) { vars = { queryType: "getValidCategories", queryText: escape(query( )) } ajaxCaller.get("categories.php", vars, onValidCategoriesResponse, false, null); latestServerQuery = query( ); } setTimeout('requestValidCategoriesLoop( );', THROTTLE_PERIOD); }
Related Patterns
Periodic Refresh
Periodic Refresh (see earlier) is somewhat the reverse of Submission Throttling: instead of periodically uploading user input, Periodic Refresh periodically downloads server state.
Progress Indicator
A common trend while performing a periodic update is to include a small Progress Indicator (Chapter 14), often as a Popup with a message such as a “Saving.”
Metaphor
Science-fiction writers have speculated that interstellar communication would work like this. Because the trip is so great, each imperial command must contain a big load of information—the inter-galactic emperor can’t just tell the colonials to “go fetch a kettle” ... (20 light-years later) . . . “now add some water” . . . .
Explicit Submission
⊙⊙⊙ Submit, Packet, Performance
Developer Story
Devi’s producing an Ajax chat web site. She decides text will only be transmitted after the user explicitly clicks a “Done” button.
Problem
How can information be submitted to the server?
Forces
It’s difficult for the server to cope with lots of messages at once.
Each message has overheads, such as packet headers, and requires some processing at each stage of the browser-server round-trip.
Users often need to manipulate a small work unit privately, and then upload it as a whole atomic unit to the server.
Solution
Instead of automatically submitting upon each browser event, require the user to explicitly request it; e.g., submit upon a button click. Typically, the user performs some work for a few seconds or minutes, and then clicks a button to tell the server. It’s a familiar pattern on the Web as it feels similar to standard form submission.
The most common example is a text field, which may be an input or textarea control. While it would be possible to transmit keystrokes as they happen, that’s often not desirable. In a wiki, for instance, imagine what would happen if a user deleted a paragraph in order to replace it. Any other user viewing the wiki would see the paragraph disappear! Furthermore, the history would reflect that transition state.
Where there’s only one input field, it sometimes makes sense
to rely on onchange
or onblur
events to detect that the change
has been made. But how about when there are several closely related
fields? Even then, some automatic submission is okay, to provide
some validation information, for instance. However, if the
information is important enough to cause a change to the server,
Explicit Submission is a good way to ensure the user intended what’s
been uploaded.
As well as some inconvenience, the downside of relying on the user to explicitly submit data is . . . what if the user doesn’t? Have you ever quit the browser and forgot to submit a form you were working on? The consequences may be minor for a query-and-report application, but for data entry applications, large amounts of work can be lost when the user forgets to submit it or doesn’t realize it’s necessary to do so. For that reason, Explicit Submission can sometimes be complemented by automated Submission Throttling. For instance, Gmail (http://gmail.com) will only send mail upon an Explicit Send command, but nonetheless has an Autosave feature that will periodically upload an in-progress message as a draft.
Decisions
How will the user request the submission?
The submission mechanism should ideally be similar to those in similar non-Ajax systems.
Buttons are one common idiom, with a generic label like
Submit, Done
, or Go!
. More meaningful names are usually
clearer, being specific to the task being conducted—for instance,
“Buy,” “Search,” or “Delete.” Buttons have the benefit of making
the Explicit Submission mechanism stupidly obvious: “I click the
button, the info’s submitted; I don’t click it, it’s not
submitted.”
Relying on a significant keystroke, usually Enter
, can also work in the right
context, and, by using the keyboard, it supports power
users.
Listening for the onblur
is another explicit technique, albeit with a submission mechanism
that is not apparent from the UI. The nice thing about onblur
is that the application can
continue submitting without interrupting the usual flow of events.
On a Live Form, for instance, users can force
a submission with Tab or Shift-Tab, which they would have hit
anyway to move out of the tab. It also means the user can click
somewhere else on the mouse to force a submission. The downside is
the risk of accidental submission. There’s also the question of
what the user can do to prevent a submission occurring, having
already begun typing something? Perhaps they can blank the field,
but clearly, communicating this to the user is not easy.
How will you deal with frequent submissions?
Explicit Submission leaves open the possibility of too many submissions
at once. This might happen because the user is working too fast,
or simply because he has grown impatient and begun banging on the
mouse button or the Enter
key.
A few coping strategies are as follows:
Limit frequency of submissions with Submission Throttling (earlier in this chapter). While that pattern emphasizes automated submissions, it’s still possible to throttle Explicit Submissions.
After a user has made a submission, use Page Rearrangement (Chapter 5) to remove the submission button.
Soothe impatient users with Progress Indicators (Chapter 14 and Guesstimates (Chapter 13).
Prevent excessive delays with Performance Optimizations like Predictive Fetch (Chapter 13).
Show the initial Submission control in a Popup (Chapter 15), and then close it upon submission. This is sometimes used for login forms.
No matter how you prevent multiple submissions, be sure to design the server so it can cope with them. RESTful Service (Chapter 9) helps here because it can handle such situations gracefully; e.g., by rejecting any invalid queries.
Real-World Examples
Lace Chat
Brett Stimmerman’s Lace Chat (http://www.socket7.net/lace/) is an Ajax chat application. Lace users type the entire message and then explicitly submit with a Say button. You can also hit Enter after typing the message. Most chat applications work this way. It’s a little more efficient, but the main benefit is actually for the user. It might be confusing to other users to see a partially constructed message, so the composer of that message should rectify any errors before posting the message.
The Fonz
"The Fonz" text adventure (http://www.mrspeaker.webeisteddfod.com/2005/04/17/the-fonz-and-ajax/) is a command-line game (Figure 10-7). In typical command-line fashion, you type something and submit the whole command at once. Interestingly, command lines don’t have to work that way—it’s feasible to provide hints or some validation support as the user types. The Assistive Search Demo (http://ajaxify.com/run/assistiveSearch/) illustrates this point.
A9
A9 (http://a9.com), as with many search engines, requires Explicit Submission before it searches for the user’s query (Figure 10-8).
Code Refactoring: AjaxPatterns Form-Based Sum
The Basic Sum Demo (http://ajaxify.com/run/sum) illustrates
Explicit Submission. In the basic case, there are some input fields
and a button, just sitting in a div
:
<div> <div id="figure"> <input type="text" class="figure" id="figure1" name="figure1" size="3" value="4"/><br/> <input type="text" class="figure" id="figure2" name="figure2" size="3" value="6"/><br/> <input type="text" class="figure" id="figure3" name="figure3" size="3" value="" /><br/> </div> <input type="button" id="addButton" value="Add" /> </div>
The button is configured to submit the form when it is clicked, an example of Explicit Submission:
$("addButton").onclick = function( ) { submitSum( ); }
We now refactor to an alternative form of Explicit Submission, in which a standard form (http://ajaxify.com/run/sum/form) makes the submission. It’s still an “Ajax submission” involving no page refresh, but it leverages the standard form mechanism. There are two reasons you might do this in real life: it’s a step towards graceful degradation, since non-JavaScript browsers will require the data to be held in a standard form; it will “feel” more like a form to the user—e.g., the Enter key will automatically submit, and any CSS styling for forms will apply to it.
The initial HTML has been wrapped by a form
tag. With standard JavaScript
enabled, the action URL makes no difference because it will never be
accessed, but if we want, we could point it to a conventional
server-side script that would process the form in the event that
JavaScript is turned off. The regular button
control has been replaced by a
submit
button:
<form action="http://ajaxify.com/run/sum/form/"> <div id="figure"> <input type="text" class="figure" id="figure1" name="figure1" size="3" value="4"/><br/> <input type="text" class="figure" id="figure2" name="figure2" size="3" value="6"/><br/> <input type="text" class="figure" id="figure3" name="figure3" size="3" value="" /><br/> </div> <input type="submit" id="addButton" value="Add" /> </form>
The script hooks into onsubmit
, which will be called when the
new submit
button is clicked. It
arranges for an XMLHttpRequest
submission via submitSum( )
, then
returns false
to prevent a
conventional form submission and page refresh.
$("sumForm").onsubmit = function( ) { submitSum( ); return false; }
Alternatives
Submission Throttling
Submission Throttling (see earlier) talks about automated, periodic submissions, where the present pattern is about the user forcing submissions to take place. The patterns can certainly be combined. Consider text editors that often harden their explicit Save mechanisms with automated backup. An Ajax App can use automated submissions, but allow for manual intervention when the submission must take place NOW.
Related Patterns
Live Form
Live Forms (Chapter 14) often use onblur
and onfocus
events, a subtle type of
Explicit Submission.
Progress Indicator
Follow up submissions with a Progress Indicator (Chapter 14) to give some feedback.
Metaphor
An author drafts each chapter of a book in entirety before submitting it for editorial review.
Distributed Events
⊙ Events, Feed, Listener, Messaging, MVC, Notification, Observer, PublishSubscribe, RSS, Refresh, Semantic, Synchronise, Synchronize, Update, WebFeed
Developer Story
Devi’s producing a web app for auditors at a financial institution, aiming to highlight substantial transactions. Relevant transactions are already published on the enterprise messaging system, and Devi transforms it into an RSS feed on the web server. The browser script then checks the feed every few seconds, and updates the view whenever it detects a substantial transaction occurred.
Problem
How do you decouple code in a complex application?
Forces
Ajax Apps involve at least two tiers: a browser tier and a web server tier. In practice, the web server tier is often dependent on further tiers and external systems.
Each tier can be quite complicated, containing many stateful entities (objects, HTML controls, or regular variables).
The state of all these entities must often be synchronized, in order to keep users and external systems up-to-date. The synchronization needs to occur within a tier as well as across tiers—for example, an HTML table needs to change whenever browser-side user preferences change, but also whenever the server-side database changes.
Keeping all these objects synchronized can become complex—there are about n2 possible message paths from one object to another.
Solution
Keep objects synchronized with an event mechanism. This is a classic software pattern applied to Ajax, related to the Observer (Gamma et al., 1995) and “Publisher-Subscribe” (Buschmann et al., 1995) patterns, and also a key feature in the classic “Model-View-Controller” architecture. The DOM already provides an mechanism for low-level events, but the events discussed here are more semantic in nature; i.e., related to business and application concepts such as “account deleted.” Note that the term “event” is used in a broad manner to mean any subscription-based approach that alleviates the need for direct calls from source to destination. Any publish-subscribe messaging mechanism falls under this definition.
Here’s a scenario to motivate the concept of events. Say you have 10 objects with inter-dependent states. That is, when one object changes, any number of the other nine must change accordingly. The naive implementation would endow each object with an understanding of the other nine objects. Each time it’s changed, an object would then tell each other object how to update. Each object now knows the other nine intimately. When one changes, the other nine must be updated—a major blow to encapsulation.
As with many programming problems, you can create a better solution by adding another layer of indirection—in this case, an event mechanism. Let each object broadcast changes instead of directly telling others how to respond. The changes should generally occur in semantic terms—rather than saying “someone’s clicked me,” an object should say “counting module has begin,” or, “transaction has successfully completed.” And let any object register to be notified whenever a message like this occurs. For larger systems, thinking in terms of events is easier as it breaks down the synchronization logic. You have one simple task to make objects broadcast events whenever they occur. And you have a separate task to decide how objects should actually respond to events, if they care about them at all.
On the Web, this pattern can be applied in different ways:
- Server-to-browser
Page elements can be kept in sync with server objects.
- Browser-to-browser
Page elements can be kept in sync with each other.
- Browser-to-server
Server objects can be kept in sync with each other.
- Server-to-server
Server objects can be kept in sync with each other.
Browser-to-server and server-to-server are both feasible, but beyond the scope of the Ajax Patterns because they are more about server-side architecture.
As we saw in Chapter 7, JavaScript offers some event-handling support, but there’s no generic event API. To use distributed events then, you’ll need to roll your own event system, which is easier than it sounds. Server-to-Browser propagation works something like this. There’s a Server Event Manager, with two responsibilities. First, it runs a Periodic Refresh (see earlier in this chapter) or participates in HTTP Streaming (Chapter 6) to check if any server activity has occurred. Second, it offers an event propagation facility so that interested browser entities can register to discover any changes. When a server change occurs, the Server Event Manager constructs an event object based on the server’s output and passes it any interested listener. To do this, the manager must retain a collection of listeners and the event types they are listening to. The minimal set of services would then be:
addListener (eventType, listener)
removeListener (eventType, listener)
These listeners can be callback functions, like the callback
function used by XMLHttpRequest
.
Or, they can be objects that contain a function with a standard
callback name for the event type being registered, such as onUpdate( )
.
With Browser-to-Browser propagation, you can also have a Central Server Manager to accept events and notifications. Alternatively, each object can be responsible for creating and propagating events specific to itself. That is, each object capable of generating events needs to allow other objects to register themselves for the events.
Observer is a special case of this pattern that arises frequently. The events are not user actions but change notifications. Event listeners are observing an object and responding to its state. Often, it’s used to keep state in sync. An HTML table, for example, can render the latest state of a timetable object on the server side.
Finally, note that this pattern is somewhat speculative and open-ended, but the main purpose should be clear: to add a layer of intermediation so that objects can encapsulate their own responses to system activity, rather than being told what to do by other objects.
Decisions about Distributed Events
Will you publish a history or just the current state?
Given the nature of HTTP, server data must be retrieved periodically—the server can’t directly call the browser when something happens. If the server exposes only the current state, there are two problems:
- Change detection
The browser can only infer a change has occurred by comparing the previous value, or perhaps by using a version ID or timestamp.
- Intermediate states
The browser will miss values that occurred between updates.
Neither of these are showstoppers, so exposing only the current state may well be feasible. The alternative is to publish a history of states along with timestamps. That’s more work to output and parse, and it also requires some form of storage on the server. If you are following that option, it’s worth formatting the changes using a standard feed-based approach such as RSS or Atom. You’ll benefit from the abundance of libraries for both browser and server. As a bonus, the service will be generic enough to be used by external clients, if that’s a requirement.
For observer-style events, will you propagate the details of what’s changed, or just point to the object that’s changed?
Often, events concern an object that’s changed state. Sometimes, you only need to propagate a pointer to the object; the recipient will then interrogate the object as required. Other times, you need to send the change itself. The latter approach is useful for functionality that requires not just the object’s current state, but the nature of the change. For example, imagine you have an auditing function that logs whenever a budget balance has been increased. It will be much easier if the change event indicates that an increase has occurred. Otherwise, it will have to manually compare the budget against its previous state. For server-to-browser events, indicating the change may be better for performance since it may alleviate a follow-up call to the server.
What information will accompany the event notification?
When an event occurs, there’s usually several pieces of information to pass to listeners—for example, a source object (an object that’s just changed), the nature of the change or action that’s occurred, and meta-information such as event time and unique ID. You can pass this information in different ways:
- String message
Pass the information as a single string.
- Single event object
Pass in a single event object containing attributes for each piece of information.
- Parameter list
Pass the information as separate parameters.
Each style has its strengths. A string message is the most flexible approach and has the benefit of being a portable format that will work on the server as well. Unfortunately, a string message must often be parsed and formatted to convert to and from useful JavaScript values. A single event object is easier to manipulate and, like a string message, can usually be extended without breaking existing code—the callback function still takes a single value. You can create a factory function to create the event and expose properties, so its interface can be made explicit if you so desire. Finally, a parameter list makes for a cleaner callback function implementation, since you don’t have to extract variables from a wrapper object. However, a long list of parameters is cumbersome and difficult to maintain.
Will events be processed synchronously or asynchronously?
The simplest way to handle events is synchronously. As soon as something happens, the event manager immediately notifies all interested parties. All this happens in the same thread of execution, so each event handler becomes a bottleneck—the main program flow that triggered the event won’t be able to proceed until each event handler has executed.
If some event handlers are slow, you can get more stable performance by handling events asynchronously. Here, the manager maintains a collection of pending events. Each time a new event arises, it simply adds it to the collection and returns—a very quick operation. Using a repeating timer (see Scheduling [Chapter 7]), the manager periodically pulls off pending events and notifies listeners.
There are various decisions involved in asynchronous event handling. First, what sort of collection do you use? A queue is most common, to ensure that events are handled in the order they arise. But sometimes a stack is more appropriate, so that if the manager falls behind, at least the most recent events will have been handled. Another decision is scheduling of the event handler, the object that picks events off the collection. The simplest style is a pure repeating timer, but if the handling takes too long (longer than the timer interval), you’ll end up with multiple processes picking off events. One way to prevent this situation is to have the event handler monitor its own progress and cease activity after a certain time has elapsed.
Real-World Examples
ActiveMQ Servlet Adaptor
ActiveMQ (http://activemq.codehaus.org/) is an open source implementation of Java Messaging Service (JMS) (http://java.sun.com/products/jms/), an official Sun standard for enterprise messaging. As such, it provides a way to pass Java objects and strings between different processes, and includes a publish-subscribe mechanism allowing a process to subscribe for all messages in a particular “topic.”
Normally, the processes run server side, but using a servlet adaptor, ActiveMQ effectively gives the web app, through JavaScript, full ability to send and receive messages.
MapBuilder
MapBuilder (http://mapbuilder.sourceforge.net) is a framework for mapping web sites, heavily influenced by MVC. The model holds application data such as current maps, positions, and dimensions. The configuration process wires each model to a number of interested widgets, all of which receive notifications when the model has changed.
Dojo Events Library
Dojo (http://dojotoolkit.org/download) is a comprehensive framework aiming to simplify JavaScript development. One thing it does is enhance JavaScript’s standard event management. This includes publish-subscribe functionality. You register one or more functions as publishers and one or more functions as subscribers. When a publisher function is called, all of the subscriber functions will also be called.
LivePage Library
LivePage (http://twisted.sourceforge.net/TwistedDocs-1.2.0/howto/livepage.html), mentioned in HTTP Streaming (Chapter 6) examples, is a framework based around Distributed Events.
Code Refactoring: AjaxPatterns Distributed Events Wiki Demo
The Basic Wiki Demo (http://ajaxify.com/run/wiki) has a single callback function that serves two purposes: to parse the incoming message and to display it to the user. That’s okay for a simple application, but what if we want to scale up the display operation by displaying different messages in different ways or performing some action when a single message has changed? It won’t be a great surprise that Distributed Events are one way to make the browser script more modular, and this refactoring shows how.
Refactoring to an event mechanism
The first refactoring lays the groundwork for a
richer message handling by introducing an event mechanism. There
are some minor user-interface differences, for coding convenience.
For example, instead of a single “synchronize” point, downloading
and uploading are split into independent timing mechanisms;
there’s no more background color change while waiting for a
message to upload; and a One-Second Spotlight
(Chapter 16) effect now
occurs when a message has updated, to compensate for the loss of
color change. Also, note that a different version of ajaxCaller
is used, which allows a
callback object to be specified in addition to a callback
function.
A model object has been introduced to track the state of each message and to play the role of an event manager, notifying listeners of changes. One type of listener receives notification of any new messages. The other type receives notification of updates to a specific message. New message listeners are held in a single array. Update listeners are held in an array of arrays, with all subscribers to a particular message held in an array that’s keyed on the message ID:
newMessageListeners: new Array( ), messageUpdateListenersById: new Array( ), addNewMessageListener: function(listener) { this.newMessageListeners.push(listener); }, addMessageUpdateListener: function(messageId, listener) { var listeners = this.messageUpdateListenersById[messageId]; listeners.push(listener); },
Notification then works by iterating through the collection of relevant listeners:
notifyNewMessageListeners: function(newMessage) { for (i=0; i<this.newMessageListeners.length; i++) { this.newMessageListeners[i](newMessage); } }, notifyMessageUpdateListeners: function(updatedMessage) { var listenersToThisMessage = this.messageUpdateListenersById[updatedMessage.id]; for (i=0; i<listenersToThisMessage.length; i++) { listenersToThisMessage[i](updatedMessage); } }
How do these events arise? The model object must be started manually and will then periodically poll the server:
start: function( ) { this.requestMessages( ); setInterval(this.requestMessages, DOWNLOAD_INTERVAL); } ... requestMessages: function( ) { ajaxCaller.getXML("content.php?messages", messageModel.onMessagesLoaded); },
As before, the server provides an XML Message (Chapter 9) describing all messages. The model steps through each message in the XML, constructing an equivalent JavaScript object. If the message ID is unknown, the new message listeners are notified, and an array of update listeners is also created for this new message. If the message differs from the current message with the same ID, it’s changed, so all the update listeners are notified. Recall that the message update notification is fine-grained: only listeners to a particular message ID are notified; hence the extraction of a message-specific list of listeners.
onMessagesLoaded: function(xml, callingContext) { var incomingMessages = xml.getElementsByTagName("message"); for (var messageCount=0; messageCount<incomingMessages.length; messageCount++) { var messageNode = incomingMessages[messageCount]; var content = this.getChildValue(messageNode, "content"); content = (content==null ? "" : unescape(content)); var incomingMessage = { id: this.getChildValue(messageNode, "id"), lastAuthor: this.getChildValue(messageNode, "lastAuthor"), ranking: this.getChildValue(messageNode, "ranking"), content: content }; var currentMessage = this.messagesById[incomingMessage.id]; if (!currentMessage) { this.messageUpdateListenersById[incomingMessage.id]=new Array( ); this.notifyNewMessageListeners(incomingMessage); } else if (!this.messagesEqual(incomingMessage, currentMessage)) { this.notifyMessageUpdateListeners(incomingMessage); } this.messagesById[incomingMessage.id] = incomingMessage; } }, getChildValue: function(parentNode, childName) { var childNode = parentNode.getElementsByTagName(childName)[0]; return childNode.firstChild == null ? null : childNode.firstChild.nodeValue; }, messagesEqual: function(message1, message2) { return message1.lastAuthor == message2.lastAuthor && message1.ranking == message2.ranking && message1.content == message2.content; }
A new messagesDiv
object
has also been created to encapsulate the message-handling logic.
On startup, it subscribes for notification of new messages. For
each message, it performs a similar function to what was
previously done on each update: it creates all the message
information and appends to the page, along with a newly introduced
visual effect (courtesy of Scriptaculous; see http://script.aculo.us).
start: function( ) { messageModel.addNewMessageListener(this.onNewMessage); }, ... onNewMessage: function(message) { var messageArea = document.createElement("textarea"); messageArea.className = "messageArea"; messageArea.id = message.id; messageArea.serverMessage = message; ... messageDiv.appendChild(lastAuthor); messageDiv.appendChild(messageArea); ... $("messages").appendChild(messageDiv); Effect.Appear(messageDiv); ... }
The messageDiv
has
another responsibility: it must update the display when a message
has updated. Thus, it registers itself as a listener on each
message. The easiest way to do this is upon adding each new
message:
onNewMessage: function(message) { ... messageModel.addMessageUpdateListener(message.id, function(message) { var messageDiv = $("messageDiv" + message.id); var lastAuthor = messageDiv.childNodes[0]; var messageArea = messageDiv.childNodes[1]; if (messageArea.hasFocus) { return; } lastAuthor.innerHTML = message.id + "." + "<em>" + message.lastAuthor + "</em>"+"." + message.ranking; messageArea.value = message.content; Effect.Appear(messageDiv); }); },
Compared to the previous version, we’re now only redrawing a
message when it’s actually changed. Using an event mechanism has
helped to separate the logic out. Now, it’s the messageDiv
itself that decides how it
will look after a message comes in, which is much more sane than
the message-receiving callback making that decision.
Introducing a watchlist
The refactoring above wouldn’t be very useful if we stopped with an event mechanism. Good for your work experience perhaps, but we haven’t yet added any functionality to justify the effort; it’s basically the same application as before. Not to worry Watchlist Wiki Demo (http://ajaxify.com/run/wiki/events/watchlist) to the rescue! A new watchlist monitors interesting messages, so that when a message you’re watching is updated (by you or someone else), the watchlist will add a summary line.
To start with, the HTML now includes a watchlist table:
<div id="summary"> <table id="watchlist"> <tr> <th>Author</th> <th>Message</th> </tr> <tbody id="watchlistBody"></tbody> </table> </div>
Which messages are in your watchlist? That’s determined by a new checkbox control, one per message:
onNewMessage: function(message) { ... var watching = document.createElement("input"); watching.type = "checkbox"; watching.messageId = message.id; watching.onclick = onWatchingToggled; ... }
When the user wants to watch a message, she selects its checkbox. A single function updates the watchlist for all chosen messages. Remember that message update events are fine-grained, so we need to ensure this callback is registered to receive notifications for all the chosen messages and nothing else. So when a user deselects a message, we’ll unregister the function as a listener on that message. Note that this functionality necessitated the creation of an function to unregister listeners, which was never required in the previous version.
function onWatchingToggled(event) { event = event || window.event; var checkbox = event.target || event.srcElement; if (checkbox.checked) { messageModel.addMessageUpdateListener(checkbox.messageId, onWatchedMessageUpdate); } else { messageModel.removeMessageUpdateListener(checkbox.messageId, onWatchedMessageUpdate); } }
onWatchedMessageUpdate
will now receive notification of any new messages that are being
watched. It simply adds a summary row to the table and runs a
visual effect:
function onWatchedMessageUpdate(message) { var summary = message.content; if (summary.length > 35) { summary = summary.substring(0, 15) + "..." + summary.substring(summary.length - 15); } var row = document.createElement("tr"); var authorCol = document.createElement("td"); authorCol.className = "authorSummary"; authorCol.innerHTML = message.author; row.appendChild(authorCol); var contentCol = document.createElement("td"); contentCol.className = "contentSummary"; contentCol.innerHTML = summary; row.appendChild(contentCol); if ($("watchlistBody").childNodes.length > 0) { $("watchlistBody").insertBefore(row, $("watchlistBody").childNodes[0]); } else { $("watchlistBody").appendChild(row); } Effect.Appear(row); }
We now have two independent functions that receive notifications of new messages arriving from the server. Each can use the information however it pleases. This is a much more scalable approach than having the server message recipient dictate how the browser should respond.
Related Patterns
Periodic Refresh, HTTP Streaming
For server-to-browser propagation, Periodic Refresh (see earlier) or HTTP Streaming (Chapter 6) is required.
RESTful Service
Distributed Events usually involve a browser element observing a server-side entity. REST is ideal for this purpose as it provides a simple, standard way to exposes server state.
XML Data Island
If the server responds with XML and you need to retain state locally—e.g., to track differences—an XML Data Island (Chapter 11) would be useful. Under some technologies illustrated in that pattern, XML Data Islands allow for automated updates—when the data island changes, then a control is updated, and vice versa.
Metaphor
The old newspaper analogy still works. People can subscribe to any number of newspapers, and each newspaper can have any number of subscribers. The algorithm does not explicitly mention any particular subscriber; rather, when a newspaper comes out, it simply loops through each subscriber and sends a copy to each of them.
Cross-Domain Proxy
⊙⊙⊙ Aggregator, Fusion, Mash-Up, Mashup, Mediator, Mix, Proxy, Tunnel
Developer Story
Dave’s working on a job-hunting web site and wants to “mash up” content from various other sites. Each job ad is accompanied by a review from a corporate forum web site, recent company news, and a stock ticker. To get these details into the browser, the server queries several sites and exposes their content as a web service that the browser can query.
Problem
How can you augment your application with services available elsewhere on the Web?
Forces
There’s a lot of data and useful services on the Web. Much of it is freely available. It’s usually more effective to leverage that content than to replicate it yourself.
The “same-origin policy” constrains most Web Remoting (Chapter 6) techniques, meaning that the browser script can only talk to the server from whence it came, and not to anyone else.
External servers often use protocols and message formats that would be difficult for browser-side scripts to process. They may also require authentication, and such details should not be exposed in the browser.
To comply with service agreements, web site owners often have to control and monitor all traffic to and from their web service, which is impossible if their server is being bypassed.
Solution
Create proxying and mediating web services to facilitate communication between the browser and external domains. As explained in XMLHttpRequest Call (Chapter 6), the same-origin policy means the browser script can only talk to the server from whence it came, which is the “base server.” Hence, any communication with external domains must go via the base server.
The simplest form of proxy is a dumb Web Service (Chapter 6) that simply routes traffic between browser and external server. The service can accept the remote URL as a parameter. That URL will then be accessed synchronously, and the service will output its response. All this follows the Proxy pattern (Gamma et al., 1995).
A cleaner type of Cross-Domain Proxy is more closely based on the Facade or Adaptor pattern (Gamma et al.). In this case, the base server presents the interface that’s most suitable to the browser script, and performs whatever manipulation is necessary to interact with the external script. For example, the external server might present a complex, over-engineered, SOAP-based RPC Service, but you want the browser to deal in simple Plain-Text Messages (Chapter 9). The likely solution is to present the plain-text Web Service, dress up requests into SOAP messages, and undress responses into Plain-Text Messages.
The Facade/Adaptor approach is generally better for two reasons. First, it keeps the client simple, protecting it from dealing with the details of the protocol used to communicate with the external server. Second, it’s more secure: the first approach will allow any client to call out to any server they feel like, whereas the second approach will allow you to exert much more control over what kind of communication takes place.
A Cross-Domain Proxy is implemented using some form of HTTP client library to access the remote server. Generally, the connection should be quite straightforward—specify the URL and grab the response as a string. However, it might get more complex if the remote web service relies on cookies and authentication, parameters that the proxy might have to pass on from the Ajax request that initiated the call.
Decisions
What external content will be accessed?
In theory, anything you can do with a browser can theoretically be accomplished by an automated HTTP client. However, there are likely to be legal constraints, and technical challenges too if there is serious usage of JavaScript or browser plugins. Furthermore, your script will be vulnerable to changes in the site layout, and many content providers have deliberately performed subtle changes to prevent automated access. Thus, relying on the ever-increasing collection of public APIs and structured data would make a better choice.
You can find a collection of public APIs at http://wsfinder.com. Some popular APIs include Amazon, Delicious, EvDB, Flickr, Google Maps, Google Search, Technorati, and Yahoo Maps.
How will you connect to the external server?
If you’re scraping content directly from a public web site, you’ll need to use the underlying web protocol, HTTP. And even if you’re talking to an API, you’ll probably be communicating with HTTP anyway. In some cases, API publishers will provide code to access the content, rather than just publishing the specification. Also, with more complex web services protocols like SOAP, you don’t have to write a lot of low-level code yourself. In many cases, though, the easiest thing to do is talk HTTP and manually format and parse messages yourself.
Many scripting languages feature built-in libraries to communicate in HTTP and are often well-equipped for Cross-Domain Proxy implementations due to strong support for regular expression manipulation. A PHP example is featured in the code example that follows shortly.
In the Java world, for instance, the standard API provides some relatively low-level support for HTTP clients, but you can use some of the web site testing libraries to quickly extract content from external sites. HttpUnit is a good example, and it also has some support for JavaScript manipulation. To grab some content:
import com.meterware.httpunit.WebConversation; import com.meterware.httpunit.GetMethodWebRequest; import com.meterware.httpunit.WebRequest; public class PatternSorter { public String getContent(String url) { try { WebConversation wc = new WebConversation( ); WebRequest request = new GetMethodWebRequest(url); String response = wc.getResponse(request).getText( ); } catch(Exception ex) { throw new RuntimeException("Could not get content from " + url, ex); } } public static void main(String[] args) { System.out.println("http://ajaxpatterns.org"); } }
How will you deal with errors and delays in accessing the service?
At times, there will be errors accessing an external web service. Think twice before attributing blame because the problem might be at your end, or somewhere along the network. This has implications for any error messages you might show users. Ideally, you should be able to detect that an error has occurred, and then log the details for immediate attention. In responding to the user, you have several options:
Ensure you have a good server-side framework in place to detect errors, so they’re not directly—ignorantly—passed on to users. In addition, be sure to detect timeouts and handle them proactively; e.g., respond with an appropriate error message.
In some cases, you will simply have to admit failure and show an error message to users. If possible, suggest an alternative or tell them when they should try again.
Provide reduced functionality.
Switch to a prearranged substitute web service.
Rely on cached results, if they’re still relevant.
Under what licensing terms will you access the remote service?
The legals of invoking Web Services (Chapter 6) are tricky, vaguely-defined, and always evolving. Some services are open for public use, others require you hold an API key, and others are critical enough to warrant authentication via digital signatures. In each case, there are usage terms involved, even if they’re not explicit, and you will need to investigate issues such as the authentication mechanism, the number of queries per day, how the data will be used, server uptime, and support level.
Real-World Examples
WPLicense
The WPLicense Wordpress plugin (http://yergler.net/projects/wplicense) presents the blogger with some forms to specify their preferred license statement, based on the Creative Commons model. The server is acting as a middleman between the browser and the Creative Commons API (http://api.creativecommons.org/rest/1.0/).
Housing Maps (Craigslist and Google Maps)
Housing Maps (http://housingmaps.com) is a mashup between Google Maps (http://maps.google.com) and Craigslist (http://craigslist.com), a community-oriented classifieds advertising web site. What does that mean? It means you get to see a list of advertised homes on one side and view a map of those homes one the other side, using the familiar Google Maps thumbtacks to pinpoint the locations of the advertised homes. (See Figure 10-11.)
A few features:
Click on a thumbtack and you’ll see a Popup (Chapter 15) balloon appear, showing a summary of the classified ad: price, terms, address, and photos. The balloon contains a hyperlink to the ad at Craigslist
Change a category (e.g., price) and you’ll see both the thumbtacks and home lists update. Change the city and you’ll see the map change as well.
You can pan and zoom as on Google Maps.
Cross-Domain Proxy is used to grab housing data from Craigslist, while the map images are fetched directly from Google’s server into the browser.
Bill Gates Wealth Clock
This was not Ajaxian, but noteworthy as the first prominent mashup application and a precursor to the Ajaxian Cross-Domain Proxy pattern. Web guru Philip Greenspun mixed data from several sources to produce a “wealth clock” showing Bill Gates’ worth at any time (http://groups-beta.google.com/group/nf.general/browse_thread/thread/bc7fc7bfab19937f/88fa32f2cf6dd6bb). This is no longer working, unfortunately (http://rhea.redhat.com/bboard-archive/webdb/0006ad.html), but the code is still available (http://philip.greenspun.com/seia/examples-basics/wealth-clock.tcl.txt). See Figure 10-12.
The price per stock was extracted from Yahoo! Finance. The number of stocks in Gates’ portfolio came from Microsoft reports (it was not automatically extracted). And the U.S. population came from the U.S. Census Bureau.
Here’s what Greenspun said back in 2001 (http://philip.greenspun.com/teaching/teaching-software-engineering):
(M)any of the most interesting and challenging problems of the coming decades will center around the Internet and Web as a distributed computing environment. An “island” site such as amazon.com or photo.net is probably less representative of the future than http://www.webho.com/WealthClock (Bill Gates Personal Wealth Clock).
Another prominent usage of Cross-Domain Proxying in “Web 1.0” was meta-searching, as performed by crawlers such as MetaCrawler.com (http://metacrawler.com). Again, all the work was done in the server, and the browser response was usually opened while the results came back to the server, so at least some information could be shown while requests were still out.
CPaint library
CPaint (http://cpaint.sourceforge.net/:) is a Web Remoting (Chapter 6) library. A special “proxy” URL can be established (http://cpaint.sourceforge.net/doc/frontend.class.cpaint.set_proxy_url.html), pointing to a proxy on the originating server, so that remoting to external domains has virtually the same API as to the originating server.
Code Example: WPLicense
See the Code Example in Live Form (Chapter 14) for a full background on the WPLicense’s license selection process, but here’s a quick summary:
A field lets the user select a license type (e.g., Creative Commons versus Public Domain versus Sampling).
Once the license type is chosen, it’s immediately sent to the server, and the server responds with a form consisting of questions relevant to this particular type. If the license is Creative Commons, for example, one of the questions is “Allow commercial uses of this work?”
Each time the user changes one of the license options, the server updates some hidden fields. These fields will be uploaded to the server, to be persisted when the user eventually submits the form.
What all this glosses over is the Cross-Domain Proxy pattern
that goes on behind the scenes. At each of these three stages, the
server is actually interacting with CreativeCommons.org via its
public API (http://api.creativecommons.org/rest/1.0/),
which you can read about at http://api.creativecommons.org. The plugin
provides a clean separation between web server logic and
cross-domain mediation: a separate PHP file (ccwsclient.php
) hosts several API facade
operations, accepting and returning data structures in standard PHP
style. This client in turn delegates all the XML infrastructure
stuff to a third-party library MiniXML, at http://minixml.psychogenic.com.
Let’s now zoom in on the three steps.
1. Retrieving license types
The server doesn’t have the license types hardcoded.
Instead, it retrieves them via a web service. This is the core of
the Cross-Domain Proxy functionality—the PHP function fs
will retrieve content from the
specified URL:
$WS_ROOT = "http://api.creativecommons.org/rest/1.0/"; ... $xml = file_get_contents($WS_ROOT);
In fact, you can see exactly what’s pumped into $xml
by visiting http://api.creativecommons.org/rest/1.0/,
and then choose View Page Source. Alternatively, use a
command-like application such as curl (curl
http://api.creativecommons.org/rest/1.0/),
which yields the following XML (reformatted):
<licenses> <license id="standard">Creative Commons</license> <license id="publicdomain">Public Domain</license> <license id="recombo">Sampling</license> </licenses>
Once we know what license types are available, it’s a matter of transforming the XML into an HTML selector. XSLT could be used here, but it’s just as easy to do it manually:
foreach($license_classes as $key => $l_id) { echo '<option value="' . $key . '" >' . $l_id . '</option>'; };
2. Retrieving license questions
Presenting the questions gets interesting, because the server must now present questions and accept answers without the programmer knowing in advance what those questions will be.
Browser requests for license questions invoke a URL that depends on the user’s chosen license type. If the license type is Creative Commons, which has the ID “standard,” the URL is http://api.creativecommons.org/rest/1.0/license/standard/. Visit that URL, and you’ll see the questions and possible answers. For example:
<field id="derivatives"> <label xml:lang="en">Allows modifications of your work?</label> <description xml:lang="en">The licensor permits others to copy, distribute and perform only unaltered copies of the work, not derivative works based on it.</description> <type>enum</type> <enum id="y"> <label xml:lang="en">Yes</label> </enum> <enum id="sa"> <label xml:lang="en">ShareAlike</label> </enum> <enum id="n"> <label xml:lang="en">No</label> </enum> </field>
Equipped with all that information about each question, the server must now transform it into an HTML selector. Ultimately, a loop is used to traverse the entire data structure and generate a selector for each field:
foreach ($fields as $f_id=>$f_data) { $result .= '<tr><th><nobr>' . $f_data['label'] . '</nobr></th><td>'; // generate the appropriate widget if ($f_data['type'] == 'enum') { $result .= '<select class="lic_q" id="'.$f_id.'" lic_q="true" size="1">'; foreach ($f_data['options'] as $enum_id=>$enum_val) { $result .= '<option value="'. $enum_id . '">' . $enum_val . '</option>'; } // for each option ...
As explained in the Live Form (Chapter 14) discussion, this new
form HTML is directly placed onto a div
.
3. Handling user answers
The Creative Commons’ issueLicense
web service accepts XML
input of all the license criteria, and then outputs a document
containing a URL for the license criteria, along with some other,
related information. In a similar manner to the previous code, all
of this information is transformed into HTML. This time, it’s used
to populate several fields, rather than generate any new input
fields.
Alternatives
On-Demand JavaScript
A fairly old cross-domain technique is to use On-Demand JavaScript; see the discussion of Cross-Domain Loading for that pattern in Chapter 6. The main benefit over Cross-Domain Proxy is reduced resources—the base server is bypassed, so there’s no bandwidth or processing costs involved. However, there’s a major constraint: the server must expose a suitable script to fetch, because it’s not possible to just extract arbitrary information from a server. Additional problems include lack of server-side logging, inability to reach services that require authentication, and the security concerns described in On-Demand JavaScript.
Shared document.domain
When we speak of the “same-origin” policy, we’re not
necessarily referring to the true domain a document is served
from; each document has a mutable domain
property (for example, document.domain
) that turns out to be
the critical factor in cross-domain calls. If two documents
declare the same domain property, regardless of their true origin,
they should be able to communicate with each other using XMLHttpRequest
. Jotspot developer Abe
Fettig has explained how to exploit this knowledge for making
cross-domain communication practical (http://fettig.net/weblog/2005/11/28/how-to-make-xmlhttprequest-connections-to-another-server-in-your-domain/
XMLHttpRequest Call). The trick relies on
embedding the external document and having that document—as well
as your own document—define the same document.domain
property. Thus, as with
the On-Demand JavaScript alternative, it does
have one key constraint: the external server must explicitly
cooperate. In addition, you can’t just declare an arbitrary
domain; it has to be a “parent” of the true domain (http://www.mozilla.org/projects/security/components/same-origin.html;
i.e., “shop.example.com” can declare “example.com” but not
“anyoldmegacorp.com”. Due to these constraints, it’s best suited
to a situation where there’s a direct relationship between the
respective site owners.
Images
Images have always worked across domains, with various benefits (syndication) and more than a few problems (“bandwidth theft”) along the way. As noted in the "Alternatives" section of XMLHttpRequest Call (Chapter 6), images can be used for general-purpose remoting, usually with 1-pixel images that will never be rendered. While that’s a hack you’ll likely not need these days, transfer of legitimate images remains a useful capability. When you run the Google Maps API (http://www.google.com/apis/maps/) in your web page, for example, it pulls down map images directly from Google’s own servers.
Related Patterns
Performance Optimization patterns
Since external calls can be expensive, the Performance Optimization patterns apply. They may be applied at the level of the browser, or the server, or both. For example, caching can take place in the browser, the server, or both.
Want to Know More?
See WSFinder: a wiki of public web services (http://www.wsfinder.com/).
Get Ajax Design Patterns now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.