From 01c8ecb2aeaa734245962b9175c36caf966961c1 Mon Sep 17 00:00:00 2001 From: Wes Tyler Date: Fri, 8 Jun 2018 11:31:53 -0500 Subject: [PATCH 1/2] Add optional "forceSequential" flag to batch request payload. Closes #85. --- README.md | 11 +++++++++++ lib/batch.js | 10 ++++++---- test/batch.js | 11 +++++++++++ test/internals.js | 26 +++++++++++++++++++++++++- 4 files changed, 53 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f4d4169..62d3428 100755 --- a/README.md +++ b/README.md @@ -57,3 +57,14 @@ Optionally you can assign the query as a third property rather than placing it d ``` If an error occurs as a result of one the requests to an endpoint it will be included in the response in the same location in the array as the request causing the issue. The error object will include an error property that you can interrogate. At this time the response is a 200 even when a request in the batch returns a different code. + +By default, requests in the `"requests"` array will be run concurrently, with the exception of pipelined requests which will be run sequentially. +To force all batched requests to run sequentially regardless of pipelining, pass in the `forceSequential: true` flag: + +```json +{ "forceSequential": true, + "requests": [ + {"method": "get", "path": "/users/1"}, + {"method": "get", "path": "/users/2"} +] } +``` diff --git a/lib/batch.js b/lib/batch.js index c2a322d..e4fc447 100644 --- a/lib/batch.js +++ b/lib/batch.js @@ -28,6 +28,7 @@ module.exports.config = function (settings) { results: [], resultsMap: [] }; + const { forceSequential } = request.payload; let errorMessage = null; @@ -71,7 +72,7 @@ module.exports.config = function (settings) { } try { - await internals.process(request, requests, payloads, resultsData); + await internals.process(request, requests, payloads, resultsData, forceSequential); } catch (err) { // console.log("ERROR ", err); @@ -89,7 +90,8 @@ module.exports.config = function (settings) { path: Joi.string().required(), query: [Joi.object().unknown().allow(null),Joi.string()], payload: [Joi.object().unknown().allow(null),Joi.string()] - }).label('BatchRequest')).min(1).required() + }).label('BatchRequest')).min(1).required(), + forceSequential: Joi.bool().label('ForceSequential').optional().default(false) }).required().label('BatchRequestPayload') }, auth: settings.auth, @@ -97,7 +99,7 @@ module.exports.config = function (settings) { }; }; -internals.process = async function (request, requests, payloads, resultsData) { +internals.process = async function (request, requests, payloads, resultsData, forceSequential) { const fnsParallel = []; const fnsSerial = []; @@ -105,7 +107,7 @@ internals.process = async function (request, requests, payloads, resultsData) { requests.forEach((requestParts, idx) => { const payloadParts = payloads[idx]; - if (internals.hasRefPart(requestParts) || payloadParts.length) { + if (forceSequential || internals.hasRefPart(requestParts) || payloadParts.length) { return fnsSerial.push( async () => await internals.batch(request, resultsData, idx, requestParts, payloadParts) ); diff --git a/test/batch.js b/test/batch.js index b792433..4a51fbe 100644 --- a/test/batch.js +++ b/test/batch.js @@ -169,6 +169,17 @@ describe('Batch', () => { expect(res[1].name).to.equal('Active Item'); }); + it('handles sequential requests with forceSequential', async () => { + + const res = await Internals.makeRequest(server, '{ "forceSequential": true, "requests": [{"method": "get", "path": "/sequential"}, {"method": "get", "path": "/sequential"}, {"method": "get", "path": "/sequential"}, {"method": "get", "path": "/sequential"}] }'); + + expect(res.length).to.equal(4); + expect(res[0]).to.equal(1); + expect(res[1]).to.equal(2); + expect(res[2]).to.equal(3); + expect(res[3]).to.equal(4); + }); + it('supports piping a response into the next request', async () => { const res = await Internals.makeRequest(server, '{ "requests": [ {"method": "get", "path": "/item"}, {"method": "get", "path": "/item/$0.id"}] }'); diff --git a/test/internals.js b/test/internals.js index 2a5e519..d5d3c96 100644 --- a/test/internals.js +++ b/test/internals.js @@ -3,6 +3,14 @@ const Hapi = require('hapi'); const Bassmaster = require('../'); +const awaitDelay = function (ms) { + + return new Promise((resolve) => { + + return setTimeout(resolve, ms); + }); +}; + const profileHandler = function (request, h) { const id = request.query.id || 'fa0dbda9b1b'; @@ -171,6 +179,21 @@ const echoHandler = function (request, h) { return request.payload; }; +const sequentialHandler = async function (request, h) { + + if (!sequentialHandler.callCount) { + sequentialHandler.callCount = 1; + + await awaitDelay(500); + + return sequentialHandler.callCount; + } + + await awaitDelay(Math.floor(Math.random() * 100)); + + return ++sequentialHandler.callCount; +}; + module.exports.setupServer = async function () { const server = new Hapi.Server(); @@ -205,7 +228,8 @@ module.exports.setupServer = async function () { ] } }, - { method: 'GET', path: '/redirect', handler: redirectHandler } + { method: 'GET', path: '/redirect', handler: redirectHandler }, + { method: 'GET', path: '/sequential', handler: sequentialHandler } ]); await server.register(Bassmaster); From 7b2c8531288c5f3f248f49dfd9f1ef9c9764ecb8 Mon Sep 17 00:00:00 2001 From: Wes Tyler Date: Fri, 8 Jun 2018 13:09:59 -0500 Subject: [PATCH 2/2] Simplify sequentialHandler test route per PR comments --- test/internals.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/test/internals.js b/test/internals.js index d5d3c96..4a8d510 100644 --- a/test/internals.js +++ b/test/internals.js @@ -182,16 +182,13 @@ const echoHandler = function (request, h) { const sequentialHandler = async function (request, h) { if (!sequentialHandler.callCount) { - sequentialHandler.callCount = 1; - - await awaitDelay(500); - - return sequentialHandler.callCount; + sequentialHandler.callCount = 0; } - await awaitDelay(Math.floor(Math.random() * 100)); + ++sequentialHandler.callCount; + await awaitDelay(50 - (10 * sequentialHandler.callCount)); - return ++sequentialHandler.callCount; + return sequentialHandler.callCount; }; module.exports.setupServer = async function () {