Created
April 25, 2011 18:12
-
-
Save secretfader/940921 to your computer and use it in GitHub Desktop.
net-http-stack.js
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
| /* | |
| 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