Read our updated version of this post for more up to date information!
Dojo 1.6 introduces a new API for Comet-style real-time communication based on the WebSocket API. WebSocket provides a bi-directional connection to servers that is ideal for pushing messages from a server to a client in real-time. Dojo’s new dojox.socket
module provides access to this API with automated fallback to HTTP-based long-polling for browsers (or servers) that do not support the new WebSocket API. This allows you start using this API with Dojo now.
The dojox.socket
module is designed to be simple, lightweight, and protocol agnostic. In the past Dojo has provided protocol specific modules like CometD and RestChannels, but there are numerous other Comet protocols out there, and dojox.socket
provides the flexibility to work with virtually any of them, with a simple foundational interface. The dojox.socket
module simply passes strings over the HTTP or WebSocket connection, making it compatible with any system.
The simplest way to start a dojox.socket
is to simply call it with a URL path:
var socket = dojox.socket("/comet");
We can now listen for message events from the server:
socket.on("message", function(event){
var data = event.data;
// do something with the data from the server
});
Here we use the socket.on()
event registration method (inspired by socket.io and NodeJS’s registration method) to listen to “message events” and retrieve data when they occur. This method is also aliased to the Dojo style socket.connect()
.
We can also use send()
to send data to the server. If you have just started the connection, you should wait for the open
to ensure the connection is ready to send data:
socket.on("open", function(event){
socket.send("hi server");
});
Finally, we can listen for the connection being closed by the server or network by listening for the “close” event. And we can initiate the close of a connection from the client by calling socket.close()
.
The dojox.socket
method can also be called with standard Dojo IO arguments to initiate the communication with the server. This makes it easy to provide any necessary headers for the requests. For example:
var socket = dojox.socket({
url:"/comet",
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
}});
This will automatically translate the relative URL path to a WebSocket URL (using ws://
scheme) or an HTTP URL depending on the browser capability.
For some applications, the server may only support HTTP/long-polling (without real WebSocket support). We can also explicitly create a long-poll based connection:
var socket = dojox.socket.LongPoll({
url:"/comet",
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
}});
We can also provide alternate transports in the socket arguments object. This would allow us to use dojo.io.script.get
to connect to a server. However, a more robust solution is to use the dojox.io.xhrPlugins
for cross-domain long-polling, which will work properly with dojox.socket
.
Auto-Reconnect
In addition to dojox.socket
, we have also added a dojox.socket.Reconnect
module. This wraps a socket, adding auto-reconnection support. When a socket is closed by network or server problems, this module will automatically attempt to reconnect to the server on a periodic basis, with a back-off algorithm to minimize resource consumption. We can upgrade a socket to auto-reconnect by this simple code fragment:
socket = dojox.socket.Reconnect(socket);
Using Dojo WebSocket with Object Stores
One of the other big enhancements in Dojo 1.6 is the new Dojo object store API (supercedes the Dojo Data API), based on the HTML5 IndexedDB object store API. Dojo 1.6 comes with several store wrappers, and the Observable
store provides notification events that work very well with Comet driven updates. Observable
is a store wrapper. To use it, we first create a store, and then wrap it with Observable:
define("my-module", function(require){
var JsonRest = require("dojo/store/JsonRest");
var Observable = require("dojo/store/Observable");
var store = new JsonRest({data:myData});
store = Observable(store);
});
This store will now provide an observe()
method on query results that widgets can use to react to changes in the data. We can notify the store of changes from the server by calling the notify()
method on the store:
socket.on("message", function(event){
var existingId = event.data.id;
var object = event.data.object;
store.notify(object, existingId);
});
We can signal a new object by calling store.notify()
and omitting the id, and a deleted object by omitting the object (undefined). A changed/updated object should include both.
Handling Long-Polling from your Server
Long-polling style connection emulation can require some care on the server-side. For many applications, the server may have sufficient information from request cookies (or other ambient data) to determine what messages to send the browser. However, other applications may vary on what information should be sent to the browser during the life of the application. Different topics may be subscribed to and unsubscribed from. In these situations, the server may need to correlate different HTTP requests with a single connection and its associated state. While there are numerous protocols, one could do this very easily be defining a unique connection and adding that as a header for the socket (the headers are added to each request in the long-poll cycles). For example, we could do:
var socket = dojox.socket.LongPoll({
url:"/comet",
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
"Client-Id": Math.random()
}});
In addition, dojox.socket
includes a Pragma: long-poll
to indicate the first request in a series of long-poll requests to help a server ensure that the connection setup and timeout is properly handled.
We can easily use dojox.socket
with other protocols as well:
CometD
To initiate a Comet connection with a CometD server, we can do a CometD handshake, connection, and subscription:
var socket = dojox.socket("/cometd");
function send(data){
return socket.send(dojo.toJson(data));
}
socket.on("connect", function(){
// send a handshake
send([
{
"channel": "/meta/handshake",
"version": "1.0",
"minimumVersion": "1.0beta",
"supportedConnectionTypes": ["long-polling"] // or ["callback-polling"] for x-domain
}
])
socket.on("message", function(data){
// wait for the response so we can connect with the provided client id
data = dojo.fromJson(data);
if(data.error){
throw new Error(error);
}
// get the client id for all future messages
clientId = data.clientId;
// send a connect message
send([
{
"channel": "/meta/connect",
"clientId": clientId,
"connectionType": "long-polling"
},
{ // also send a subscription message
"channel": "/meta/subscribe",
"clientId": clientId,
"subscription": "/foo/**"
}
]);
socket.on("message", function(){
// handle messages from the server
});
});
});
Socket.IO
Socket.IO provides a lower-level interface like dojox.socket, providing simple text-based message passing. Here is an example of how to connect to a Socket.IO server:
var args, ws = typeof WebSocket != "undefined";
var socket = dojox.socket(args = {
url: ws ? "/socket.io/websocket" : "/socket.io/xhr-polling",
headers:{
"Content-Type":"application/x-www-urlencoded"
},
transport: function(args, message){
args.content = message; // use URL-encoding to send the message instead of a raw body
dojo.xhrPost(args);
};
});
var sessionId;
socket.on("message", function(){
if (!sessionId){
sessionId = message;
args.url += '/' + sessionId;
}else if(message.substr(0, 3) == '~h~'){
// a heartbeat
}
});
Comet Session Protocol
And here is an example of connecting to a Comet Session Protocol server (the following example was tested with Orbited, but could work with Hookbox, APE, and others):
var args, socket = dojox.socket(args = {
url: "/csp/handshake"
});
function send(data){
return socket.send(dojo.toJson(data));
}
var sessionId = Math.random().toString().substring(2);
socket.on("connect", function(){
send({session:sessionId});
socket.on("message", function(){
args.url = "/csp/comet";
send({session:sessionId});
});
});
Tunguska
Tunguska provides a Comet-based interface for subscribing to data changes. This is a very simple protocol which allows us to communicate with a Tunguska server:
var socket = dojox.socket({
url:"/comet",
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
"Client-Id": Math.random()
}});
function send(data){
return socket.send(dojo.toJson(data));
}
socket.on("connect", function(){
// now subscribe to all changes for MyTable
send([{"to":"/MyTable/*", "method":"subscribe"}]);
});
Conclusion
Dojo’s socket API is a flexible simple module for connecting to a variety of servers and building powerful, efficient real-time applications without constraints. This adds to the array of awesome new features and improvements in Dojo 1.6.
- Cometd
- Dojo
- Persevere
Pingback: Comet Daily » Blog Archive » Dojo and WebSockets()
Guys this is great stuff, but I have to admit I’m getting frustrated not really being able to use this ! So I have to ask : is there a way to implement these technology relatively simply under apache ? I don’t know if the partnership between Zend framework and dojo would make sense to rely on for this, but I think the community could *really* benefit from examples on how to build this kind of client/store communication using common tools (php, apache, …).
I can’t say more that I’m dying to use this very very cool stuff under my current developement framework ;-)
@zladivliba: I have little experience with PHP, perhaps someone else can provide an example, but I was under the impression that PHP/Apache was stuck on thread-per-connection architecture and it would be very difficult to create anything scalable directly on that stack. I think you would need a separate Comet server handler to work with a PHP app properly.
@zladivliba: You might be interested in something like Hookbox (or Hosted Hookbox)… they have some really simple examples to use it with PHP, but keeps the Comet part of the picture on a separate server (or as a hosted service in the case of the hosted version).
@zladivliba: You can use the asynchronous streams in PHP5. Unfortunately there is very little information available (because its pretty unknown), but for a start see here:
wezfurlong.org/blog/2005/may/guru-multiplexing
www.ibm.com/developerworks/web/library/os-php-multitask/
Pingback: Comet Daily » Blog Archive » State of WebSocket Support()
Pingback: WebSocket and Socket.IO()
This sounds very nice; when/where do we get this dojo-1.6?
We expect a January release for Dojo 1.6.
Pingback: Many Comet Solutions for your Real-time Apps | SitePen Blog()
Could you provide a zip file with an example, I would like to connect with socket.io server but I am not able to.
Thanks