From 6bb9b36522d73f9c079735d9006a12376aee39ea Mon Sep 17 00:00:00 2001 From: Offir Golan Date: Sat, 27 Nov 2021 17:56:37 -0700 Subject: [PATCH] feat!: Use base64 instead of hex encoding for binary data (#420) BREAKING CHANGE: Use the standard `encoding` field on the generated har file instead of `_isBinary` and use `base64` encoding instead of `hex` to reduce the payload size. --- packages/@pollyjs/adapter-fetch/src/index.js | 10 +++--- .../@pollyjs/adapter-node-http/src/index.js | 32 +++++++++---------- .../tests/integration/adapter-test.js | 2 +- packages/@pollyjs/adapter-xhr/src/index.js | 12 +++---- .../src/utils/normalize-recorded-response.js | 2 +- .../@pollyjs/core/src/-private/request.js | 4 +-- .../@pollyjs/core/src/-private/response.js | 4 +-- .../core/tests/unit/-private/response-test.js | 4 +-- .../@pollyjs/persister/src/har/response.js | 4 +-- .../utils/src/utils/serializers/buffer.js | 2 +- .../node/unit/utils/serializers/buffer.js | 16 ++++++---- tests/integration/persister-tests.js | 12 +++---- 12 files changed, 53 insertions(+), 51 deletions(-) diff --git a/packages/@pollyjs/adapter-fetch/src/index.js b/packages/@pollyjs/adapter-fetch/src/index.js index 6bb09eb0..b0a3d6d7 100644 --- a/packages/@pollyjs/adapter-fetch/src/index.js +++ b/packages/@pollyjs/adapter-fetch/src/index.js @@ -190,8 +190,8 @@ export default class FetchAdapter extends Adapter { return { statusCode: response.status, headers: serializeHeaders(response.headers), - body: buffer.toString(isBinaryBuffer ? 'hex' : 'utf8'), - isBinary: isBinaryBuffer + body: buffer.toString(isBinaryBuffer ? 'base64' : 'utf8'), + encoding: isBinaryBuffer ? 'base64' : undefined }; } @@ -223,14 +223,14 @@ export default class FetchAdapter extends Adapter { } const { absoluteUrl, response: pollyResponse } = pollyRequest; - const { statusCode, body, isBinary } = pollyResponse; + const { statusCode, body, encoding } = pollyResponse; let responseBody = body; if (statusCode === 204 && responseBody === '') { responseBody = null; - } else if (isBinary) { - responseBody = bufferToArrayBuffer(Buffer.from(body, 'hex')); + } else if (encoding) { + responseBody = bufferToArrayBuffer(Buffer.from(body, encoding)); } const response = new Response(responseBody, { diff --git a/packages/@pollyjs/adapter-node-http/src/index.js b/packages/@pollyjs/adapter-node-http/src/index.js index 1b990067..0de5e8b9 100644 --- a/packages/@pollyjs/adapter-node-http/src/index.js +++ b/packages/@pollyjs/adapter-node-http/src/index.js @@ -220,13 +220,13 @@ export default class HttpAdapter extends Adapter { headers: response.headers, statusCode: response.statusCode, body: responseBody.body, - isBinary: responseBody.isBinary + encoding: responseBody.encoding }; } async respondToRequest(pollyRequest, error) { const { req, respond } = pollyRequest.requestArguments; - const { statusCode, body, headers, isBinary } = pollyRequest.response; + const { statusCode, body, headers, encoding } = pollyRequest.response; if (pollyRequest[ABORT_HANDLER]) { req.off('abort', pollyRequest[ABORT_HANDLER]); @@ -248,7 +248,7 @@ export default class HttpAdapter extends Adapter { return; } - const chunks = this.getChunksFromBody(body, headers, isBinary); + const chunks = this.getChunksFromBody(body, headers, encoding); const stream = new ReadableStream(); // Expose the response data as a stream of chunks since @@ -281,7 +281,7 @@ export default class HttpAdapter extends Adapter { // should not be concatenated. Instead, the chunks should // be preserved as-is so that each chunk can be mocked individually if (isContentEncoded(headers)) { - const hexChunks = chunks.map((chunk) => { + const encodedChunks = chunks.map((chunk) => { if (!Buffer.isBuffer(chunk)) { this.assert( 'content-encoded responses must all be binary buffers', @@ -290,12 +290,12 @@ export default class HttpAdapter extends Adapter { chunk = Buffer.from(chunk); } - return chunk.toString('hex'); + return chunk.toString('base64'); }); return { - isBinary: true, - body: JSON.stringify(hexChunks) + encoding: 'base64', + body: JSON.stringify(encodedChunks) }; } @@ -303,15 +303,15 @@ export default class HttpAdapter extends Adapter { const isBinaryBuffer = !isUtf8Representable(buffer); // The merged buffer can be one of two things: - // 1. A binary buffer which then has to be recorded as a hex string. + // 1. A binary buffer which then has to be recorded as a base64 string. // 2. A string buffer. return { - isBinary: isBinaryBuffer, - body: buffer.toString(isBinaryBuffer ? 'hex' : 'utf8') + encoding: isBinaryBuffer ? 'base64' : undefined, + body: buffer.toString(isBinaryBuffer ? 'base64' : 'utf8') }; } - getChunksFromBody(body, headers, isBinary = false) { + getChunksFromBody(body, headers, encoding) { if (!body) { return []; } @@ -321,16 +321,16 @@ export default class HttpAdapter extends Adapter { } // If content-encoding is set in the header then the body/content - // is as an array of hex strings + // is as an array of base64 strings if (isContentEncoded(headers)) { - const hexChunks = JSON.parse(body); + const encodedChunks = JSON.parse(body); - return hexChunks.map((chunk) => Buffer.from(chunk, 'hex')); + return encodedChunks.map((chunk) => Buffer.from(chunk, encoding)); } // The body can be one of two things: - // 1. A hex string which then means its binary data. + // 1. A base64 string which then means its binary data. // 2. A utf8 string which means a regular string. - return [Buffer.from(body, isBinary ? 'hex' : 'utf8')]; + return [Buffer.from(body, encoding ? encoding : 'utf8')]; } } diff --git a/packages/@pollyjs/adapter-node-http/tests/integration/adapter-test.js b/packages/@pollyjs/adapter-node-http/tests/integration/adapter-test.js index f5b23ee8..8a28c66c 100644 --- a/packages/@pollyjs/adapter-node-http/tests/integration/adapter-test.js +++ b/packages/@pollyjs/adapter-node-http/tests/integration/adapter-test.js @@ -126,7 +126,7 @@ function commonTests(transport) { expect(requests[0].body.toString('base64')).to.equal( body.toString('base64') ); - expect(requests[0].identifiers.body).to.equal(body.toString('hex')); + expect(requests[0].identifiers.body).to.equal(body.toString('base64')); }); it('should be able to upload form data', async function () { diff --git a/packages/@pollyjs/adapter-xhr/src/index.js b/packages/@pollyjs/adapter-xhr/src/index.js index 45b795f4..59581891 100644 --- a/packages/@pollyjs/adapter-xhr/src/index.js +++ b/packages/@pollyjs/adapter-xhr/src/index.js @@ -132,14 +132,14 @@ export default class XHRAdapter extends Adapter { const buffer = Buffer.from(arrayBuffer); isBinary = !isBufferUtf8Representable(buffer); - body = buffer.toString(isBinary ? 'hex' : 'utf8'); + body = buffer.toString(isBinary ? 'base64' : 'utf8'); } return { statusCode: xhr.status, headers: serializeResponseHeaders(xhr.getAllResponseHeaders()), - body, - isBinary + encoding: isBinary ? 'base64' : undefined, + body }; } @@ -159,11 +159,11 @@ export default class XHRAdapter extends Adapter { // https://github.com/sinonjs/nise/blob/v1.4.10/lib/fake-xhr/index.js#L614-L621 xhr.error(); } else { - const { statusCode, headers, body, isBinary } = pollyRequest.response; + const { statusCode, headers, body, encoding } = pollyRequest.response; let responseBody = body; - if (isBinary) { - const buffer = Buffer.from(body, 'hex'); + if (encoding) { + const buffer = Buffer.from(body, encoding); if (BINARY_RESPONSE_TYPES.includes(xhr.responseType)) { responseBody = bufferToArrayBuffer(buffer); diff --git a/packages/@pollyjs/adapter/src/utils/normalize-recorded-response.js b/packages/@pollyjs/adapter/src/utils/normalize-recorded-response.js index 0a204053..bf26dc67 100644 --- a/packages/@pollyjs/adapter/src/utils/normalize-recorded-response.js +++ b/packages/@pollyjs/adapter/src/utils/normalize-recorded-response.js @@ -8,7 +8,7 @@ export default function normalizeRecordedResponse(response) { statusCode: status, headers: normalizeHeaders(headers), body: content && content.text, - isBinary: Boolean(content && content._isBinary) + encoding: content && content.encoding }; } diff --git a/packages/@pollyjs/core/src/-private/request.js b/packages/@pollyjs/core/src/-private/request.js index 25afd7a2..dbc4eab4 100644 --- a/packages/@pollyjs/core/src/-private/request.js +++ b/packages/@pollyjs/core/src/-private/request.js @@ -172,7 +172,7 @@ export default class PollyRequest extends HTTPBase { } async respond(response) { - const { statusCode, headers, body, isBinary = false } = response || {}; + const { statusCode, headers, body, encoding } = response || {}; assert( 'Cannot respond to a request that already has a response.', @@ -195,7 +195,7 @@ export default class PollyRequest extends HTTPBase { // Set the body without modifying any headers (instead of using .send()) this.response.body = body; - this.response.isBinary = isBinary; + this.response.encoding = encoding; // Trigger the `beforeResponse` event await this._emit('beforeResponse', this.response); diff --git a/packages/@pollyjs/core/src/-private/response.js b/packages/@pollyjs/core/src/-private/response.js index 71ed468d..f3f004e6 100644 --- a/packages/@pollyjs/core/src/-private/response.js +++ b/packages/@pollyjs/core/src/-private/response.js @@ -5,12 +5,12 @@ import HTTPBase from './http-base'; const DEFAULT_STATUS_CODE = 200; export default class PollyResponse extends HTTPBase { - constructor(statusCode, headers, body, isBinary = false) { + constructor(statusCode, headers, body, encoding) { super(); this.status(statusCode || DEFAULT_STATUS_CODE); this.setHeaders(headers); this.body = body; - this.isBinary = isBinary; + this.encoding = encoding; } get ok() { diff --git a/packages/@pollyjs/core/tests/unit/-private/response-test.js b/packages/@pollyjs/core/tests/unit/-private/response-test.js index cbeaf7b2..7887660a 100644 --- a/packages/@pollyjs/core/tests/unit/-private/response-test.js +++ b/packages/@pollyjs/core/tests/unit/-private/response-test.js @@ -14,8 +14,8 @@ describe('Unit | Response', function () { expect(new PollyResponse().statusCode).to.equal(200); }); - it('should default isBinary to false', function () { - expect(new PollyResponse().isBinary).to.be.false; + it('should default encoding to undefined', function () { + expect(new PollyResponse().encoding).to.be.undefined; }); describe('API', function () { diff --git a/packages/@pollyjs/persister/src/har/response.js b/packages/@pollyjs/persister/src/har/response.js index a9fe72dc..dc32dba4 100644 --- a/packages/@pollyjs/persister/src/har/response.js +++ b/packages/@pollyjs/persister/src/har/response.js @@ -38,8 +38,8 @@ export default class Response { if (response.body && typeof response.body === 'string') { this.content.text = response.body; - if (response.isBinary) { - this.content._isBinary = true; + if (response.encoding) { + this.content.encoding = response.encoding; } } diff --git a/packages/@pollyjs/utils/src/utils/serializers/buffer.js b/packages/@pollyjs/utils/src/utils/serializers/buffer.js index fb385893..afb98c91 100644 --- a/packages/@pollyjs/utils/src/utils/serializers/buffer.js +++ b/packages/@pollyjs/utils/src/utils/serializers/buffer.js @@ -21,7 +21,7 @@ export function serialize(body) { } if (Buffer.isBuffer(buffer)) { - return buffer.toString('hex'); + return buffer.toString('base64'); } } diff --git a/packages/@pollyjs/utils/tests/node/unit/utils/serializers/buffer.js b/packages/@pollyjs/utils/tests/node/unit/utils/serializers/buffer.js index 73593d1b..68d6340d 100644 --- a/packages/@pollyjs/utils/tests/node/unit/utils/serializers/buffer.js +++ b/packages/@pollyjs/utils/tests/node/unit/utils/serializers/buffer.js @@ -18,36 +18,38 @@ describe('Unit | Utils | Serializers | buffer', function () { it('should handle buffers', function () { const buffer = Buffer.from('buffer'); - expect(serialize(buffer)).to.equal(buffer.toString('hex')); + expect(serialize(buffer)).to.equal(buffer.toString('base64')); }); it('should handle array of buffers', function () { const buffers = [Buffer.from('b1'), Buffer.from('b2')]; - expect(serialize(buffers)).to.include(buffers[0].toString('hex')); - expect(serialize(buffers)).to.include(buffers[1].toString('hex')); + expect(serialize(buffers)).to.include(buffers[0].toString('base64')); + expect(serialize(buffers)).to.include(buffers[1].toString('base64')); }); it('should handle a mixed array of buffers and strings', function () { const buffers = [Buffer.from('b1'), 's1']; - expect(serialize(buffers)).to.include(buffers[0].toString('hex')); + expect(serialize(buffers)).to.include(buffers[0].toString('base64')); expect(serialize(buffers)).to.include( - Buffer.from(buffers[1]).toString('hex') + Buffer.from(buffers[1]).toString('base64') ); }); it('should handle an ArrayBuffer', function () { const buffer = new ArrayBuffer(8); - expect(serialize(buffer)).to.equal(Buffer.from(buffer).toString('hex')); + expect(serialize(buffer)).to.equal(Buffer.from(buffer).toString('base64')); }); it('should handle an ArrayBufferView', function () { const buffer = new Uint8Array(8); expect(serialize(buffer)).to.equal( - Buffer.from(buffer, buffer.byteOffset, buffer.byteLength).toString('hex') + Buffer.from(buffer, buffer.byteOffset, buffer.byteLength).toString( + 'base64' + ) ); }); }); diff --git a/tests/integration/persister-tests.js b/tests/integration/persister-tests.js index 54775fe9..0f367a15 100644 --- a/tests/integration/persister-tests.js +++ b/tests/integration/persister-tests.js @@ -365,12 +365,12 @@ export default function persisterTests() { content = har.log.entries[0].response.content; expect(await validate.har(har)).to.be.true; - expect(content._isBinary).to.be.undefined; + expect(content.encoding).to.be.undefined; // Binary content server.get(this.recordUrl()).once('beforeResponse', (req, res) => { - res.isBinary = true; - res.body = '536f6d6520636f6e74656e74'; + res.encoding = 'base64'; + res.body = 'U29tZSBjb250ZW50'; }); await this.fetchRecord(); @@ -380,11 +380,11 @@ export default function persisterTests() { content = har.log.entries[1].response.content; expect(await validate.har(har)).to.be.true; - expect(content._isBinary).to.be.true; + expect(content.encoding).to.equal('base64'); // Binary content with no body server.get(this.recordUrl()).once('beforeResponse', (req, res) => { - res.isBinary = true; + res.encoding = 'base64'; res.body = ''; }); @@ -395,6 +395,6 @@ export default function persisterTests() { content = har.log.entries[2].response.content; expect(await validate.har(har)).to.be.true; - expect(content._isBinary).to.be.undefined; + expect(content.encoding).to.be.undefined; }); }