Skip to content

Commit

Permalink
Add header listings and option to not buffer entire post data and var…
Browse files Browse the repository at this point in the history
…ious bug fixes

In the past, I would do .buffer('data', 'content_length') to make protoparse buffer up the entire post data before emitting a request.
Clients could abuse this by sending huge post data. I now allow a parameter to SCGIServer to stop this behavior, and just pass the socket and the buffer list.
Note that the bufferlist will continue to suck up data until you tell it not to (by socket.removeAllListeners('data')). This can be useful is you want
to wait until emitting data, for example if you want to give 'downstream' code a chance to register its handlers
  • Loading branch information
yorickvP committed Sep 24, 2011
1 parent f589ec7 commit ac08ea2
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 20 deletions.
62 changes: 47 additions & 15 deletions httpapi.js
Expand Up @@ -7,7 +7,7 @@ var SCGIServer = require('./index.js')
exports.Server = function Server() {
net.Server.call(this)
EventEmitter.call(this)
this._scgi = new SCGIServer(this)
this._scgi = new SCGIServer(this, true) // pass true to stop buffering up post data
var self = this
this._scgi.on('request', function(error, socket, headers, data) {
if(error) return self.emit('clientError', error)
Expand All @@ -27,12 +27,18 @@ exports.ServerRequest = function ServerRequest(socket, headers, data) {
this.httpVersion = '1.0'
this.httpVersionMajor = 1
this.httpVersionMinor = 0
this.headers = headers()
var encoding = null
, self = this
this.setEncoding = function(e) { encoding = e }
process.nextTick(function() {
self.emit('data', data.toString(encoding))
self.emit('end') })}
socket.removeAllListeners('data')
data.forEach(function(data) {
self.emit('data', encoding ? data.toString(encoding) : data) })
data.length = 0
socket.on('data', self.emit.bind(self, 'data'))
if (!socket.writable) self.emit('end')
else socket.on('end', self.emit.bind(self, 'end')) })}

exports.ServerRequest.prototype = new EventEmitter()
exports.ServerRequest.pause = function() { throw "pausing not implemented" }
Expand All @@ -47,43 +53,69 @@ exports.ServerResponse = function ServerResponse(req) {
this.__defineGetter__('writable', function() { return self.connection.writable })
this.destroy = this.connection.destroy.bind(this.connection)
this.destroySoon = this.connection.destroySoon.bind(this.connection)
this._hasBody = req.method !== 'HEAD'
this._head_written = false
this._headers = {}
this.statusCode = 200 }

util.inherits(exports.ServerResponse, EventEmitter)
var CRLF = '\r\n'
;(function(i, k) { for(var j in k) i[j] = k[j]})(exports.ServerResponse.prototype,
{ write: function write() {
if (!this._head_written) this.writeHead()
// todo: requests without body
{ write: function write(chunk) {
if (!this._headerSent) this.writeHead()
if (!this._hasBody) {
console.error('This type of response MUST NOT have a body. Ignoring write() calls.')
return }

if (typeof chunk !== 'string' && !Buffer.isBuffer(chunk) && !Array.isArray(chunk))
throw new TypeError('first argument must be a string, Array, or Buffer')

if (chunk.length === 0) return false
this.connection.write.apply(this.connection, arguments) }

, end: function end() {
if (arguments.length) this.write.apply(this, arguments)
if (!this._head_written) this.writeHead()
if (arguments.length && arguments[0]) this.write.apply(this, arguments)
if (!this._headerSent) this.writeHead()
this.connection.end() }

, addTrailers: function addTrailers() {
throw "trailers not implemented" }
throw "trailers not implemented" }

, setHeader: function setHeader(name, value) {
this._headers[name] = value }

, getHeader: function getHeader(name) {
var n = ("" + name).toLowerCase()
, r, h = this._headers
Object.keys(h).forEach(function(x) {
if (x.toLowerCase() === n) r = h[x] }, this)
return r }

, removeHeader: function removeHeader(name) {
var n = ("" + name).toLwoerCase()
, h = this._headers
Object.keys(h).forEach(function(x) {
if (x.toLowerCase() === n) delete h[x] }, this)}

, writeHead: function writeHead(statuscode, reason, headers) {
, writeHead: function writeHead(statusCode, reason, headers) {
if (arguments.length == 2) {
headers = reason
reason = "" }
if (statuscode) this.statusCode = statuscode
if (!reason) reason = http.STATUS_CODES[this.statusCode] || 'unknown'
if (!statusCode) statusCode = this.statusCode
if (!reason) reason = http.STATUS_CODES[statusCode] || 'unknown'
var hdr = this._headers
if (headers) Object.keys(headers).forEach(function(x) {hdr[x] = headers[x]})
var headtxt = ""
headtxt += 'Status: ' + statuscode + ' ' + reason + CRLF
headtxt += 'Status: ' + statusCode + ' ' + reason + CRLF
Object.keys(hdr).forEach(function(k) {
if (typeof hdr[k] == 'string')
if (!Array.isArray(hdr[k]))
headtxt += k + ': ' + hdr[k] + CRLF
else hdr[k].forEach(function(j) {
headtxt += k + ': ' + j + CRLF })})
this.connection.write(headtxt + CRLF)
this._head_written = true }})
this._headerSent = true
if (statusCode === 204 || statusCode === 304 || (100 <= statusCode && statusCode <= 199))
this._hasBody = false }})

exports.createServer = function createServer(callback) {
var s = new exports.Server()
Expand Down
19 changes: 14 additions & 5 deletions index.js
Expand Up @@ -6,7 +6,7 @@ var net = require('net')
, EventEmitter = require('events').EventEmitter
module.exports = SCGIServer

function SCGIServer(server) {
function SCGIServer(server, no_buffer) {
if ('number' == typeof server) {
var port = server
server = net.createServer()
Expand All @@ -16,7 +16,7 @@ function SCGIServer(server) {
EventEmitter.call(this)
server.on('connection', function connectionHandler(socket) {
var headers = {}
Parser.Stream(socket)
, p = Parser.Stream(socket)
.scan('len', ':')
.tap(function(vars) {
if (isNaN(+vars.len)) {
Expand Down Expand Up @@ -52,11 +52,20 @@ function SCGIServer(server) {
}
vars.content_length = l
})
.buffer('data', 'content_length')
.tap(function(vars) {
if (!no_buffer) p.buffer('data', 'content_length')
p.tap(function(vars) {
self.emit('request', null, socket, function header(n, encoding) {
if (!n) {
var r = {} // format the headers as lower case http headers
Object.keys(headers)
.filter(function(i) {return i.indexOf('HTTP_') === 0})
.forEach(function(k) {r[k.toLowerCase()
.split('_')
.slice(1)
.join('-')] = header(k)})
return r }
return (headers[n] ? (encoding == 'buffer' ? headers[n] :
headers[n].toString(encoding || 'ascii')) : undefined) }, vars.data)
headers[n].toString(encoding || 'ascii')) : undefined) }, no_buffer ? p._buffer : vars.data)
})
})
return self
Expand Down

0 comments on commit ac08ea2

Please sign in to comment.