From 846a99600d4921d79b25e127b5dee7777ec018b4 Mon Sep 17 00:00:00 2001 From: Matt Harrison Date: Fri, 9 Sep 2016 12:07:09 +0100 Subject: [PATCH 1/8] Remove custom parsing and buffer payload chunks --- lib/response.js | 100 ++++++++---------------------------------------- 1 file changed, 16 insertions(+), 84 deletions(-) diff --git a/lib/response.js b/lib/response.js index b567b48..09c544a 100755 --- a/lib/response.js +++ b/lib/response.js @@ -14,6 +14,8 @@ exports = module.exports = internals.Response = function (req, onEnd) { Http.ServerResponse.call(this, { method: req.method, httpVersionMajor: 1, httpVersionMinor: 1 }); + this.shot = { trailers: {}, payloadChunks: [] }; + this.once('finish', () => { const res = internals.payload(this); @@ -53,6 +55,7 @@ internals.Response.prototype.writeHead = function () { internals.Response.prototype.write = function (data, encoding) { + this.shot.payloadChunks.push(new Buffer(data, encoding)); Http.ServerResponse.prototype.write.call(this, data, encoding); return true; // Write always returns false when disconnected }; @@ -70,6 +73,14 @@ internals.Response.prototype.destroy = function () { }; +internals.Response.prototype.addTrailers = function (trailers) { + + for (const key in trailers) { + this.shot.trailers[key.toLowerCase().trim()] = trailers[key].toString().trim(); + } +}; + + internals.payload = function (response) { // Prepare response object @@ -84,91 +95,12 @@ 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 - - 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'); + // Prepare payload and trailers - // 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)]; -}; - - -internals.bufferEqual = function (a, b) { - - for (let i = 0; i < a.length; ++i) { - if (a[i] !== b[i]) { - return false; - } - } - - return true; -}; From ffdebb6efd29ee7f5ac5f179bbcd5cce40ec4364 Mon Sep 17 00:00:00 2001 From: Matt Harrison Date: Fri, 9 Sep 2016 13:49:23 +0100 Subject: [PATCH 2/8] Use _shot namespace --- lib/response.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/response.js b/lib/response.js index 09c544a..7cb2378 100755 --- a/lib/response.js +++ b/lib/response.js @@ -14,7 +14,7 @@ exports = module.exports = internals.Response = function (req, onEnd) { Http.ServerResponse.call(this, { method: req.method, httpVersionMajor: 1, httpVersionMinor: 1 }); - this.shot = { trailers: {}, payloadChunks: [] }; + this._shot = { trailers: {}, payloadChunks: [] }; this.once('finish', () => { @@ -55,8 +55,8 @@ internals.Response.prototype.writeHead = function () { internals.Response.prototype.write = function (data, encoding) { - this.shot.payloadChunks.push(new Buffer(data, encoding)); Http.ServerResponse.prototype.write.call(this, data, encoding); + this._shot.payloadChunks.push(new Buffer(data, encoding)); return true; // Write always returns false when disconnected }; @@ -76,7 +76,7 @@ internals.Response.prototype.destroy = function () { internals.Response.prototype.addTrailers = function (trailers) { for (const key in trailers) { - this.shot.trailers[key.toLowerCase().trim()] = trailers[key].toString().trim(); + this._shot.trailers[key.toLowerCase().trim()] = trailers[key].toString().trim(); } }; @@ -97,10 +97,10 @@ internals.payload = function (response) { // Prepare payload and trailers - const rawBuffer = Buffer.concat(response.shot.payloadChunks); + const rawBuffer = Buffer.concat(response._shot.payloadChunks); res.rawPayload = rawBuffer; res.payload = rawBuffer.toString(); - res.trailers = response.shot.trailers; + res.trailers = response._shot.trailers; return res; }; From 7e66b7614882188ceb817f5e75fe770542007267 Mon Sep 17 00:00:00 2001 From: Matt Harrison Date: Fri, 9 Sep 2016 13:50:55 +0100 Subject: [PATCH 3/8] Write into null socket --- lib/response.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/response.js b/lib/response.js index 7cb2378..49453cc 100755 --- a/lib/response.js +++ b/lib/response.js @@ -3,6 +3,7 @@ // Load modules const Http = require('http'); +const Stream = require('stream'); const Util = require('util'); // Declare internals @@ -16,6 +17,8 @@ exports = module.exports = internals.Response = function (req, onEnd) { this._shot = { trailers: {}, payloadChunks: [] }; + this.assignSocket(internals.nullSocket()); + this.once('finish', () => { const res = internals.payload(this); @@ -104,3 +107,16 @@ internals.payload = function (response) { return res; }; + + +// Throws away all written data to prevent response from buffering payload + +internals.nullSocket = function () { + + return new Stream.Writable({ + write(chunk, encoding, callback) { + + setImmediate(callback); + } + }); +}; From 5ca963c3983b15c510a4e8e14e7658257073759b Mon Sep 17 00:00:00 2001 From: Matt Harrison Date: Mon, 19 Sep 2016 17:58:19 +0100 Subject: [PATCH 4/8] ES6 class syntax --- lib/response.js | 91 +++++++++++++++++++++++-------------------------- 1 file changed, 42 insertions(+), 49 deletions(-) diff --git a/lib/response.js b/lib/response.js index 49453cc..c3a099a 100755 --- a/lib/response.js +++ b/lib/response.js @@ -4,82 +4,75 @@ const Http = require('http'); const Stream = require('stream'); -const Util = require('util'); // Declare internals const internals = {}; -exports = module.exports = internals.Response = function (req, onEnd) { +exports = module.exports = internals.Response = class Response extends Http.ServerResponse { - Http.ServerResponse.call(this, { method: req.method, httpVersionMajor: 1, httpVersionMinor: 1 }); + constructor(req, onEnd) { - this._shot = { trailers: {}, payloadChunks: [] }; + super({ method: req.method, httpVersionMajor: 1, httpVersionMinor: 1 }); + this._shot = { trailers: {}, payloadChunks: [] }; + this.assignSocket(internals.nullSocket()); - this.assignSocket(internals.nullSocket()); + this.once('finish', () => { - this.once('finish', () => { - - const res = internals.payload(this); - res.raw.req = req; - process.nextTick(() => onEnd(res)); - }); -}; - -Util.inherits(internals.Response, Http.ServerResponse); - - -internals.Response.prototype.writeHead = function () { - - 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 = this._headers || {}; - const keys = Object.keys(headers); - for (let i = 0; i < keys.length; ++i) { - this._headers[keys[i]] = headers[keys[i]]; + const res = internals.payload(this); + res.raw.req = req; + process.nextTick(() => onEnd(res)); + }); } - // Add raw headers + writeHead() { - ['Date', 'Connection', 'Transfer-Encoding'].forEach((name) => { + 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); - const regex = new RegExp('\\r\\n' + name + ': ([^\\r]*)\\r\\n'); - const field = this._header.match(regex); - if (field) { - this._headers[name.toLowerCase()] = field[1]; + this._headers = this._headers || {}; + const keys = Object.keys(headers); + for (let i = 0; i < keys.length; ++i) { + this._headers[keys[i]] = headers[keys[i]]; } - }); - return result; -}; + // Add raw headers + ['Date', 'Connection', 'Transfer-Encoding'].forEach((name) => { -internals.Response.prototype.write = function (data, encoding) { - - Http.ServerResponse.prototype.write.call(this, data, encoding); - this._shot.payloadChunks.push(new Buffer(data, encoding)); - return true; // Write always returns false when disconnected -}; + 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.end = function (data, encoding) { + write(data, encoding) { - Http.ServerResponse.prototype.end.call(this, data, encoding); - this.emit('finish'); // Will not be emitted 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.destroy = function () { + super.end(data, encoding); + this.emit('finish'); + } -}; + destroy() { + } -internals.Response.prototype.addTrailers = function (trailers) { + addTrailers(trailers) { - for (const key in trailers) { - this._shot.trailers[key.toLowerCase().trim()] = trailers[key].toString().trim(); + for (const key in trailers) { + this._shot.trailers[key.toLowerCase().trim()] = trailers[key].toString().trim(); + } } }; From 6b4b435e2d74b6c19400c99a52f5daf5dd133beb Mon Sep 17 00:00:00 2001 From: Matt Harrison Date: Mon, 19 Sep 2016 18:01:19 +0100 Subject: [PATCH 5/8] Object.assign() --- lib/response.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/response.js b/lib/response.js index c3a099a..d19380b 100755 --- a/lib/response.js +++ b/lib/response.js @@ -31,11 +31,7 @@ exports = module.exports = internals.Response = class Response extends Http.Serv 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 = this._headers || {}; - const keys = Object.keys(headers); - for (let i = 0; i < keys.length; ++i) { - this._headers[keys[i]] = headers[keys[i]]; - } + this._headers = Object.assign({}, this._headers, headers); // Add raw headers From 75eb8237d257b9e871ae13aac70e1e5b19123964 Mon Sep 17 00:00:00 2001 From: Matt Harrison Date: Mon, 19 Sep 2016 18:02:22 +0100 Subject: [PATCH 6/8] Styling --- lib/response.js | 1 + lib/schema.js | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/lib/response.js b/lib/response.js index d19380b..639d805 100755 --- a/lib/response.js +++ b/lib/response.js @@ -5,6 +5,7 @@ const Http = require('http'); const Stream = require('stream'); + // Declare internals const internals = {}; 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(), From bd385e3457660758b6e05d357c1093cd15ef8b20 Mon Sep 17 00:00:00 2001 From: Matt Harrison Date: Mon, 19 Sep 2016 18:03:50 +0100 Subject: [PATCH 7/8] Remove internals.Response --- lib/response.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/response.js b/lib/response.js index 639d805..03bf541 100755 --- a/lib/response.js +++ b/lib/response.js @@ -11,7 +11,7 @@ const Stream = require('stream'); const internals = {}; -exports = module.exports = internals.Response = class Response extends Http.ServerResponse { +exports = module.exports = class Response extends Http.ServerResponse { constructor(req, onEnd) { From 4bffff145f781a732b06d7fe43f4393adaafdefd Mon Sep 17 00:00:00 2001 From: Matt Harrison Date: Mon, 19 Sep 2016 18:07:50 +0100 Subject: [PATCH 8/8] Added missing super call --- lib/response.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/response.js b/lib/response.js index 03bf541..4bf2122 100755 --- a/lib/response.js +++ b/lib/response.js @@ -30,7 +30,7 @@ exports = module.exports = class Response extends Http.ServerResponse { writeHead() { 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); + const result = super.writeHead.apply(this, arguments); this._headers = Object.assign({}, this._headers, headers);