Bi-directional communication between web servers and browsers is nothing new. Stack Overflow does it today if a new answer is posted to a question you're reading. There are a few different strategies for implementing socket-style behavior using existing technologies:
- AJAX short polling: Connect to the server and ask if there are any new messages. If not, disconnect immediately and ask again after a short interval. This is useful when you don't want to leave a lot of long-running, idle connections open to the server, but it means that you will only receive new messages as fast as your polling interval, and you incur the overhead of establishing a new HTTP connection every time you poll.
- AJAX long polling: Connect to the server and leave the connection open until a new message is available. This gives you fast delivery of new messages and less frequent HTTP connections, but it results in more long-running idle processes on the server.
- Iframe long polling: Same as above, only with a hidden iframe instead of an XHR object. Useful for getting around the same-origin policy when you want to do cross-site long polling.
- Plugins: Flash's XMLSocket, Java applets, etc. can be used to establish something closer to a real low-level persistent socket to a browser.
HTML5 sockets don't really change the underlying strategies available. Mostly they just formalize the strategies already in use, and allow persistent connections to be explicitly identified and thus handled more intelligently. Let's say you want to do web-based push messaging to a mobile browser. With normal long-polling, the mobile device needs to stay awake to persist the connection. With WebSockets, when the mobile device wants to go to sleep, it can hand off the connection to a proxy, and when the proxy receives new data it can wake up the device and pass back the message.
The server-side is wide open. To implement the server-side of a short polling application, you just need some kind of a chronological message queue. When clients connect they can shift new messages off the queue, or they can pass an offset and read any messages that are newer than their offset.
Implementing server-side long polling is where your choices start to narrow. Most HTTP servers are designed for short-lived requests: connect, request a resource, and then disconnect. If 300 people visit your site in 10 minutes, and each takes 2 seconds to connect and download HTTP resources, your server will have an average of 1 HTTP connection open at any given time. With a long polling app, you're suddenly maintaining 300 times as many connections.
If you're running your own dedicated server you may be able to handle this, but on shared hosting platforms you're likely to bump up against resource limits, and App Engine is no exception. App Engine is designed to handle a high volume of low latency requests, e.g. short polling. You can implement long polling on App Engine, but it's ill-advised; requests that run for longer than 30 seconds will get terminated, and the long running processes will eat up your CPU quota.
App Engine's solution for this is the upcoming Channel API. The channel API implements long polling using Google's existing robust XMPP infrastructure.
Brett Bavar and Moishe Lettvin's Google I/O talk lays out the usage pattern as follows:
App Engine apps create a channel on a remote server, and are returned a channel ID which they pass off to the web browser.
class MainPage(webapp.RequestHandler):
def get(self):
id = channel.create_channel(key)
self.response.out.write(
{'channel_id': id})
The web browser passes the channel ID to the same remote server to establish a connection via iframe long polling:
<script src='/_ah/channel/jsapi'></script>
<script>
var channelID = '{{ channel_id }}';
var channel =
new goog.appengine.Channel(channelId);
var socket = channel.open();
socket.onmessage = function(evt) {
alert(evt.data);
}
</script>
When something interesting happens, the App Engine app can push a message to the user's channel, and the browser's long poll request will immediately receive it:
class OtherPage(webapp.RequestHandler):
def get(self):
# something happened
channel.send_message(key, 'bar')