Skip to content

Instantly share code, notes, and snippets.

@secretfader
Created April 25, 2011 18:12
Show Gist options
  • Save secretfader/940921 to your computer and use it in GitHub Desktop.
Save secretfader/940921 to your computer and use it in GitHub Desktop.
net-http-stack.js
/*
A Custom StreamStack HTTP implementation.
Based off the work by Nathan Rajlich.
(C) 2011 Nicholas Young. All rights reserved.
*/
var inherits = require('util').inherits,
querystring = require('querystring'),
StreamStack = require('stream-stack').StreamStack,
CRLF = '\r\n',
END_OF_HEADER = new Buffer(CRLF + CRLF),
STATUS_CODES = require('http').STATUS_CODES,
buffertools = require('buffertools');
function bufferIndexOf(haystack, needle) {
for (var i=0, l=haystack.length-needle.length+1; i<l; i++) {
var good = true;
for (var j=0, n=needle.length; j<n; j++) {
if (haystack[i+j] !== needle[j]) {
good = false;
break;
}
}
if (good) return i;
}
return -1;
}
function HttpBaseStack(stream) {
StreamStack.call(this, stream, {
data: this._onData
});
};
inherits(HttpBaseStack, StreamStack);
exports.HttpBaseStack = HttpBaseStack;
HttpBaseStack.prototype.httpVersion = 'HTTP/1.1';
HttpBaseStack.prototype.customRequestEvents = false;
HttpBaseStack.prototype.parsingHeader = true;
HttpBaseStack.prototype.write = function(chunk, enc) {
if(this.chunkedOutgoing) {
return this._writeChunk(chunk, enc);
} else {
return this.stream.write(chunk, enc);
}
};
HttpBaseStack.prototype.end = function(chunk, enc) {
if(chunk) this.write(chunk, enc);
if(!this.shouldKeepAlive) this.stream.end();
};
HttpBaseStack.prototype._writeChunk = function(chunk, enc) {
var len = Buffer.isBuffer(chunk) ? chunk.length : Buffer.byteLength(chunk, enc),
lenHex = len.toString(16),
buf = new Buffer(len + CRLF.length + lenHex.length + CRLF.length),
pos = buf.write(lenHex, 0);
pos += buf.write(CRLF + pos);
if(Buffer.isBuffer(chunk)) {
pos += chunk.copy(buf, pos, 0);
} else {
pos += buf.write(chunk, enc, pos);
}
pos += buf.write(CRLF, pos);
return this.stream.write(buf);
};
HttpBaseStack.prototype._writeHeader = function(firstLine, headers) {
if(!Array.isArray(headers)) {
var h = [];
for(var i in headers) {
h.push(i + ': ' + headers[i]);
}
headers = h;
}
var req = firstLine + CRLF,
cur;
for(var i=0, l=headers.length; i<l; i++) {
cur = headers[i];
if(cur.name && cur.value) {
req += cur.name + ': ' + cur.value + CRLF;
} else {
req += cur + CRLF;
}
}
return this.stream.write(req + CRLF);
};
HttpBaseStack.prototype._onData = function(data) {
if(this.parsingHeader) {
if(!this.rawHeaders) {
this.rawHeaders = data;
} else {
this.rawHeaders = buffertools.concat(this.rawHeaders, data);
}
var index = bufferIndexOf(this.rawHeaders, END_OF_HEADER);
if(index > 0) {
var leftover,
end = index + END_OF_HEADER.length;
if(end < this.rawHeaders.length) {
leftover = this.rawHeaders.slice(end);
this.rawHeaders = this.rawHeaders.slice(0, end);
}
this._onHeadersComplete();
this.parsingHeader = false;
if(leftover) this._onData(leftover);
}
} else {
this.emit('data', data);
}
};
HttpBaseStack.prototype._onHeadersComplete = function() {
var stream = new StreamStack(this),
lines = this.rawHeaders.toString().split(CRLF),
headers = {};
this._onFirstLine(lines[0], stream);
lines = lines.slice(1, lines.length - 2);
lines.forEach(function(line) {
var entry = line.split(': ');
headers[entry[0].toLowerCase()] = entry[1];
});
stream.headers = headers;
stream.params = querystring.parse(stream.path.split('?')[1]);
stream.cleanPath = stream.path.split('?')[0];
if(this.customRequestEvents) {
this.emit(stream.method.toLowerCase(), stream);
} else {
this.emit(this._headerCompleteEvent, stream);
}
};
function HttpResponseStack(stream) {
HttpBaseStack.call(this, stream);
};
inherits(HttpResponseStack, HttpBaseStack);
exports.HttpResponseStack = HttpResponseStack;
HttpResponseStack.prototype.headerSent = false;
HttpResponseStack.prototype.writeHead = function(statusCode, headers) {
headers = headers || [];
this.headerSent = true;
return this._writeHeader(this.httpVersion + ' ' + statusCode + ' ' + STATUS_CODES[statusCode], headers);
};
HttpResponseStack.prototype._headerCompleteEvent = 'request';
HttpResponseStack.prototype._onFirstLine = function(line, req) {
var i = line.indexOf(' ');
req.method = line.substring(0, i);
var j = line.indexOf(' ', i + 1);
req.path = line.substring(i + 1, j);
req.httpVersion = line.substring(j + 1);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment