Skip to content

Instantly share code, notes, and snippets.

@magiconair
Created February 1, 2011 15:27
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save magiconair/806004 to your computer and use it in GitHub Desktop.
Save magiconair/806004 to your computer and use it in GitHub Desktop.
Long polling server skeleton
/**
* Module import
*/
var express = require('express');
/**
* Port the server listens on
*/
var port = 3000;
/**
* User event map.
*
* events[username] = [ event1, event2, ... ]
*/
var events = {}
/**
* The request dispatcher
*/
var app = express.createServer();
/**
* GET handler for retrieving events for the user.
* The username is required and the timestamp parameter
* is optional.
*
* Example: GET /?user=joe&timestamp=1296564580384
*/
app.get('/', function(req, res) {
console.log('get');
res.send('get\n', 200);
res.end();
});
/**
* POST handler for adding a new event for the user.
* The user and the type parameters are required.
* The data object is in the body.
*/
app.post('/', function(req, res) {
console.log('post');
res.send('post\n', 200);
res.end();
});
// start the server
app.listen(port);
console.log("Server started on port " + port);
/**
* Module import
*/
var express = require('express'),
url = require('url');
/**
* Port the server listens on
*/
var port = 3000;
/**
* User event map
*
* events[user] = [ event1, event2, ... ]
*/
var events = {}
/**
* The request dispatcher
*/
var app = express.createServer();
/**
* Returns the current time in milliseconds from 1 Jan 1970, 00:00
*/
function currentTimestamp() {
return new Date().getTime();
}
/**
* Helper function for logging a debug message
*
* @param String user - the username
* @param int requestId - the request id (optional)
* @param String message - the message
*/
function debug(user, requestId, message) {
if (message) {
console.log("["+user+"/"+requestId+"] " + message);
} else {
console.log("["+user+"] " + requestId);
}
}
/**
* Adds a new event 'type' and optional 'data' for
* the user.
*
* @param String user - the username
* @param String type - the event type
* @param Object data - an optional data object
*/
function addEvent(user, type, data) {
if (!events[user])
events[user] = [];
var event = {
type : type,
timestamp : currentTimestamp()
}
if (data)
event.data = data;
events[user].push(event);
debug(user, "P", "added " + JSON.stringify(event));
}
/**
* Checks for all pending requests for the user
* if an event is available. If an event is
* available it is sent to the client and the
* connection is closed.
*
* @param String user - the username
*/
function notify(user) {
debug(user, "P", "notify");
}
/**
* GET handler for retrieving events for the user.
* The username is required and the timestamp parameter
* is optional.
*
* Example: GET /?user=joe&timestamp=1296564580384
*/
app.get('/', function(req, res) {
var u = url.parse(req.url, true);
if (u.query && u.query.user && events[u.query.user]) {
var msg = "events: " + events[u.query.user].length;
debug(u.query.user, msg);
res.send(msg+"\n", 200);
}
});
/**
* POST handler for adding a new event for the user.
* The user and the type parameters are required.
* The data object is in the body.
*/
app.post('/', function(req, res) {
var u = url.parse(req.url, true);
// check for bad request
if (!u.query || !u.query.user || !u.query.type) {
res.send('', 400);
return;
}
// extract the parameters
var user = u.query.user,
type = u.query.type,
data = u.data;
// add the event
addEvent(user, type, data);
// notify pending requests
notify(user);
// send 200 OK
res.send('', 200);
});
// start the server
app.listen(port);
console.log("Server started on port " + port);
/**
* Module import
*/
var express = require('express'),
url = require('url');
/**
* Port the server listens on
*/
var port = 3000;
/**
* User event map
*
* events[user] = [ event1, event2, ... ]
*/
var events = {}
/**
* Maximum age of events in seconds
*/
var maxAge = 60;
/**
* Global counter for unique ids
*/
var lastRequestId = 0;
/**
* The request dispatcher
*/
var app = express.createServer();
/**
* Helper function for compacting an array by removing
* all null values.
*
* @param Array arr - the input array
* @return Array A new array with all the non-null values from 'arr'
*/
function compact(arr) {
if (!arr) return null;
var i, data = [];
for (i=0; i < arr.length; i++) {
if (arr[i]) data.push(arr[i]);
}
return data;
}
/**
* Returns the current time in milliseconds from 1 Jan 1970, 00:00
*/
function currentTimestamp() {
return new Date().getTime();
}
/**
* Helper function for logging a debug message
*
* @param String user - the username
* @param int requestId - the request id (optional)
* @param String message - the message
*/
function debug(user, requestId, message) {
if (message) {
console.log("["+user+"/"+requestId+"] " + message);
} else {
console.log("["+user+"] " + requestId);
}
}
/**
* Adds a new event 'type' and optional 'data' for
* the user.
*
* @param String user - the username
* @param String type - the event type
* @param Object data - an optional data object
*/
function addEvent(user, type, data) {
if (!events[user])
events[user] = [];
var event = {
type : type,
timestamp : currentTimestamp()
}
if (data)
event.data = data;
events[user].push(event);
debug(user, "P", "added " + JSON.stringify(event));
}
/**
* Returns the next event for the user.
*
* The next event is the first (oldest) event after the
* the 'timestamp'. If 'timestamp' is omitted the oldest
* event which has not expired is returned.
*
* The 'timestamp' parameter represents the last event
* the caller has seen and the function returns the
* next event.
*
* While iterating over the events the function also
* expires events which are older than maxAge seconds.
*
* @param String user - the username
* @param int timestamp - the timestamp of the last event
* @returns Object - an event or null
*/
function nextEvent(user, timestamp) {
if (!events[user]) return null;
if (!timestamp) timestamp = 0;
// - loop over the events for the user
// - timeout events older than maxAge seconds
// - return the oldest event with a timestamp
// greater than 'timestamp'
var event, i;
var minTimestamp = currentTimestamp() - maxAge * 1000;
for(i=0; i < events[user].length; i++) {
event = events[user][i];
// expire event?
if (event.timestamp < minTimestamp) {
debug(user, "expired " + JSON.stringify(event));
events[user][i] = null;
continue;
}
// next event?
if (event.timestamp > timestamp) {
break;
}
}
// compact the event array
events[user] = compact(events[user]);
// return the event
return event;
}
/**
* Checks for all pending requests for the user
* if an event is available. If an event is
* available it is sent to the client and the
* connection is closed.
*
* @param String user - the username
*/
function notify(user) {
debug(user, "P", "notify");
}
/**
* Pauses the current request for the user and
* stores the request and response object in
* the list of pending requests for the user
*
* @param String user - the username
* @param String timestamp - the timestamp filter of the request
* @param Object req - the request
* @param Object res - the response
* @param int requestId - the unique request id
*/
function pause(user, timestamp, req, res, requestId) {
debug(user, requestId, "paused");
}
/**
* GET handler for retrieving events for the user.
* The username is required and the timestamp parameter
* is optional.
*
* Example: GET /?user=joe&timestamp=1296564580384
*/
app.get('/', function(req, res) {
var u = url.parse(req.url, true);
// check for bad request
if (!u.query || !u.query.user) {
res.send(null, 400);
return;
}
// extract the parameters
var user = u.query.user,
timestamp = u.query.timestamp || 0,
requestId = lastRequestId++;
// get the next event
var event = nextEvent(user, timestamp);
// pause the request if there is no pending event
// or send the event
if (!event) {
pause(user, timestamp, req, res, requestId);
} else {
res.send(event);
res.end();
debug(user, requestId, "sent " + JSON.stringify(event));
}
});
/**
* POST handler for adding a new event for the user.
* The user and the type parameters are required.
* The data object is in the body.
*/
app.post('/', function(req, res) {
var u = url.parse(req.url, true);
// check for bad request
if (!u.query || !u.query.user || !u.query.type) {
res.send('', 400);
return;
}
// extract the parameters
var user = u.query.user,
type = u.query.type,
data = u.data;
// add the event
addEvent(user, type, data);
// notify pending requests
notify(user);
// send 200 OK
res.send('', 200);
});
// start the server
app.listen(port);
console.log("Server started on port " + port);
/**
* Module import
*/
var express = require('express'),
url = require('url');
/**
* Port the server listens on
*/
var port = 3000;
/**
* User event map
*
* events[user] = [ event1, event2, ... ]
*/
var events = {}
/**
* Pending requests per user
*
* pending[user] : [ requestCtx1, requestCtx2, ... ]
*/
var pending = {};
/**
* Timeout of a pending request in seconds
*/
var connectionTimeout = 60;
/**
* Maximum age of events in seconds
*/
var maxAge = 60;
/**
* Global counter for unique ids
*/
var lastRequestId = 0;
/**
* The request dispatcher
*/
var app = express.createServer();
/**
* Helper function for compacting an array by removing
* all null values.
*
* @param Array arr - the input array
* @return Array A new array with all the non-null values from 'arr'
*/
function compact(arr) {
if (!arr) return null;
var i, data = [];
for (i=0; i < arr.length; i++) {
if (arr[i]) data.push(arr[i]);
}
return data;
}
/**
* Returns the current time in milliseconds from 1 Jan 1970, 00:00
*/
function currentTimestamp() {
return new Date().getTime();
}
/**
* Helper function for logging a debug message
*
* @param String user - the username
* @param int requestId - the request id (optional)
* @param String message - the message
*/
function debug(user, requestId, message) {
if (message) {
console.log("["+user+"/"+requestId+"] " + message);
} else {
console.log("["+user+"] " + requestId);
}
}
/**
* Adds a new event 'type' and optional 'data' for
* the user.
*
* @param String user - the username
* @param String type - the event type
* @param Object data - an optional data object
*/
function addEvent(user, type, data) {
if (!events[user])
events[user] = [];
var event = {
type : type,
timestamp : currentTimestamp()
}
if (data)
event.data = data;
events[user].push(event);
debug(user, "P", "added " + JSON.stringify(event));
}
/**
* Returns the next event for the user.
*
* The next event is the first (oldest) event after the
* the 'timestamp'. If 'timestamp' is omitted the oldest
* event which has not expired is returned.
*
* The 'timestamp' parameter represents the last event
* the caller has seen and the function returns the
* next event.
*
* While iterating over the events the function also
* expires events which are older than maxAge seconds.
*
* @param String user - the username
* @param int timestamp - the timestamp of the last event
* @returns Object - an event or null
*/
function nextEvent(user, timestamp) {
if (!events[user]) return null;
if (!timestamp) timestamp = 0;
// - loop over the events for the user
// - timeout events older than maxAge seconds
// - return the oldest event with a timestamp
// greater than 'timestamp'
var event, i;
var minTimestamp = currentTimestamp() - maxAge * 1000;
for(i=0; i < events[user].length; i++) {
event = events[user][i];
// expire event?
if (event.timestamp < minTimestamp) {
debug(user, "expired " + JSON.stringify(event));
events[user][i] = null;
continue;
}
// next event?
if (event.timestamp > timestamp) {
break;
}
}
// compact the event array
events[user] = compact(events[user]);
// return the event
return event;
}
/**
* Checks for all pending requests for the user
* if an event is available. If an event is
* available it is sent to the client and the
* connection is closed.
*
* @param String user - the username
*/
function notify(user) {
if (!pending[user]) return;
// loop over pending requests for the user
// and respond if an event is available
var i, ctx, event;
for (i=0; i < pending[user].length; i++) {
ctx = pending[user][i];
// ctx.req == null -> timeout, cleanup
if (!ctx.req) {
pending[user][i] = null;
continue;
}
// get next event
event = nextEvent(user, ctx.timestamp);
// user has event? -> respond, close and cleanup
if (event) {
ctx.req.resume();
ctx.res.send(event);
ctx.res.end();
pending[user][i] = null;
debug(user, ctx.id, "sent " + JSON.stringify(event));
}
}
// compact the list of pending requests
pending[user] = compact(pending[user]);
}
/**
* Pauses the current request for the user and
* stores the request and response object in
* the list of pending requests for the user
*
* @param String user - the username
* @param String timestamp - the timestamp filter of the request
* @param Object req - the request
* @param Object res - the response
* @param int requestId - the unique request id
*/
function pause(user, timestamp, req, res, requestId) {
if (!pending[user])
pending[user] = [];
// save the request context
var ctx = {
id : requestId,
timestamp : timestamp,
req : req,
res : res
};
pending[user].push(ctx);
// configure a timeout on the request
req.connection.setTimeout(connectionTimeout * 1000);
req.connection.on('timeout', function(){
ctx.req = null;
ctx.res = null;
debug(user, requestId, "timeout");
});
// pause the request
req.pause();
debug(user, requestId, "paused");
}
/**
* GET handler for retrieving events for the user.
* The username is required and the timestamp parameter
* is optional.
*
* Example: GET /?user=joe&timestamp=1296564580384
*/
app.get('/', function(req, res) {
var u = url.parse(req.url, true);
// check for bad request
if (!u.query || !u.query.user) {
res.send(null, 400);
return;
}
// add a close handler for the connection
req.connection.on('close', function(){
debug(user, requestId, "close");
});
// extract the parameters
var user = u.query.user,
timestamp = u.query.timestamp || 0,
requestId = lastRequestId++;
// get the next event
var event = nextEvent(user, timestamp);
// pause the request if there is no pending event
// or send the event
if (!event) {
pause(user, timestamp, req, res, requestId);
} else {
res.send(event);
res.end();
debug(user, requestId, "sent " + JSON.stringify(event));
}
});
/**
* POST handler for adding a new event for the user.
* The user and the type parameters are required.
* The data object is in the body.
*/
app.post('/', function(req, res) {
var u = url.parse(req.url, true);
// check for bad request
if (!u.query || !u.query.user || !u.query.type) {
res.send('', 400);
return;
}
// extract the parameters
var user = u.query.user,
type = u.query.type,
data = u.data;
// add the event
addEvent(user, type, data);
// notify pending requests
notify(user);
// send 200 OK
res.send('', 200);
});
// start the server
app.listen(port);
console.log("Server started on port " + port);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment