From 4eb54099e3b8877f97787c2808c184a617cf2f2a Mon Sep 17 00:00:00 2001 From: Stephan Hesse Date: Wed, 10 Oct 2018 02:36:10 +0200 Subject: [PATCH 1/6] add support for binary and serialized object-data responses (arraybuffer/json responseType) --- lib/index.js | 63 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 5 deletions(-) diff --git a/lib/index.js b/lib/index.js index 281d6f4..2a80c48 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,5 +1,7 @@ 'use strict'; +/* global ArrayBuffer */ + /** * Node.js `XMLHttpRequest` implementation using `http.request()`. * @@ -24,7 +26,11 @@ var supportedResponseTypes = Object.freeze({ /** Text response (implicit) */ '': true, /** Text response */ - 'text': true + 'text': true, + /** Binary-data response */ + 'arraybuffer': true, + /** JSON response */ + 'json': true }); /** @@ -139,6 +145,14 @@ module.exports = function () { * @type {?String} */ this._responseText = null; + + /** + * @see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/response + * + * @private + * @type {ArrayBuffer | String | Object} + */ + this._response = null; }; /** @alias module:node-http-xhr */ @@ -297,7 +311,15 @@ NodeHttpXHR.prototype = Object.create( throw new Error('Unsupported responseType "' + type + '"'); } - return this._responseText; + switch (type) { + case '': + case 'text': + return this._responseText; + case 'arraybuffer': + return this._response; + default: + throw new Error('Assertion failed: unsupported response-type: ' + type); + } } }, /** @@ -308,10 +330,19 @@ NodeHttpXHR.prototype = Object.create( * This will be incomplete until the response actually finishes. * * @type {?String} + * @throws when `response` not a String * @readonly */ responseText: { - get: function getResponseText() { return this._responseText; } + get: function getResponseText() { + + // @see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseText#Exceptions + if (this._responseType !== 'text' && this._responseType !== '') { + throw new Error('InvalidStateError: Response-type is ' + this._responseType); + } + + return this._responseText; + } }, /** * Indicates whether or not cross-site `Access-Control` requests should be @@ -469,13 +500,35 @@ NodeHttpXHR.prototype.send = function (data) { this._resp = resp; this._responseText = ''; - resp.setEncoding('utf8'); + // var contentType = resp.headers['content-type']; + // TODO: adjust responseType from content-type header if applicable + + // from Node API docs: The encoding argument is optional and only applies when chunk is a string. Defaults to 'utf8'. + if (this._responseType === 'text' || this._responseType === '') { + resp.setEncoding('utf8'); + } + resp.on('data', function onData(chunk) { - this._responseText += chunk; + + if (typeof chunk === 'string') { + this._responseText += chunk; + } else if (typeof chunk === 'object') { + // binary data usually comes in one chunk (no chunky transfer-encoding) + // or at least, that's what we'll support here for now + if (chunk instanceof Buffer) { + this._response = chunk.buffer; + } else if (chunk instanceof ArrayBuffer) { + this._response = chunk; + } else { + // JSON case + this._response = chunk; + } + } if (this.readyState !== this.LOADING) { this._setReadyState(this.LOADING); } + }.bind(this)); resp.on('end', function onEnd() { From db00a75d91094909d150e9be752f4380f37f7d15 Mon Sep 17 00:00:00 2001 From: Stephan Hesse Date: Wed, 10 Oct 2018 02:36:33 +0200 Subject: [PATCH 2/6] add test case for arraybuffer responseType --- test/index.test.js | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/index.test.js b/test/index.test.js index 942fe75..c19a051 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -413,6 +413,31 @@ function describeXHRProps() { }); describe('#responseType', function () { + + it('supports binary data (arraybuffer responseType)', function (done) { + + var responseData = new Buffer(909); + + var binaryScope = nock('http://example.com') + .get('/music.mp3') + .reply(200, responseData, { + 'Content-Type': 'audio/mpeg' + }); + + req.responseType = 'arraybuffer'; + req.open('GET', 'http://example.com/music.mp3'); + req.send(); + + req.onload = function () { + + assume(req.responseType).equals('arraybuffer'); + assume(req.response).equals(responseData.buffer); + + binaryScope.done(); + done(); + }; + }); + it('intially is `\'\'`', function () { assume(req.responseType).equals(''); }); From dc87e4ae77faec9090084b55ebf75122a43a025f Mon Sep 17 00:00:00 2001 From: Stephan Hesse Date: Wed, 10 Oct 2018 02:36:43 +0200 Subject: [PATCH 3/6] add editorconfig --- .editorconfig | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..56cd390 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +# http://editorconfig.org +root = true +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true From 3fd4eeee03e4354d22f5d514386c44a3442d121a Mon Sep 17 00:00:00 2001 From: Stephan Hesse Date: Wed, 10 Oct 2018 02:57:01 +0200 Subject: [PATCH 4/6] Buffer-type chunks: support concatenation --- lib/index.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/index.js b/lib/index.js index 2a80c48..fcb0779 100644 --- a/lib/index.js +++ b/lib/index.js @@ -513,10 +513,14 @@ NodeHttpXHR.prototype.send = function (data) { if (typeof chunk === 'string') { this._responseText += chunk; } else if (typeof chunk === 'object') { - // binary data usually comes in one chunk (no chunky transfer-encoding) - // or at least, that's what we'll support here for now if (chunk instanceof Buffer) { - this._response = chunk.buffer; + if (this._response) { + this._response = Buffer.concat([this._response, chunk]); + } else { + this._response = chunk; + } + // binary data usually comes in one chunk (no chunky transfer-encoding) + // or at least, that's what we'll support here for now for ArrayBuffer } else if (chunk instanceof ArrayBuffer) { this._response = chunk; } else { From 4804d0bf09e574f5e0cc9e353f3f5c57c0027003 Mon Sep 17 00:00:00 2001 From: Stephan Hesse Date: Wed, 10 Oct 2018 03:05:45 +0200 Subject: [PATCH 5/6] handle json case and also handle both ArrayBuffer and Buffer passed by 'data' callback equally --- lib/index.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/index.js b/lib/index.js index fcb0779..a7b061e 100644 --- a/lib/index.js +++ b/lib/index.js @@ -316,7 +316,12 @@ NodeHttpXHR.prototype = Object.create( case 'text': return this._responseText; case 'arraybuffer': - return this._response; + case 'json': + if (this._response instanceof ArrayBuffer) { + return this._response; + } else if (this._response instanceof Buffer) { + return this._response.buffer; + } default: throw new Error('Assertion failed: unsupported response-type: ' + type); } From 0c370c9ab117128b51190ed246f4c6cd6eedcdeb Mon Sep 17 00:00:00 2001 From: Stephan Hesse Date: Wed, 10 Oct 2018 03:12:38 +0200 Subject: [PATCH 6/6] simplify handling of arraybuffer case and properly handle json data --- lib/index.js | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/lib/index.js b/lib/index.js index a7b061e..3478948 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,7 +1,5 @@ 'use strict'; -/* global ArrayBuffer */ - /** * Node.js `XMLHttpRequest` implementation using `http.request()`. * @@ -315,13 +313,10 @@ NodeHttpXHR.prototype = Object.create( case '': case 'text': return this._responseText; - case 'arraybuffer': case 'json': - if (this._response instanceof ArrayBuffer) { - return this._response; - } else if (this._response instanceof Buffer) { - return this._response.buffer; - } + return JSON.parse(this._responseText); + case 'arraybuffer': + return this._response.buffer; default: throw new Error('Assertion failed: unsupported response-type: ' + type); } @@ -518,18 +513,12 @@ NodeHttpXHR.prototype.send = function (data) { if (typeof chunk === 'string') { this._responseText += chunk; } else if (typeof chunk === 'object') { - if (chunk instanceof Buffer) { - if (this._response) { - this._response = Buffer.concat([this._response, chunk]); - } else { - this._response = chunk; - } - // binary data usually comes in one chunk (no chunky transfer-encoding) - // or at least, that's what we'll support here for now for ArrayBuffer - } else if (chunk instanceof ArrayBuffer) { - this._response = chunk; + if (!(chunk instanceof Buffer)) { + throw new Error('Assertion failed: Response-data should be of `Buffer` type'); + } + if (this._response) { + this._response = Buffer.concat([this._response, chunk]); } else { - // JSON case this._response = chunk; } }