Skip to content

Instantly share code, notes, and snippets.

@stellaraccident
Created January 23, 2011 16:36
Show Gist options
  • Save stellaraccident/792197 to your computer and use it in GitHub Desktop.
Save stellaraccident/792197 to your computer and use it in GitHub Desktop.
Illustrate exception handling with a simple proxy middleware
/**
* Pattern for performing error handling given standard node
* callback patterns (ie. error passed as first argument).
*/
function Armorer(nextCallback) {
this.nextCallback=nextCallback;
this.cleanupCallbacks=[];
}
Armorer.prototype={
onCleanup: function(callback) {
this.cleanupCallbacks.push(callback);
return this;
},
/**
* Given an eventEmitter, attach to the error event such that
* the retreat is called if it is ever raised.
*/
enlist: function(eventEmitter, cleanupCallback) {
eventEmitter.on('error', this.retreat.bind(this));
if (cleanupCallback) {
this.cleanupCallbacks.push(cleanupCallback);
}
return this;
},
/**
* Handle the given exception, invoking all exception handlers
* in the process.
*/
retreat: function(exception) {
this.cleanup();
return this.nextCallback(exception);
},
/**
* Initiate cleanup
*/
cleanup: function() {
for (var i=0; i<this.cleanupCallbacks.length; i++) {
var cb=this.cleanupCallbacks[i];
cb();
}
},
/**
* Traps any exceptions coming out of callback and calls
* retreat() if they happen.
*/
guard: function(callback) {
try {
return callback();
} catch (e) {
console.log('Exception handled in guarded callback: ' + e);
return this.retreat(e);
}
},
/**
* Returns a function that will invoke callback with full exception
* handling.
*/
trap: function(callback) {
var me=this;
return function() {
try {
return callback.apply(this, arguments);
} catch (e) {
console.log('Exception handled in guarded callback: ' + e);
return me.retreat(e);
}
};
}
};
function armor(nextCallback) {
return new Armorer(nextCallback);
}
Function.prototype.armor=function(armorer) {
return armorer.trap(this);
};
module.exports=armor;
/**
* Simple proxy intended to be used as middleware to direct parts
* of an HTTP namespace to other servers.
*
* Copyright (c) 2011, Stella Laurenzo
*/
var util=require('util'),
url=require('url'),
http=require('http'),
armor=require('./armor');
function makeAppendDestination(baseUrl) {
return function(request) {
//console.log('Request: ' + util.inspect(request));
return baseUrl + request.url;
};
}
module.exports=function(destination) {
if (typeof destination==='string')
destination=makeAppendDestination(destination);
return function(req, res, next) {
var newUrl=destination(req),
parsed=url.parse(newUrl),
port=parsed.port;
//console.log('url: ' + util.inspect(parsed));
if (!port) {
if (parsed.protocol==='http:') port=80;
else if (parsed.protocol==='https:') port=443;
else port=80;
}
var client=http.createClient(port, parsed.hostname),
clientRequest=client.request(req.method,
parsed.pathname + (parsed.search||''),
req.headers);
// Error handling
var a=armor(next)
.enlist(client)
.enlist(clientRequest, clientRequest.end.bind(clientRequest));
// Patch through the response
clientRequest.on('response', function(pxres) {
pxres.on('data', function(chunk) {
res.write(chunk, 'binary');
}.armor(a));
pxres.on('end', function() {
res.end();
}.armor(a));
res.writeHead(pxres.statusCode, pxres.headers);
}.armor(a));
// Patch through the request
req.on('data', function(chunk) {
clientRequest.write(chunk, 'binary');
}.armor(a));
req.on('end', function() {
clientRequest.end();
}.armor(a));
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment