diff --git a/lib/response.js b/lib/response.js index b567b48..4bf2122 100755 --- a/lib/response.js +++ b/lib/response.js @@ -3,70 +3,74 @@ // Load modules const Http = require('http'); -const Util = require('util'); +const Stream = require('stream'); + // Declare internals const internals = {}; -exports = module.exports = internals.Response = function (req, onEnd) { +exports = module.exports = class Response extends Http.ServerResponse { - Http.ServerResponse.call(this, { method: req.method, httpVersionMajor: 1, httpVersionMinor: 1 }); + constructor(req, onEnd) { - this.once('finish', () => { + super({ method: req.method, httpVersionMajor: 1, httpVersionMinor: 1 }); + this._shot = { trailers: {}, payloadChunks: [] }; + this.assignSocket(internals.nullSocket()); - const res = internals.payload(this); - res.raw.req = req; - process.nextTick(() => onEnd(res)); - }); -}; + this.once('finish', () => { -Util.inherits(internals.Response, Http.ServerResponse); + const res = internals.payload(this); + res.raw.req = req; + process.nextTick(() => onEnd(res)); + }); + } + writeHead() { -internals.Response.prototype.writeHead = function () { + const headers = ((arguments.length === 2 && typeof arguments[1] === 'object') ? arguments[1] : (arguments.length === 3 ? arguments[2] : {})); + const result = super.writeHead.apply(this, arguments); - const headers = ((arguments.length === 2 && typeof arguments[1] === 'object') ? arguments[1] : (arguments.length === 3 ? arguments[2] : {})); - const result = Http.ServerResponse.prototype.writeHead.apply(this, arguments); + this._headers = Object.assign({}, this._headers, headers); - this._headers = this._headers || {}; - const keys = Object.keys(headers); - for (let i = 0; i < keys.length; ++i) { - this._headers[keys[i]] = headers[keys[i]]; - } + // Add raw headers - // Add raw headers + ['Date', 'Connection', 'Transfer-Encoding'].forEach((name) => { - ['Date', 'Connection', 'Transfer-Encoding'].forEach((name) => { - - const regex = new RegExp('\\r\\n' + name + ': ([^\\r]*)\\r\\n'); - const field = this._header.match(regex); - if (field) { - this._headers[name.toLowerCase()] = field[1]; - } - }); - - return result; -}; + const regex = new RegExp('\\r\\n' + name + ': ([^\\r]*)\\r\\n'); + const field = this._header.match(regex); + if (field) { + this._headers[name.toLowerCase()] = field[1]; + } + }); + return result; + } -internals.Response.prototype.write = function (data, encoding) { + write(data, encoding) { - Http.ServerResponse.prototype.write.call(this, data, encoding); - return true; // Write always returns false when disconnected -}; + super.write(data, encoding); + this._shot.payloadChunks.push(new Buffer(data, encoding)); + return true; // Write always returns false when disconnected + } + end(data, encoding) { -internals.Response.prototype.end = function (data, encoding) { + super.end(data, encoding); + this.emit('finish'); + } - Http.ServerResponse.prototype.end.call(this, data, encoding); - this.emit('finish'); // Will not be emitted when disconnected -}; + destroy() { + } -internals.Response.prototype.destroy = function () { + addTrailers(trailers) { + for (const key in trailers) { + this._shot.trailers[key.toLowerCase().trim()] = trailers[key].toString().trim(); + } + } }; @@ -84,91 +88,25 @@ internals.payload = function (response) { trailers: {} }; - // Read payload - - const raw = []; - let rawLength = 0; - for (let i = 0; i < response.output.length; ++i) { - const chunk = (response.output[i] instanceof Buffer ? response.output[i] : new Buffer(response.output[i], response.outputEncodings[i])); - raw.push(chunk); - rawLength = rawLength + chunk.length; - } - - const rawBuffer = Buffer.concat(raw, rawLength); - - // Parse payload + // Prepare payload and trailers - res.payload = ''; - - const CRLF = '\r\n'; - const sep = new Buffer(CRLF + CRLF); - const parts = internals.splitBufferInTwo(rawBuffer, sep); - const payloadBuffer = parts[1]; - - if (!res.headers['transfer-encoding']) { - res.rawPayload = payloadBuffer; - res.payload = payloadBuffer.toString(); - return res; - } - - const CRLFBuffer = new Buffer(CRLF); - let rest = payloadBuffer; - let payloadBytes = []; - let size; - do { - const payloadParts = internals.splitBufferInTwo(rest, CRLFBuffer); - const next = payloadParts[1]; - size = parseInt(payloadParts[0].toString(), 16); - if (size === 0) { - rest = next; - } - else { - const nextData = next.slice(0, size); - payloadBytes = payloadBytes.concat(Array.prototype.slice.call(nextData, 0)); - rest = next.slice(size + 2); - } - } - while (size); - - res.rawPayload = new Buffer(payloadBytes); - res.payload = res.rawPayload.toString('utf8'); - - // Parse trailers - - const trailerLines = rest.toString().split(CRLF); - trailerLines.forEach((line) => { - - const trailerParts = line.split(':'); - if (trailerParts.length === 2) { - res.trailers[trailerParts[0].trim().toLowerCase()] = trailerParts[1].trim(); - } - }); + const rawBuffer = Buffer.concat(response._shot.payloadChunks); + res.rawPayload = rawBuffer; + res.payload = rawBuffer.toString(); + res.trailers = response._shot.trailers; return res; }; -internals.splitBufferInTwo = function (buffer, seperator) { - - for (let i = 0; i < buffer.length - seperator.length; ++i) { - if (internals.bufferEqual(buffer.slice(i, i + seperator.length), seperator)) { - const part1 = buffer.slice(0, i); - const part2 = buffer.slice(i + seperator.length); - return [part1, part2]; - } - } - - return [buffer, new Buffer(0)]; -}; +// Throws away all written data to prevent response from buffering payload +internals.nullSocket = function () { -internals.bufferEqual = function (a, b) { + return new Stream.Writable({ + write(chunk, encoding, callback) { - for (let i = 0; i < a.length; ++i) { - if (a[i] !== b[i]) { - return false; + setImmediate(callback); } - } - - return true; + }); }; diff --git a/lib/schema.js b/lib/schema.js index b961a49..19a65ab 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -2,8 +2,10 @@ const Joi = require('joi'); + const internals = {}; + internals.url = Joi.alternatives(Joi.string().required(), Joi.object().keys({ protocol: Joi.string(), hostname: Joi.string(), @@ -12,6 +14,7 @@ internals.url = Joi.alternatives(Joi.string().required(), Joi.object().keys({ query: Joi.any() }).required()); + internals.options = Joi.object().keys({ url: internals.url.required(), headers: Joi.object(), @@ -27,6 +30,7 @@ internals.options = Joi.object().keys({ method: Joi.string().regex(/^[a-zA-Z0-9!#\$%&'\*\+\-\.^_`\|~]+$/) }).min(1); + module.exports = Joi.object().keys({ dispatchFunc: Joi.func().required(), options: Joi.alternatives(internals.options, internals.url).required(),