Created
January 6, 2011 16:16
-
-
Save eventualbuddha/768096 to your computer and use it in GitHub Desktop.
Capture and replay HTTP requests and responses.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
/** | |
* Capture or replay responses from an HTTP server. | |
*/ | |
var sys = require('sys'), | |
path = require('path'), | |
args = process.argv.slice(2), | |
// default arg values | |
port = 9090, | |
dir = 'assets', | |
proxyHost = null, | |
proxyPort = null, | |
command = null, | |
isServer = null; | |
function usage(error) { | |
var $0 = path.basename(process.argv[1]); | |
if (error) | |
sys.error($0+": error: "+error+"\n"); | |
sys.error("Usage: "+$0+" COMMAND [-p PORT] [-d DIR] -h HOST -P PORT"); | |
sys.error(""); | |
sys.error("Commands:"); | |
sys.error(" client Takes incoming requests, proxies them, and stores the responses in DIR."); | |
sys.error(" server Takes incoming requests and retrieves responses out of DIR."); | |
sys.error(""); | |
sys.error(" -p, --port Run the file server on this port (defaults to "+port+")"); | |
sys.error(" -d, --dir Capture to or serve from this directory (defaults to "+dir+")"); | |
sys.error(""); | |
sys.error(" -h, --proxy-host"); | |
sys.error(" -P, --proxy-port"); | |
process.exit(error ? 1 : 0); | |
} | |
switch (command = args.shift()) { | |
case 'client': isServer = false; break; | |
case 'server': isServer = true; break; | |
case undefined: usage(); break; | |
default: usage("invalid command "+command); break; | |
} | |
while (args.length) { | |
var arg = args.shift(); | |
var match = arg.match(/^(--\w+[\w-])(?:=(.*))?$/); | |
if (match) { | |
// --port=1234, --port, etc | |
arg = match[1]; | |
args.unshift(match[2]); | |
} | |
switch (arg) { | |
case '-p': case '--port': | |
var portArg = args.shift(); | |
if (portArg === undefined) | |
usage("expected port number after "+arg); | |
port = Number(portArg); | |
break; | |
case '-d': case '--dir': | |
var dirArg = args.shift(); | |
if (dirArg === undefined) | |
usage("expected directory after "+arg); | |
dir = dirArg; | |
break; | |
case '-h': case '--proxy-host': | |
var proxyHostArg = args.shift(); | |
if (proxyHostArg === undefined) | |
usage("expected host after "+arg); | |
proxyHost = proxyHostArg; | |
break; | |
case '-P': case '--proxy-port': | |
var proxyPortArg = args.shift(); | |
if (proxyPortArg === undefined) | |
usage("expected port after "+arg); | |
proxyPort = Number(proxyPortArg); | |
break; | |
case '--help': | |
usage(); | |
break; | |
default: | |
usage('unrecognized argument '+arg); | |
break; | |
} | |
} | |
if (proxyHost === null) | |
usage("proxy host is required"); | |
if (proxyPort === null) | |
usage("proxy port is required"); | |
var http = require('http'), | |
url = require('url'), | |
fs = require('fs'), | |
requestCountByURL = {}; | |
function hash(string) { | |
return new Buffer(string, 'utf8').toString('base64').replace(/\//g, '@'); | |
} | |
var contentTypeMap = { | |
txt: 'text/plain', | |
html: 'text/html', | |
xml: 'application/xml', | |
jpg: 'image/jpeg', | |
png: 'image/png', | |
tiff: 'image/tiff', | |
gif: 'image/gif' | |
}; | |
function mkdir(dir, callback) { | |
fs.mkdir(dir, 0755, function(err) { | |
if (err && err.message.indexOf('EEXIST') != 0) { | |
sys.error('unable to create '+dir+': '+err); | |
callback(err); | |
} else { | |
callback(); | |
} | |
}); | |
} | |
mkdir(dir, function(err) { | |
if (err) return; | |
http.createServer(function(request, response) { | |
var count = requestCountByURL[request.url] || 0, | |
urlRoot = path.join(dir, hash(request.url)); | |
requestCountByURL[request.url] = count + 1; | |
function write(code, body, headers, root, encoding) { | |
if (!headers) headers = {}; | |
if (!headers['Content-Type'] && !headers['content-type']) headers['Content-Type'] = contentTypeMap.txt; | |
response.writeHead(code, headers); | |
response.end(body, encoding); | |
var status = request.method+' '+request.url+' '+code+' '+(body||'').length; | |
if (root) status += ' <= '+root+'\n'; | |
sys.print(status); | |
} | |
if (request.url == '/:/toggle') { | |
isServer = !isServer; | |
write(200); | |
return; | |
} | |
function serve(root, count, callback) { | |
if (count < 0) { | |
callback('no such request found '+root); | |
return; | |
} | |
fs.readFile([root, count, 'info'].join('-'), function(err, data) { | |
if (err) { | |
serve(root, count-1, callback); | |
return; | |
} | |
var info = JSON.parse(data); | |
fs.readFile([root, count, 'body'].join('-'), 'binary', function(err, body) { | |
if (err) { | |
serve(root, count-1, callback); | |
return; | |
} | |
write(info.code, body, info.headers, root, 'binary'); | |
callback(); | |
}); | |
}); | |
} | |
if (isServer) { | |
// serve the file out of the response store | |
serve(urlRoot, count, function(err) { | |
if (err) | |
write(503, "unable to read response file for request: "+err+"\n", urlRoot); | |
}); | |
} else { | |
// proxy the request and store the response | |
var proxy = http.createClient(proxyPort, proxyHost), | |
code = null, | |
contentLength = 0, | |
proxyRequest = proxy.request(request.method, request.url, request.headers), | |
fileStream = fs.createWriteStream([urlRoot, count, 'body'].join('-')); | |
proxyRequest.addListener('response', function(proxyResponse) { | |
code = proxyResponse.statusCode; | |
for (var k in proxyResponse.headers) | |
if (k.toLowerCase() == 'content-length') | |
contentLength = proxyResponse.headers[k]; | |
proxyResponse.addListener('data', function(chunk) { | |
response.write(chunk, 'binary'); | |
fileStream.write(chunk, 'binary'); | |
}); | |
proxyResponse.addListener('end', function() { | |
response.end(); | |
fileStream.end(); | |
}); | |
response.writeHead(proxyResponse.statusCode, proxyResponse.headers); | |
// save header info | |
fs.writeFile([urlRoot, count, 'info'].join('-'), JSON.stringify({code: proxyResponse.statusCode, headers: proxyResponse.headers}), function(err) { | |
if (err) | |
sys.error('unable to write header info to '+urlRoot+': '+err); | |
}); | |
sys.print(request.method+' '+request.url+' '+code+' '+contentLength+' => '+urlRoot+'\n'); | |
}); | |
request.addListener('data', function(chunk) { | |
proxyRequest.write(chunk, 'binary'); | |
}); | |
request.addListener('end', function() { | |
proxyRequest.end(); | |
}); | |
} | |
}).listen(port); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment