From 4ae0bc1f1dfdcbbd0880d3f370259ca444d42a31 Mon Sep 17 00:00:00 2001 From: AVVS Date: Mon, 17 Oct 2016 17:16:51 +0100 Subject: [PATCH 01/73] feat: adds onRequest hook, which passes request from Wreck.request BREAKING CHANGE: upgrades Hapi to 15.x.x --- lib/index.js | 8 +++++++- package.json | 21 ++++++++++++--------- test/index.js | 50 +++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 64 insertions(+), 15 deletions(-) diff --git a/lib/index.js b/lib/index.js index 5b7dbfd..11a0e60 100644 --- a/lib/index.js +++ b/lib/index.js @@ -39,6 +39,7 @@ internals.schema = Joi.object({ timeout: Joi.number().integer(), mapUri: Joi.func(), onResponse: Joi.func(), + onRequest: Joi.func(), agent: Joi.object(), ttl: Joi.string().valid('upstream').allow(null), maxSockets: Joi.number().positive().allow(false) @@ -138,7 +139,7 @@ internals.handler = function (route, handlerOptions) { // Send request - Wreck.request(request.method, uri, options, (err, res) => { + const req = Wreck.request(request.method, uri, options, (err, res) => { let ttl = null; @@ -169,6 +170,11 @@ internals.handler = function (route, handlerOptions) { .code(res.statusCode) .passThrough(!!settings.passThrough); // Default to false }); + + // if there is an onRequest handler, pass it + if (settings.onRequest) { + settings.onRequest(req); + } }); }; }; diff --git a/package.json b/package.json index 66af1f1..048f2f4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "h2o2", "description": "Proxy handler plugin for hapi.js", - "version": "5.4.0", + "version": "6.0.0", "repository": "git://github.com/hapijs/h2o2", "main": "lib/index.js", "keywords": [ @@ -15,16 +15,19 @@ "node": ">=4.0.0" }, "dependencies": { - "boom": "3.X.X", - "hoek": "4.X.X", - "joi": "9.X.X", - "wreck": "9.X.X" + "boom": "4.x.x", + "hoek": "4.x.x", + "joi": "9.x.x", + "wreck": "10.x.x" + }, + "peerDependencies": { + "hapi": "14.x.x || 15.x.x" }, "devDependencies": { - "code": "3.X.X", - "hapi": "14.X.X", - "inert": "4.X.X", - "lab": "11.X.X" + "code": "^4.0.0", + "hapi": "^15.1.1", + "inert": "4.x.x", + "lab": "11.x.x" }, "scripts": { "test": "lab -a code -t 100 -L", diff --git a/test/index.js b/test/index.js index fdd6405..e9dc6bf 100644 --- a/test/index.js +++ b/test/index.js @@ -25,7 +25,7 @@ const lab = exports.lab = Lab.script(); const describe = lab.describe; const it = lab.it; const expect = Code.expect; - +const cookieExpect = ['test=123; Secure; HttpOnly; SameSite=Strict', 'auto=xyz; Secure; HttpOnly; SameSite=Strict']; describe('H2o2', () => { @@ -92,7 +92,7 @@ describe('H2o2', () => { expect(response.statusCode).to.equal(200); expect(response.payload).to.contain('John Doe'); - expect(response.headers['set-cookie']).to.equal(['test=123', 'auto=xyz']); + expect(response.headers['set-cookie']).to.equal(cookieExpect); expect(response.headers['cache-control']).to.equal('max-age=2, must-revalidate, private'); server.inject('/profile', (res) => { @@ -528,7 +528,47 @@ describe('H2o2', () => { server.inject('/', (res) => { expect(res.statusCode).to.equal(404); - expect(res.headers['set-cookie'][0]).to.equal('a=b'); + expect(res.headers['set-cookie'][0]).to.equal('a=b; Secure; HttpOnly; SameSite=Strict'); + done(); + }); + }); + }); + + it('calls onRequest when it\'s created', (done) => { + + const upstream = new Hapi.Server(); + upstream.connection(); + upstream.start(() => { + + let called = false; + const onRequestWithSocket = function (req) { + + called = true; + expect(req).to.be.an.instanceof(Http.ClientRequest); + }; + + const on = function (err, res, request, reply, settings, ttl) { + + expect(err).to.be.null(); + reply(this.c); + }; + + const handler = { + proxy: { + host: 'localhost', + port: upstream.info.port, + onRequest: onRequestWithSocket, + onResponse: on + } + }; + + const server = provisionServer(); + server.route({ method: 'GET', path: '/onRequestSocket', config: { handler, bind: { c: 6 } } }); + + server.inject('/onRequestSocket', (res) => { + + expect(res.result).to.equal(6); + expect(called).to.equal(true); done(); }); }); @@ -1029,7 +1069,7 @@ describe('H2o2', () => { expect(res.statusCode).to.equal(200); expect(res.payload).to.contain('John Doe'); - expect(res.headers['set-cookie']).to.equal(['test=123', 'auto=xyz']); + expect(res.headers['set-cookie']).to.equal(cookieExpect); done(); }); }); @@ -1061,7 +1101,7 @@ describe('H2o2', () => { expect(res.statusCode).to.equal(200); expect(res.payload).to.contain('John Doe'); - expect(res.headers['set-cookie']).to.equal(['test=123', 'auto=xyz']); + expect(res.headers['set-cookie']).to.equal(cookieExpect); done(); }); }); From e9cbf4807bb3b6a9d38d35b96877266e57036454 Mon Sep 17 00:00:00 2001 From: Vitaly Aminev Date: Mon, 17 Oct 2016 23:28:11 +0100 Subject: [PATCH 02/73] chore: update dev deps style --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 048f2f4..674359c 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,8 @@ "hapi": "14.x.x || 15.x.x" }, "devDependencies": { - "code": "^4.0.0", - "hapi": "^15.1.1", + "code": "4.x.x", + "hapi": "15.x.x", "inert": "4.x.x", "lab": "11.x.x" }, From 0dc24eeb23e123c7666c9b2e83c8161889067182 Mon Sep 17 00:00:00 2001 From: Santiago Castro Date: Tue, 18 Apr 2017 06:28:20 -0300 Subject: [PATCH 03/73] Fix broken Markdown headings --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index edce4fc..3912a00 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -#h2o2 +# h2o2 Proxy handler plugin for hapi.js. From 409508eafbbd9f51a8f337fd62fb774b0ddab825 Mon Sep 17 00:00:00 2001 From: Oscar Funes Date: Sat, 12 Aug 2017 12:32:39 -0600 Subject: [PATCH 04/73] Upgrade all dependencies, add package-lock.json --- lib/index.js | 18 +- package-lock.json | 1766 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 14 +- test/index.js | 11 +- 4 files changed, 1789 insertions(+), 20 deletions(-) create mode 100644 package-lock.json diff --git a/lib/index.js b/lib/index.js index 5b7dbfd..5c46e0d 100644 --- a/lib/index.js +++ b/lib/index.js @@ -11,7 +11,7 @@ const Wreck = require('wreck'); // Declare internals const internals = { - agents: {} // server.info.uri -> { http, https, insecure } + agents: {} // server.info.uri -> { http, https, insecure } }; @@ -19,8 +19,8 @@ internals.defaults = { xforward: false, passThrough: false, redirects: false, - timeout: 1000 * 60 * 3, // Timeout request after 3 minutes - localStatePassThrough: false, // Pass cookies defined by the server upstream + timeout: 1000 * 60 * 3, // Timeout request after 3 minutes + localStatePassThrough: false, // Pass cookies defined by the server upstream maxSockets: Infinity }; @@ -165,9 +165,9 @@ internals.handler = function (route, handlerOptions) { } return reply(res) - .ttl(ttl) - .code(res.statusCode) - .passThrough(!!settings.passThrough); // Default to false + .ttl(ttl) + .code(res.statusCode) + .passThrough(!!settings.passThrough); // Default to false }); }); }; @@ -196,9 +196,9 @@ internals.mapUri = function (protocol, host, port, uri) { } let address = uri.replace(/{protocol}/g, request.connection.info.protocol) - .replace(/{host}/g, request.connection.info.host) - .replace(/{port}/g, request.connection.info.port) - .replace(/{path}/g, request.url.path); + .replace(/{host}/g, request.connection.info.host) + .replace(/{port}/g, request.connection.info.port) + .replace(/{path}/g, request.url.path); Object.keys(request.params).forEach((key) => { diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..4a748e3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1766 @@ +{ + "name": "h2o2", + "version": "5.4.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "accept": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/accept/-/accept-2.1.4.tgz", + "integrity": "sha1-iHr1TO7lx/RDBGGXHsQAxh0JrLs=", + "dev": true, + "requires": { + "boom": "5.2.0", + "hoek": "4.2.0" + } + }, + "acorn": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.1.tgz", + "integrity": "sha512-vOk6uEMctu0vQrvuSqFdJyqj1Q0S5VTDL79qtjo+DhRr+1mmaD+tluFSCZqhvi/JUhXSzoZN2BhtstaPEeE8cw==", + "dev": true + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "3.3.0" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, + "ajv": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.2.2.tgz", + "integrity": "sha1-R8aNaehvXZUxA7AHSpQw3GPaXjk=", + "dev": true, + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.0.0", + "json-schema-traverse": "0.3.1", + "json-stable-stringify": "1.0.1" + } + }, + "ajv-keywords": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", + "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", + "dev": true + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dev": true, + "requires": { + "kind-of": "3.2.2", + "longest": "1.0.1", + "repeat-string": "1.6.1" + } + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, + "ammo": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/ammo/-/ammo-2.0.4.tgz", + "integrity": "sha1-v4CqshFpjqePY+9efxE91dnokX8=", + "dev": true, + "requires": { + "boom": "5.2.0", + "hoek": "4.2.0" + } + }, + "ansi-escapes": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-2.0.0.tgz", + "integrity": "sha1-W65SvkJIeN2Xg+iRDj/Cki6DyBs=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "argparse": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", + "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", + "dev": true, + "requires": { + "sprintf-js": "1.0.3" + } + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "1.0.3" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "b64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/b64/-/b64-3.0.3.tgz", + "integrity": "sha512-Pbeh0i6OLubPJdIdCepn8ZQHwN2MWznZHbHABSTEfQ706ie+yuxNSaPdqX1xRatT6WanaS1EazMiSg0NUW2XxQ==", + "dev": true + }, + "babel-code-frame": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz", + "integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ=", + "dev": true, + "requires": { + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "boom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", + "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", + "requires": { + "hoek": "4.2.0" + } + }, + "bossy": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/bossy/-/bossy-3.0.4.tgz", + "integrity": "sha1-+a6fJugbQaMY9O4Ng2huSlwlB7k=", + "dev": true, + "requires": { + "hoek": "4.2.0", + "joi": "10.6.0" + } + }, + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "call": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/call/-/call-4.0.2.tgz", + "integrity": "sha1-33b19R7o3Ui4VqyEAPfmnm1zmcQ=", + "dev": true, + "requires": { + "boom": "5.2.0", + "hoek": "4.2.0" + } + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true, + "optional": true + }, + "catbox": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/catbox/-/catbox-7.1.5.tgz", + "integrity": "sha512-4fui5lELzqZ+9cnaAP/BcqXTH6LvWLBRtFhJ0I4FfgfXiSaZcf6k9m9dqOyChiTxNYtvLk7ZMYSf7ahMq3bf5A==", + "dev": true, + "requires": { + "boom": "5.2.0", + "hoek": "4.2.0", + "joi": "10.6.0" + } + }, + "catbox-memory": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/catbox-memory/-/catbox-memory-2.0.4.tgz", + "integrity": "sha1-Qz4lWQLK9UIz0ShkKcj03xToItU=", + "dev": true, + "requires": { + "hoek": "4.2.0" + } + }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dev": true, + "optional": true, + "requires": { + "align-text": "0.1.4", + "lazy-cache": "1.0.4" + } + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "requires": { + "restore-cursor": "2.0.0" + } + }, + "cli-width": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.1.0.tgz", + "integrity": "sha1-sjTKIJsp72b8UY2bmNWEewDt8Ao=", + "dev": true + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, + "optional": true, + "requires": { + "center-align": "0.1.3", + "right-align": "0.1.3", + "wordwrap": "0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true, + "optional": true + } + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/code/-/code-4.1.0.tgz", + "integrity": "sha1-IJrRHQWvigwceq9pTZ+k0sfZW4U=", + "dev": true, + "requires": { + "hoek": "4.2.0" + } + }, + "color-convert": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz", + "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", + "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", + "dev": true, + "requires": { + "inherits": "2.0.3", + "readable-stream": "2.3.3", + "typedarray": "0.0.6" + } + }, + "content": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/content/-/content-3.0.5.tgz", + "integrity": "sha512-MQVYZuNnm5N0xalwtRGlZrcKFwgE6VKXCEh3XkCeoWUo3gL5BS52UiqswRvAwQW1ocfdCNkEKI5uy0pNGax+IQ==", + "dev": true, + "requires": { + "boom": "5.2.0" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "4.1.1", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + }, + "cryptiles": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", + "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", + "dev": true, + "requires": { + "boom": "5.2.0" + } + }, + "debug": { + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "optional": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "del": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "5.0.0", + "is-path-cwd": "1.0.0", + "is-path-in-cwd": "1.0.0", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "rimraf": "2.6.1" + } + }, + "diff": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.0.tgz", + "integrity": "sha512-w0XZubFWn0Adlsapj9EAWX0FqWdO4tz8kc3RiYdWLh4k/V8PTb6i0SMgXt0vRM3zyKnT8tKO7mUlieRQHIjMNg==", + "dev": true + }, + "doctrine": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz", + "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=", + "dev": true, + "requires": { + "esutils": "2.0.2", + "isarray": "1.0.0" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.4.1.tgz", + "integrity": "sha1-mc1+r8/8ov+Zpcj18qR01jZLS9M=", + "dev": true, + "requires": { + "ajv": "5.2.2", + "babel-code-frame": "6.22.0", + "chalk": "1.1.3", + "concat-stream": "1.6.0", + "cross-spawn": "5.1.0", + "debug": "2.6.8", + "doctrine": "2.0.0", + "eslint-scope": "3.7.1", + "espree": "3.5.0", + "esquery": "1.0.0", + "estraverse": "4.2.0", + "esutils": "2.0.2", + "file-entry-cache": "2.0.0", + "functional-red-black-tree": "1.0.1", + "glob": "7.1.2", + "globals": "9.18.0", + "ignore": "3.3.3", + "imurmurhash": "0.1.4", + "inquirer": "3.2.1", + "is-resolvable": "1.0.0", + "js-yaml": "3.9.1", + "json-stable-stringify": "1.0.1", + "levn": "0.3.0", + "lodash": "4.17.4", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "natural-compare": "1.4.0", + "optionator": "0.8.2", + "path-is-inside": "1.0.2", + "pluralize": "4.0.0", + "progress": "2.0.0", + "require-uncached": "1.0.3", + "semver": "5.4.1", + "strip-json-comments": "2.0.1", + "table": "4.0.1", + "text-table": "0.2.0" + } + }, + "eslint-config-hapi": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-hapi/-/eslint-config-hapi-10.0.0.tgz", + "integrity": "sha1-mYCv/XYQPrwf7JK0Vjg0XbGTSPU=", + "dev": true + }, + "eslint-plugin-hapi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-hapi/-/eslint-plugin-hapi-4.0.0.tgz", + "integrity": "sha1-RKouRfeTmlI5Kc2DK7mqEpqV6CM=", + "dev": true, + "requires": { + "hapi-capitalize-modules": "1.1.6", + "hapi-for-you": "1.0.0", + "hapi-scope-start": "2.1.1", + "no-arrowception": "1.0.0" + } + }, + "eslint-scope": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", + "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", + "dev": true, + "requires": { + "esrecurse": "4.2.0", + "estraverse": "4.2.0" + } + }, + "espree": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.0.tgz", + "integrity": "sha1-mDWGJb3QVYYeon4oZ+pyn69GPY0=", + "dev": true, + "requires": { + "acorn": "5.1.1", + "acorn-jsx": "3.0.1" + } + }, + "esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "dev": true + }, + "esquery": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", + "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", + "dev": true, + "requires": { + "estraverse": "4.2.0" + } + }, + "esrecurse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", + "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", + "dev": true, + "requires": { + "estraverse": "4.2.0", + "object-assign": "4.1.1" + } + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "external-editor": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.0.4.tgz", + "integrity": "sha1-HtkZnanL/i7y96MbL96LDRI2iXI=", + "dev": true, + "requires": { + "iconv-lite": "0.4.18", + "jschardet": "1.5.1", + "tmp": "0.0.31" + } + }, + "fast-deep-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", + "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "requires": { + "escape-string-regexp": "1.0.5" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "1.2.2", + "object-assign": "4.1.1" + } + }, + "find-rc": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/find-rc/-/find-rc-3.0.1.tgz", + "integrity": "sha1-VKQXg3DxC8k3H6jRssKAmir6DM4=", + "dev": true + }, + "flat-cache": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.2.2.tgz", + "integrity": "sha1-+oZxTnLCHbiGAXYezy9VXRq8a5Y=", + "dev": true, + "requires": { + "circular-json": "0.3.3", + "del": "2.2.2", + "graceful-fs": "4.1.11", + "write": "0.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "globby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "1.0.2", + "arrify": "1.0.1", + "glob": "7.1.2", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "handlebars": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.10.tgz", + "integrity": "sha1-PTDHGLCaPZbyPqTMH0A8TTup/08=", + "dev": true, + "requires": { + "async": "1.5.2", + "optimist": "0.6.1", + "source-map": "0.4.4", + "uglify-js": "2.8.29" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "requires": { + "amdefine": "1.0.1" + } + } + } + }, + "hapi": { + "version": "16.5.2", + "resolved": "https://registry.npmjs.org/hapi/-/hapi-16.5.2.tgz", + "integrity": "sha512-8aXEiC2IT1bfF/5RYz9LtWqm99oGVDd2JcQ3KRRvFGDngEE35kZl9Bk2jproY97fBrmAlvkNrmQKWf3jb2lNxw==", + "dev": true, + "requires": { + "accept": "2.1.4", + "ammo": "2.0.4", + "boom": "5.2.0", + "call": "4.0.2", + "catbox": "7.1.5", + "catbox-memory": "2.0.4", + "cryptiles": "3.1.2", + "heavy": "4.0.4", + "hoek": "4.2.0", + "iron": "4.0.5", + "items": "2.1.1", + "joi": "10.6.0", + "mimos": "3.0.3", + "podium": "1.3.0", + "shot": "3.4.2", + "statehood": "5.0.3", + "subtext": "5.0.0", + "topo": "2.0.2" + } + }, + "hapi-capitalize-modules": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/hapi-capitalize-modules/-/hapi-capitalize-modules-1.1.6.tgz", + "integrity": "sha1-eZEXFBXhXmqjIx5k3ac8gUZmUxg=", + "dev": true + }, + "hapi-for-you": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hapi-for-you/-/hapi-for-you-1.0.0.tgz", + "integrity": "sha1-02L77o172pwseAHiB+WlzRoLans=", + "dev": true + }, + "hapi-scope-start": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/hapi-scope-start/-/hapi-scope-start-2.1.1.tgz", + "integrity": "sha1-dJWnJv5yt7yo3izcwdh82M5qtPI=", + "dev": true + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "heavy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/heavy/-/heavy-4.0.4.tgz", + "integrity": "sha1-NskTNsAMz+hSyqTRUwhjNc0vAOk=", + "dev": true, + "requires": { + "boom": "5.2.0", + "hoek": "4.2.0", + "joi": "10.6.0" + } + }, + "hoek": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", + "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==" + }, + "iconv-lite": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz", + "integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA==", + "dev": true + }, + "ignore": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.3.tgz", + "integrity": "sha1-QyNS5XrM2HqzEQ6C0/6g5HgSFW0=", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inert": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/inert/-/inert-4.2.1.tgz", + "integrity": "sha512-qmbbZYPSzU/eOUOStPQvSjrU9IR1Q3uDtsEsVwnBQeZG43xu7Nrj6yuUrX3ice/03rv5dj/KiKB+NGCbiqH+aQ==", + "dev": true, + "requires": { + "ammo": "2.0.4", + "boom": "5.2.0", + "hoek": "4.2.0", + "items": "2.1.1", + "joi": "10.6.0", + "lru-cache": "4.1.1" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "inquirer": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.2.1.tgz", + "integrity": "sha512-QgW3eiPN8gpj/K5vVpHADJJgrrF0ho/dZGylikGX7iqAdRgC9FVKYKWFLx6hZDBFcOLEoSqINYrVPeFAeG/PdA==", + "dev": true, + "requires": { + "ansi-escapes": "2.0.0", + "chalk": "2.1.0", + "cli-cursor": "2.1.0", + "cli-width": "2.1.0", + "external-editor": "2.0.4", + "figures": "2.0.0", + "lodash": "4.17.4", + "mute-stream": "0.0.7", + "run-async": "2.3.0", + "rx-lite": "4.0.8", + "rx-lite-aggregates": "4.0.8", + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "through": "2.3.8" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "dev": true, + "requires": { + "color-convert": "1.9.0" + } + }, + "chalk": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", + "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", + "dev": true, + "requires": { + "ansi-styles": "3.2.0", + "escape-string-regexp": "1.0.5", + "supports-color": "4.2.1" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + }, + "supports-color": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.2.1.tgz", + "integrity": "sha512-qxzYsob3yv6U+xMzPrv170y8AwGP7i74g+pbixCfD6rgso8BscLT2qXIuz6TpOaiJZ3mFgT5O9lyT9nMU4LfaA==", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + } + } + }, + "iron": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/iron/-/iron-4.0.5.tgz", + "integrity": "sha1-TwQszri5c480a1mqc0yDqJvDFCg=", + "dev": true, + "requires": { + "boom": "5.2.0", + "cryptiles": "3.1.2", + "hoek": "4.2.0" + } + }, + "is-buffer": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", + "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", + "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", + "dev": true, + "requires": { + "is-path-inside": "1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz", + "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=", + "dev": true, + "requires": { + "path-is-inside": "1.0.2" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-resolvable": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz", + "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=", + "dev": true, + "requires": { + "tryit": "1.0.3" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isemail": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-2.2.1.tgz", + "integrity": "sha1-A1PT2aYpUQgMJiwqoKQrjqjp4qY=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "items": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/items/-/items-2.1.1.tgz", + "integrity": "sha1-i9FtnIOxlSneWuoyGsqtp4NkoZg=" + }, + "joi": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-10.6.0.tgz", + "integrity": "sha512-hBF3LcqyAid+9X/pwg+eXjD2QBZI5eXnBFJYaAkH4SK3mp9QSRiiQnDYlmlz5pccMvnLcJRS4whhDOTCkmsAdQ==", + "requires": { + "hoek": "4.2.0", + "isemail": "2.2.1", + "items": "2.1.1", + "topo": "2.0.2" + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.9.1.tgz", + "integrity": "sha512-CbcG379L1e+mWBnLvHWWeLs8GyV/EMw862uLI3c+GxVyDHWZcjZinwuBd3iW2pgxgIlksW/1vNJa4to+RvDOww==", + "dev": true, + "requires": { + "argparse": "1.0.9", + "esprima": "4.0.0" + } + }, + "jschardet": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/jschardet/-/jschardet-1.5.1.tgz", + "integrity": "sha512-vE2hT1D0HLZCLLclfBSfkfTTedhVj0fubHpJBHKwwUWX0nSbhPAfk+SG9rTX95BYNmau8rGFfCeaT6T5OW1C2A==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "requires": { + "jsonify": "0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.5" + } + }, + "lab": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/lab/-/lab-14.1.2.tgz", + "integrity": "sha512-Sfgv3ABgcLHYuy5sal7litun4xat1sMSKKRzVi+8fgZ3CDhSzrPCn0SyOOZokGuFJ3XER2or/TmGBCy72h9usA==", + "dev": true, + "requires": { + "bossy": "3.0.4", + "code": "4.1.0", + "diff": "3.3.0", + "eslint": "4.4.1", + "eslint-config-hapi": "10.0.0", + "eslint-plugin-hapi": "4.0.0", + "espree": "3.5.0", + "find-rc": "3.0.1", + "handlebars": "4.0.10", + "hoek": "4.2.0", + "items": "2.1.1", + "json-stable-stringify": "1.0.1", + "json-stringify-safe": "5.0.1", + "mkdirp": "0.5.1", + "seedrandom": "2.4.3", + "source-map": "0.5.6", + "source-map-support": "0.4.15" + } + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true, + "optional": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2", + "type-check": "0.3.2" + } + }, + "lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", + "dev": true + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true + }, + "lru-cache": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", + "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", + "dev": true, + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } + }, + "mime-db": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.29.0.tgz", + "integrity": "sha1-SNJtI1WJZRcErFkWygYAGRQmaHg=", + "dev": true + }, + "mimic-fn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz", + "integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg=", + "dev": true + }, + "mimos": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/mimos/-/mimos-3.0.3.tgz", + "integrity": "sha1-uRCQcq03jCty9qAQHEPd+ys2ZB8=", + "dev": true, + "requires": { + "hoek": "4.2.0", + "mime-db": "1.29.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "1.1.8" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "nigel": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/nigel/-/nigel-2.0.2.tgz", + "integrity": "sha1-k6GGb7DFLYc5CqdeKxYfS1x15bE=", + "dev": true, + "requires": { + "hoek": "4.2.0", + "vise": "2.0.2" + } + }, + "no-arrowception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/no-arrowception/-/no-arrowception-1.0.0.tgz", + "integrity": "sha1-W/PpXrnEG1c4SoBTM9qjtzTuMno=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "1.1.0" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "0.0.8", + "wordwrap": "0.0.3" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + } + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "1.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "pez": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/pez/-/pez-2.1.5.tgz", + "integrity": "sha1-XsLMYlAMw+tCNtSkFM9aF7XrUAc=", + "dev": true, + "requires": { + "b64": "3.0.3", + "boom": "5.2.0", + "content": "3.0.5", + "hoek": "4.2.0", + "nigel": "2.0.2" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "2.0.4" + } + }, + "pluralize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-4.0.0.tgz", + "integrity": "sha1-WbcIwcAZCi9pLxx2GMRGsFL9F2I=", + "dev": true + }, + "podium": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/podium/-/podium-1.3.0.tgz", + "integrity": "sha512-ZIujqk1pv8bRZNVxwwwq0BhXilZ2udycQT3Kp8ah3f3TcTmVg7ILJsv/oLf47gRa2qeiP584lNq+pfvS9U3aow==", + "dev": true, + "requires": { + "hoek": "4.2.0", + "items": "2.1.1", + "joi": "10.6.0" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "progress": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", + "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "0.1.0", + "resolve-from": "1.0.1" + } + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "requires": { + "onetime": "2.0.1", + "signal-exit": "3.0.2" + } + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dev": true, + "optional": true, + "requires": { + "align-text": "0.1.4" + } + }, + "rimraf": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", + "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", + "dev": true, + "requires": { + "glob": "7.1.2" + } + }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "2.1.0" + } + }, + "rx-lite": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", + "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", + "dev": true + }, + "rx-lite-aggregates": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", + "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "dev": true, + "requires": { + "rx-lite": "4.0.8" + } + }, + "safe-buffer": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "dev": true + }, + "seedrandom": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.3.tgz", + "integrity": "sha1-JDhQTa0zkXMUv/GKxNeU8W1qrsw=", + "dev": true + }, + "semver": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", + "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "shot": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/shot/-/shot-3.4.2.tgz", + "integrity": "sha1-Hlw/bysmZJrcQvfrNQIUpaApHWc=", + "dev": true, + "requires": { + "hoek": "4.2.0", + "joi": "10.6.0" + } + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "slice-ansi": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "dev": true + }, + "source-map": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", + "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", + "dev": true + }, + "source-map-support": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.15.tgz", + "integrity": "sha1-AyAt9lwG0r2MfsI2KhkwVv7407E=", + "dev": true, + "requires": { + "source-map": "0.5.6" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "statehood": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/statehood/-/statehood-5.0.3.tgz", + "integrity": "sha512-YrPrCt10t3ImH/JMO5szSwX7sCm8HoqVl3VFLOa9EZ1g/qJx/ZmMhN+2uzPPB/vaU6hpkJpXxcBWsgIkkG+MXA==", + "dev": true, + "requires": { + "boom": "5.2.0", + "cryptiles": "3.1.2", + "hoek": "4.2.0", + "iron": "4.0.5", + "items": "2.1.1", + "joi": "10.6.0" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "subtext": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/subtext/-/subtext-5.0.0.tgz", + "integrity": "sha512-2nXG1G1V+K64Z20cQII7k0s38J2DSycMXBLMAk9RXUFG0uAkAbLSVoa88croX9VhTdBCJbLAe9g6LmzKwpJhhQ==", + "dev": true, + "requires": { + "boom": "5.2.0", + "content": "3.0.5", + "hoek": "4.2.0", + "pez": "2.1.5", + "wreck": "12.2.3" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "table": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/table/-/table-4.0.1.tgz", + "integrity": "sha1-qBFsEz+sLGH0pCCrbN9cTWHw5DU=", + "dev": true, + "requires": { + "ajv": "4.11.8", + "ajv-keywords": "1.5.1", + "chalk": "1.1.3", + "lodash": "4.17.4", + "slice-ansi": "0.0.4", + "string-width": "2.1.1" + }, + "dependencies": { + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true, + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "tmp": { + "version": "0.0.31", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz", + "integrity": "sha1-jzirlDjhcxXl29izZX6L+yd65Kc=", + "dev": true, + "requires": { + "os-tmpdir": "1.0.2" + } + }, + "topo": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/topo/-/topo-2.0.2.tgz", + "integrity": "sha1-zVYVdSU5BXwNwEkaYhw7xvvh0YI=", + "requires": { + "hoek": "4.2.0" + } + }, + "tryit": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", + "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "1.1.2" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dev": true, + "optional": true, + "requires": { + "source-map": "0.5.6", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true, + "optional": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "vise": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/vise/-/vise-2.0.2.tgz", + "integrity": "sha1-awjo+0y3bjpQzW3Q7DczjoEaDTk=", + "dev": true, + "requires": { + "hoek": "4.2.0" + } + }, + "which": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", + "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "dev": true, + "requires": { + "isexe": "2.0.0" + } + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true, + "optional": true + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "wreck": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/wreck/-/wreck-12.2.3.tgz", + "integrity": "sha512-6gIqKsW7Qc5s6cxLImozB0jUZ0N0x3Xycc8o4gMUzIINNOMGfmK1Fz532mrtrLS1XrLsfBxffNqkCj0UDJsigQ==", + "requires": { + "boom": "5.2.0", + "hoek": "4.2.0" + } + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "0.5.1" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "optional": true, + "requires": { + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", + "window-size": "0.1.0" + } + } + } +} diff --git a/package.json b/package.json index 4cfd1b5..1e8e325 100644 --- a/package.json +++ b/package.json @@ -15,19 +15,19 @@ "node": ">=4.0.0" }, "dependencies": { - "boom": "3.x.x", + "boom": "5.x.x", "hoek": "4.x.x", - "joi": "9.x.x", - "wreck": "9.x.x" + "joi": "10.x.x", + "wreck": "12.x.x" }, "devDependencies": { - "code": "3.x.x", - "hapi": "14.x.x", + "code": "4.x.x", + "hapi": "16.x.x", "inert": "4.x.x", - "lab": "11.x.x" + "lab": "14.x.x" }, "scripts": { - "test": "lab -a code -t 100 -L", + "test": "lab -a code -t 100 --L", "test-cov-html": "lab -a code -r html -o coverage.html" }, "license": "BSD-3-Clause" diff --git a/test/index.js b/test/index.js index fdd6405..cc2610d 100644 --- a/test/index.js +++ b/test/index.js @@ -92,7 +92,8 @@ describe('H2o2', () => { expect(response.statusCode).to.equal(200); expect(response.payload).to.contain('John Doe'); - expect(response.headers['set-cookie']).to.equal(['test=123', 'auto=xyz']); + expect(response.headers['set-cookie'][0]).to.include(['test=123']); + expect(response.headers['set-cookie'][1]).to.include(['auto=xyz']); expect(response.headers['cache-control']).to.equal('max-age=2, must-revalidate, private'); server.inject('/profile', (res) => { @@ -528,7 +529,7 @@ describe('H2o2', () => { server.inject('/', (res) => { expect(res.statusCode).to.equal(404); - expect(res.headers['set-cookie'][0]).to.equal('a=b'); + expect(res.headers['set-cookie'][0]).to.include('a=b'); done(); }); }); @@ -1029,7 +1030,8 @@ describe('H2o2', () => { expect(res.statusCode).to.equal(200); expect(res.payload).to.contain('John Doe'); - expect(res.headers['set-cookie']).to.equal(['test=123', 'auto=xyz']); + expect(res.headers['set-cookie'][0]).to.include(['test=123']); + expect(res.headers['set-cookie'][1]).to.include(['auto=xyz']); done(); }); }); @@ -1061,7 +1063,8 @@ describe('H2o2', () => { expect(res.statusCode).to.equal(200); expect(res.payload).to.contain('John Doe'); - expect(res.headers['set-cookie']).to.equal(['test=123', 'auto=xyz']); + expect(res.headers['set-cookie'][0]).to.include(['test=123']); + expect(res.headers['set-cookie'][1]).to.include(['auto=xyz']); done(); }); }); From ea66e8c4fe0b6fe6952ffc2df1d362ee3b3e9b7d Mon Sep 17 00:00:00 2001 From: Oscar Funes Date: Sat, 12 Aug 2017 12:39:46 -0600 Subject: [PATCH 05/73] Update travis YML to use LTS line and latest stable of node --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index fee5429..22c8dc2 100755 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,8 @@ language: node_js node_js: - - "4" + - "lts/argon" + - "lts/boron" - "node" sudo: false From 059d87024e0f8e6de7957e003dabadf3561570b4 Mon Sep 17 00:00:00 2001 From: Oscar Funes Date: Sat, 12 Aug 2017 12:48:30 -0600 Subject: [PATCH 06/73] Change --L to -L for lab to correctly lint --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1e8e325..5c894c6 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "lab": "14.x.x" }, "scripts": { - "test": "lab -a code -t 100 --L", + "test": "lab -a code -t 100 -L", "test-cov-html": "lab -a code -r html -o coverage.html" }, "license": "BSD-3-Clause" From e94752db3bc834a140fe500b2728ddedbfe12c2f Mon Sep 17 00:00:00 2001 From: Oscar Funes Date: Sat, 12 Aug 2017 12:51:05 -0600 Subject: [PATCH 07/73] 6.0.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4a748e3..d47f737 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "h2o2", - "version": "5.4.0", + "version": "6.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 5c894c6..9827991 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "h2o2", "description": "Proxy handler plugin for hapi.js", - "version": "5.4.0", + "version": "6.0.0", "repository": "git://github.com/hapijs/h2o2", "main": "lib/index.js", "keywords": [ From 9224b4af164f5580daf26759935a74079facb8f2 Mon Sep 17 00:00:00 2001 From: Sanjay Pandit Date: Tue, 12 Sep 2017 11:19:00 -0700 Subject: [PATCH 08/73] accept default configuration via plugin registration and support TLS options ref #36 - adds ciphers and secureProtocol TLS options to supported parameters requires Wreck 12.5.0 which enables support for TLS ciphers --- README.md | 6 ++++++ lib/index.js | 14 +++++++++++++- test/index.js | 39 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3912a00..ae02f7c 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,8 @@ _**NOTE**: h2o2 is included with and loaded by default in Hapi < 9.0._ ## Options +The plugin can be registered with an optional object specifying defaults to be applied to the proxy handler object. + The proxy handler object has the following properties: * `host` - upstream service host to proxy requests to. It will have the same path as the client request. @@ -69,6 +71,10 @@ The proxy handler object has the following properties: * `ttl` - if set to `'upstream'`, applies the upstream response caching policy to the response using the `response.ttl()` method (or passed as an argument to the `onResponse` method if provided). * `agent` - a node [http(s) agent](http://nodejs.org/api/http.html#http_class_http_agent) to be used for connections to upstream server. * `maxSockets` - sets the maximum number of sockets available per outgoing proxy host connection. `false` means use the **wreck** module default value (`Infinity`). Does not affect non-proxy outgoing client connections. Defaults to `Infinity`. +* `secureProtocol` - [TLS](http://nodejs.org/api/tls.html) flag indicating the SSL method to use, e.g. `SSLv3_method` +to force SSL version 3. The possible values depend on your installation of OpenSSL. Read the official OpenSSL docs for possible [SSL_METHODS](http://www.openssl.org/docs/ssl/ssl.html#DEALING_WITH_PROTOCOL_METHODS). +* `ciphers` - [TLS](https://nodejs.org/api/tls.html#tls_modifying_the_default_tls_cipher_suite) list of TLS ciphers to override node's default. +The possible values depend on your installation of OpenSSL. Read the official OpenSSL docs for possible [TLS_CIPHERS](https://www.openssl.org/docs/man1.0.2/apps/ciphers.html#CIPHER-LIST-FORMAT). ## Usage diff --git a/lib/index.js b/lib/index.js index 5c46e0d..eb0fdea 100644 --- a/lib/index.js +++ b/lib/index.js @@ -41,7 +41,9 @@ internals.schema = Joi.object({ onResponse: Joi.func(), agent: Joi.object(), ttl: Joi.string().valid('upstream').allow(null), - maxSockets: Joi.number().positive().allow(false) + maxSockets: Joi.number().positive().allow(false), + secureProtocol: Joi.string(), + ciphers: Joi.string() }) .xor('host', 'mapUri', 'uri') .without('mapUri', 'port', 'protocol') @@ -50,6 +52,8 @@ internals.schema = Joi.object({ exports.register = function (server, pluginOptions, next) { + internals.defaults = Hoek.applyToDefaults(internals.defaults, pluginOptions); + server.handler('proxy', internals.handler); server.decorate('reply', 'proxy', function (options) { @@ -131,6 +135,14 @@ internals.handler = function (route, handlerOptions) { options.headers['x-forwarded-host'] = (options.headers['x-forwarded-host'] ? options.headers['x-forwarded-host'] + ',' : '') + request.info.host; } + if (settings.ciphers) { + options.ciphers = settings.ciphers; + } + + if (settings.secureProtocol) { + options.secureProtocol = settings.secureProtocol; + } + const contentType = request.headers['content-type']; if (contentType) { options.headers['content-type'] = contentType; diff --git a/test/index.js b/test/index.js index cc2610d..a3a459e 100644 --- a/test/index.js +++ b/test/index.js @@ -1348,7 +1348,7 @@ describe('H2o2', () => { path: '/item/{param_a}/{param_b}', handler: function (request, reply) { - return reply({ a: request.params.param_a, b:request.params.param_b }); + return reply({ a: request.params.param_a, b: request.params.param_b }); } }); @@ -1836,4 +1836,41 @@ describe('H2o2', () => { }); }); }); + + it('uses custom TLS settings', (done) => { + + const upstream = new Hapi.Server(); + upstream.connection({ tls: tlsOptions }); + upstream.route({ + method: 'GET', + path: '/', + handler: function (request, reply) { + + return reply('ok'); + } + }); + + upstream.start(() => { + + const server = new Hapi.Server(); + server.connection({}); + server.register({ register: H2o2.register, options: { secureProtocol: 'TLSv1_1_method', ciphers: 'DES-CBC3-SHA' } }); + + server.route({ + method: 'GET', + path: '/', + handler: function (request, reply) { + + return reply.proxy({ host: '127.0.0.1', protocol: 'https', port: upstream.info.port, rejectUnauthorized: false }); + } + }); + + server.inject('/', (res) => { + + expect(res.statusCode).to.equal(200); + expect(res.payload).to.equal('ok'); + done(); + }); + }); + }); }); From 9bde0d21ba0e9b970ef223b375598d33046a00fb Mon Sep 17 00:00:00 2001 From: Sanjay Pandit Date: Tue, 12 Sep 2017 11:19:34 -0700 Subject: [PATCH 09/73] update wreck to 12.5.0 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index d47f737..5a6d69c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1578,7 +1578,7 @@ "content": "3.0.5", "hoek": "4.2.0", "pez": "2.1.5", - "wreck": "12.2.3" + "wreck": "12.5.0" } }, "supports-color": { @@ -1726,9 +1726,9 @@ "dev": true }, "wreck": { - "version": "12.2.3", - "resolved": "https://registry.npmjs.org/wreck/-/wreck-12.2.3.tgz", - "integrity": "sha512-6gIqKsW7Qc5s6cxLImozB0jUZ0N0x3Xycc8o4gMUzIINNOMGfmK1Fz532mrtrLS1XrLsfBxffNqkCj0UDJsigQ==", + "version": "12.5.0", + "resolved": "https://registry.npmjs.org/wreck/-/wreck-12.5.0.tgz", + "integrity": "sha512-O2wljTGaQhlhhNLrpTDCecBw/Oa4aeUZ77ql2bOudD/phlHRJDT7ksoz9nFOX7eyLDkCG/QT5eH4FGTA34CHNw==", "requires": { "boom": "5.2.0", "hoek": "4.2.0" diff --git a/package.json b/package.json index 9827991..ecfd1af 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "boom": "5.x.x", "hoek": "4.x.x", "joi": "10.x.x", - "wreck": "12.x.x" + "wreck": "^12.5.0" }, "devDependencies": { "code": "4.x.x", From df799329b6186f16e930377d3c17e1d1330793b7 Mon Sep 17 00:00:00 2001 From: Sanjay Pandit Date: Tue, 12 Sep 2017 15:38:21 -0700 Subject: [PATCH 10/73] update expired cert, use tls 1.2 + a standard cipher for test --- test/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/index.js b/test/index.js index a3a459e..8c6e7d7 100644 --- a/test/index.js +++ b/test/index.js @@ -30,8 +30,8 @@ const expect = Code.expect; describe('H2o2', () => { const tlsOptions = { - key: '-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA0UqyXDCqWDKpoNQQK/fdr0OkG4gW6DUafxdufH9GmkX/zoKz\ng/SFLrPipzSGINKWtyMvo7mPjXqqVgE10LDI3VFV8IR6fnART+AF8CW5HMBPGt/s\nfQW4W4puvBHkBxWSW1EvbecgNEIS9hTGvHXkFzm4xJ2e9DHp2xoVAjREC73B7JbF\nhc5ZGGchKw+CFmAiNysU0DmBgQcac0eg2pWoT+YGmTeQj6sRXO67n2xy/hA1DuN6\nA4WBK3wM3O4BnTG0dNbWUEbe7yAbV5gEyq57GhJIeYxRvveVDaX90LoAqM4cUH06\n6rciON0UbDHV2LP/JaH5jzBjUyCnKLLo5snlbwIDAQABAoIBAQDJm7YC3pJJUcxb\nc8x8PlHbUkJUjxzZ5MW4Zb71yLkfRYzsxrTcyQA+g+QzA4KtPY8XrZpnkgm51M8e\n+B16AcIMiBxMC6HgCF503i16LyyJiKrrDYfGy2rTK6AOJQHO3TXWJ3eT3BAGpxuS\n12K2Cq6EvQLCy79iJm7Ks+5G6EggMZPfCVdEhffRm2Epl4T7LpIAqWiUDcDfS05n\nNNfAGxxvALPn+D+kzcSF6hpmCVrFVTf9ouhvnr+0DpIIVPwSK/REAF3Ux5SQvFuL\njPmh3bGwfRtcC5d21QNrHdoBVSN2UBLmbHUpBUcOBI8FyivAWJhRfKnhTvXMFG8L\nwaXB51IZAoGBAP/E3uz6zCyN7l2j09wmbyNOi1AKvr1WSmuBJveITouwblnRSdvc\nsYm4YYE0Vb94AG4n7JIfZLKtTN0xvnCo8tYjrdwMJyGfEfMGCQQ9MpOBXAkVVZvP\ne2k4zHNNsfvSc38UNSt7K0HkVuH5BkRBQeskcsyMeu0qK4wQwdtiCoBDAoGBANF7\nFMppYxSW4ir7Jvkh0P8bP/Z7AtaSmkX7iMmUYT+gMFB5EKqFTQjNQgSJxS/uHVDE\nSC5co8WGHnRk7YH2Pp+Ty1fHfXNWyoOOzNEWvg6CFeMHW2o+/qZd4Z5Fep6qCLaa\nFvzWWC2S5YslEaaP8DQ74aAX4o+/TECrxi0z2lllAoGAdRB6qCSyRsI/k4Rkd6Lv\nw00z3lLMsoRIU6QtXaZ5rN335Awyrfr5F3vYxPZbOOOH7uM/GDJeOJmxUJxv+cia\nPQDflpPJZU4VPRJKFjKcb38JzO6C3Gm+po5kpXGuQQA19LgfDeO2DNaiHZOJFrx3\nm1R3Zr/1k491lwokcHETNVkCgYBPLjrZl6Q/8BhlLrG4kbOx+dbfj/euq5NsyHsX\n1uI7bo1Una5TBjfsD8nYdUr3pwWltcui2pl83Ak+7bdo3G8nWnIOJ/WfVzsNJzj7\n/6CvUzR6sBk5u739nJbfgFutBZBtlSkDQPHrqA7j3Ysibl3ZIJlULjMRKrnj6Ans\npCDwkQKBgQCM7gu3p7veYwCZaxqDMz5/GGFUB1My7sK0hcT7/oH61yw3O8pOekee\nuctI1R3NOudn1cs5TAy/aypgLDYTUGQTiBRILeMiZnOrvQQB9cEf7TFgDoRNCcDs\nV/ZWiegVB/WY7H0BkCekuq5bHwjgtJTpvHGqQ9YD7RhE8RSYOhdQ/Q==\n-----END RSA PRIVATE KEY-----\n', - cert: '-----BEGIN CERTIFICATE-----\nMIIDBjCCAe4CCQDvLNml6smHlTANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJV\nUzETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0\ncyBQdHkgTHRkMB4XDTE0MDEyNTIxMjIxOFoXDTE1MDEyNTIxMjIxOFowRTELMAkG\nA1UEBhMCVVMxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0\nIFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nANFKslwwqlgyqaDUECv33a9DpBuIFug1Gn8Xbnx/RppF/86Cs4P0hS6z4qc0hiDS\nlrcjL6O5j416qlYBNdCwyN1RVfCEen5wEU/gBfAluRzATxrf7H0FuFuKbrwR5AcV\nkltRL23nIDRCEvYUxrx15Bc5uMSdnvQx6dsaFQI0RAu9weyWxYXOWRhnISsPghZg\nIjcrFNA5gYEHGnNHoNqVqE/mBpk3kI+rEVzuu59scv4QNQ7jegOFgSt8DNzuAZ0x\ntHTW1lBG3u8gG1eYBMquexoSSHmMUb73lQ2l/dC6AKjOHFB9Ouq3IjjdFGwx1diz\n/yWh+Y8wY1Mgpyiy6ObJ5W8CAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAoSc6Skb4\ng1e0ZqPKXBV2qbx7hlqIyYpubCl1rDiEdVzqYYZEwmst36fJRRrVaFuAM/1DYAmT\nWMhU+yTfA+vCS4tql9b9zUhPw/IDHpBDWyR01spoZFBF/hE1MGNpCSXXsAbmCiVf\naxrIgR2DNketbDxkQx671KwF1+1JOMo9ffXp+OhuRo5NaGIxhTsZ+f/MA4y084Aj\nDI39av50sTRTWWShlN+J7PtdQVA5SZD97oYbeUeL7gI18kAJww9eUdmT0nEjcwKs\nxsQT1fyKbo7AlZBY4KSlUMuGnn0VnAsB9b+LxtXlDfnjyM8bVQx1uAfRo0DO8p/5\n3J5DTjAU55deBQ==\n-----END CERTIFICATE-----\n' + key: '-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEA3IDFzxorKO8xWeCOosuK1pCPoTUMlhOkis4pWO9CLCv0o0Q7\nyUCZlHzPYWM49+QmWe5u3Xbl1rhkFsoeYowH1bts5r6HY8xYHexvU+6zEyxOU4Q7\nP7EXkFfW5h7WsO6uaEyEBVdniTIjK4c8hzjy7h6hNIvM+kEAAy1UFatMKmOwsp4Z\ns4+oCmS4ZPlItAMbRv/4a5DCopluOS7WN8UwwJ6zRrY8ZVFnkKPThflnwiaIy2Qh\nGgTwLANIUlWPQMh+LLHnV56NOlj1VUO03G+pKxTJ6ZkfYefaD41Ez4iPc7nyg4iD\njqnqFX+jYOLRoCktztYd9T43Sgb2sfgrlY0ENwIDAQABAoIBAQCoznyg/CumfteN\nMvh/cMutT6Zlh7NHAWqqSQImb6R9JHl4tDgA7k+k+ZfZuphWTnd9yadeLDPwmeEm\nAT4Zu5IT8hSA4cPMhxe+cM8ZtlepifW8wjKJpA2iF10RdvJtKYyjlFBNtogw5A1A\nuZuA+fwgh5pqG8ykmTZlOEJzBFye5Z7xKc/gwy9BGv3RLNVf+yaJCqPKLltkAxtu\nFmrBLuIZMoOJvT+btgVxHb/nRVzURKv5iKMY6t3JM84OSxNn0/tHpX2xTcqsVre+\nsdSokKGYoyzk/9miDYhoSVOrM3bU5/ygBDt1Pmf/iyK/MDO2P9tX9cEp/+enJc7a\nLg5O/XCBAoGBAPNwayF6DLu0PKErsdCG5dwGrxhC69+NBEJkVDMPMjSHXAQWneuy\n70H+t2QHxpDbi5wMze0ZClMlgs1wItm4/6iuvOn9HJczwiIG5yM9ZJo+OFIqlBq3\n1vQG+oEXe5VpTfpyQihxqTSiMuCXkTYtNjneHseXWAjFuUQe9AOxxzNRAoGBAOfh\nZEEDY7I1Ppuz7bG1D6lmzYOTZZFfMCVGGTrYmam02+rS8NC+MT0wRFCblQ0E7SzM\nr9Bv2vbjrLY5fCe/yscF+/u/UHJu1dR7j62htdYeSi7XbQiSwyUm1QkMXjKDQPUw\njwR3WO8ZHQf2tywE+7iRs/bJ++Oolaw03HoIp40HAoGBAJJwGpGduJElH5+YCDO3\nIghUIPnIL9lfG6PQdHHufzXoAusWq9J/5brePXU31DOJTZcGgM1SVcqkcuWfwecU\niP3wdwWOU6eE5A/R9TJWmPDL4tdSc5sK4YwTspb7CEVdfiHcn31yueVGeLJvmlNr\nqQXwXrWTjcphHkwjDog2ZeyxAoGBAJ5Yyq+i8uf1eEW3v3AFZyaVr25Ur51wVV5+\n2ifXVkgP28YmOpEx8EoKtfwd4tE7NgPL25wJZowGuiDObLxwOrdinMszwGoEyj0K\nC/nUXmpT0PDf5/Nc1ap/NCezrHfuLePCP0gbgD329l5D2p5S4NsPlMfI8xxqOZuZ\nlZ44XsLtAoGADiM3cnCZ6x6/e5UQGfXa6xN7KoAkjjyO+0gu2AF0U0jDFemu1BNQ\nCRpe9zVX9AJ9XEefNUGfOI4bhRR60RTJ0lB5Aeu1xAT/OId0VTu1wRrbcnwMHGOo\nf7Kk1Vk5+1T7f1QbTu/q4ddp22PEt2oGJ7widRTZrr/gtH2wYUEjMVQ=\n-----END RSA PRIVATE KEY-----\n', + cert: '-----BEGIN CERTIFICATE-----\nMIIC+zCCAeOgAwIBAgIJANnDRcmEqJssMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNV\nBAMMCWxvY2FsaG9zdDAeFw0xNzA5MTIyMjMxMDRaFw0yNzA5MTAyMjMxMDRaMBQx\nEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\nggEBANyAxc8aKyjvMVngjqLLitaQj6E1DJYTpIrOKVjvQiwr9KNEO8lAmZR8z2Fj\nOPfkJlnubt125da4ZBbKHmKMB9W7bOa+h2PMWB3sb1PusxMsTlOEOz+xF5BX1uYe\n1rDurmhMhAVXZ4kyIyuHPIc48u4eoTSLzPpBAAMtVBWrTCpjsLKeGbOPqApkuGT5\nSLQDG0b/+GuQwqKZbjku1jfFMMCes0a2PGVRZ5Cj04X5Z8ImiMtkIRoE8CwDSFJV\nj0DIfiyx51eejTpY9VVDtNxvqSsUyemZH2Hn2g+NRM+Ij3O58oOIg46p6hV/o2Di\n0aApLc7WHfU+N0oG9rH4K5WNBDcCAwEAAaNQME4wHQYDVR0OBBYEFJBSho+nF530\nsxpoBxYqD/ynn/t0MB8GA1UdIwQYMBaAFJBSho+nF530sxpoBxYqD/ynn/t0MAwG\nA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAJFAh3X5CYFAl0cI6Q7Vcp4H\nO0S8s/C4FHNIsyUu54NcRH3taUwn3Fshn5LiwaEdFmouALbxMaejvEVw7hVBtY9X\nOjqt0mZ6+X6GOFhoUvlaG1c7YLOk5x51TXchg8YD2wxNXS0rOrAdZaScOsy8Q62S\nHehBJMN19JK8TiR3XXzxKVNcFcg0wyQvCGgjrHReaUF8WePfWHtZDdP01kBmMEIo\n6wY7E3jFqvDUs33vTOB5kmWixIoJKmkgOVmbgchmu7z27n3J+fawNr2r4IwjdUpK\nc1KvFYBXLiT+2UVkOJbBZ3C8mKfhXKHs2CrI3cSa4+E0sxTy4joG/yzlRs5l954=\n-----END CERTIFICATE-----\n' }; const provisionServer = function (options) { @@ -1854,7 +1854,7 @@ describe('H2o2', () => { const server = new Hapi.Server(); server.connection({}); - server.register({ register: H2o2.register, options: { secureProtocol: 'TLSv1_1_method', ciphers: 'DES-CBC3-SHA' } }); + server.register({ register: H2o2.register, options: { secureProtocol: 'TLSv1_2_method', ciphers: 'ECDHE-RSA-AES128-SHA256' } }); server.route({ method: 'GET', From d8d8e9b5fdaaf516460d5eaa8c29a0fe0c52e2c2 Mon Sep 17 00:00:00 2001 From: Sanjay Pandit Date: Tue, 12 Sep 2017 16:10:00 -0700 Subject: [PATCH 11/73] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ecfd1af..9827991 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "boom": "5.x.x", "hoek": "4.x.x", "joi": "10.x.x", - "wreck": "^12.5.0" + "wreck": "12.x.x" }, "devDependencies": { "code": "4.x.x", From 2f56ebcb3f6c920fd7a42bca23a1042c593d9a0a Mon Sep 17 00:00:00 2001 From: Oscar Funes Date: Thu, 14 Sep 2017 14:28:16 -0600 Subject: [PATCH 12/73] 6.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9827991..26c3746 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "h2o2", "description": "Proxy handler plugin for hapi.js", - "version": "6.0.0", + "version": "6.0.1", "repository": "git://github.com/hapijs/h2o2", "main": "lib/index.js", "keywords": [ From ec5a02e4138709d049d36c347352c6d3076f21ca Mon Sep 17 00:00:00 2001 From: Oscar Funes Date: Mon, 16 Oct 2017 18:45:22 -0600 Subject: [PATCH 13/73] fix aplication of settings before doing assertions. Update package-lock.json Closes #59 --- lib/index.js | 2 +- package-lock.json | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/index.js b/lib/index.js index eb0fdea..36d54e6 100644 --- a/lib/index.js +++ b/lib/index.js @@ -71,9 +71,9 @@ exports.register.attributes = { internals.handler = function (route, handlerOptions) { + const settings = Hoek.applyToDefaultsWithShallow(internals.defaults, handlerOptions, ['agent']); Joi.assert(handlerOptions, internals.schema, 'Invalid proxy handler options (' + route.path + ')'); Hoek.assert(!route.settings.payload || ((route.settings.payload.output === 'data' || route.settings.payload.output === 'stream') && !route.settings.payload.parse), 'Cannot proxy if payload is parsed or if output is not stream or data'); - const settings = Hoek.applyToDefaultsWithShallow(internals.defaults, handlerOptions, ['agent']); settings.mapUri = handlerOptions.mapUri || internals.mapUri(handlerOptions.protocol, handlerOptions.host, handlerOptions.port, handlerOptions.uri); if (settings.ttl === 'upstream') { diff --git a/package-lock.json b/package-lock.json index 5a6d69c..233c220 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "h2o2", - "version": "6.0.0", + "version": "6.0.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1517,15 +1517,6 @@ "joi": "10.6.0" } }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -1553,6 +1544,15 @@ } } }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", From b7ea3b2a529d0b352e2690a07208868b3823d608 Mon Sep 17 00:00:00 2001 From: Sanjay Pandit Date: Sun, 26 Nov 2017 15:40:29 -0800 Subject: [PATCH 14/73] remove package-lock.json --- package-lock.json | 1766 --------------------------------------------- 1 file changed, 1766 deletions(-) delete mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 233c220..0000000 --- a/package-lock.json +++ /dev/null @@ -1,1766 +0,0 @@ -{ - "name": "h2o2", - "version": "6.0.1", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "accept": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/accept/-/accept-2.1.4.tgz", - "integrity": "sha1-iHr1TO7lx/RDBGGXHsQAxh0JrLs=", - "dev": true, - "requires": { - "boom": "5.2.0", - "hoek": "4.2.0" - } - }, - "acorn": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.1.tgz", - "integrity": "sha512-vOk6uEMctu0vQrvuSqFdJyqj1Q0S5VTDL79qtjo+DhRr+1mmaD+tluFSCZqhvi/JUhXSzoZN2BhtstaPEeE8cw==", - "dev": true - }, - "acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", - "dev": true, - "requires": { - "acorn": "3.3.0" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } - } - }, - "ajv": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.2.2.tgz", - "integrity": "sha1-R8aNaehvXZUxA7AHSpQw3GPaXjk=", - "dev": true, - "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.0.0", - "json-schema-traverse": "0.3.1", - "json-stable-stringify": "1.0.1" - } - }, - "ajv-keywords": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", - "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", - "dev": true - }, - "align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "dev": true, - "requires": { - "kind-of": "3.2.2", - "longest": "1.0.1", - "repeat-string": "1.6.1" - } - }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", - "dev": true - }, - "ammo": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/ammo/-/ammo-2.0.4.tgz", - "integrity": "sha1-v4CqshFpjqePY+9efxE91dnokX8=", - "dev": true, - "requires": { - "boom": "5.2.0", - "hoek": "4.2.0" - } - }, - "ansi-escapes": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-2.0.0.tgz", - "integrity": "sha1-W65SvkJIeN2Xg+iRDj/Cki6DyBs=", - "dev": true - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "argparse": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", - "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", - "dev": true, - "requires": { - "sprintf-js": "1.0.3" - } - }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, - "requires": { - "array-uniq": "1.0.3" - } - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true - }, - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, - "b64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/b64/-/b64-3.0.3.tgz", - "integrity": "sha512-Pbeh0i6OLubPJdIdCepn8ZQHwN2MWznZHbHABSTEfQ706ie+yuxNSaPdqX1xRatT6WanaS1EazMiSg0NUW2XxQ==", - "dev": true - }, - "babel-code-frame": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz", - "integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ=", - "dev": true, - "requires": { - "chalk": "1.1.3", - "esutils": "2.0.2", - "js-tokens": "3.0.2" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "boom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", - "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", - "requires": { - "hoek": "4.2.0" - } - }, - "bossy": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/bossy/-/bossy-3.0.4.tgz", - "integrity": "sha1-+a6fJugbQaMY9O4Ng2huSlwlB7k=", - "dev": true, - "requires": { - "hoek": "4.2.0", - "joi": "10.6.0" - } - }, - "brace-expansion": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", - "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", - "dev": true, - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "call": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/call/-/call-4.0.2.tgz", - "integrity": "sha1-33b19R7o3Ui4VqyEAPfmnm1zmcQ=", - "dev": true, - "requires": { - "boom": "5.2.0", - "hoek": "4.2.0" - } - }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", - "dev": true, - "requires": { - "callsites": "0.2.0" - } - }, - "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", - "dev": true - }, - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", - "dev": true, - "optional": true - }, - "catbox": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/catbox/-/catbox-7.1.5.tgz", - "integrity": "sha512-4fui5lELzqZ+9cnaAP/BcqXTH6LvWLBRtFhJ0I4FfgfXiSaZcf6k9m9dqOyChiTxNYtvLk7ZMYSf7ahMq3bf5A==", - "dev": true, - "requires": { - "boom": "5.2.0", - "hoek": "4.2.0", - "joi": "10.6.0" - } - }, - "catbox-memory": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/catbox-memory/-/catbox-memory-2.0.4.tgz", - "integrity": "sha1-Qz4lWQLK9UIz0ShkKcj03xToItU=", - "dev": true, - "requires": { - "hoek": "4.2.0" - } - }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", - "dev": true, - "optional": true, - "requires": { - "align-text": "0.1.4", - "lazy-cache": "1.0.4" - } - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "dev": true - }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "dev": true, - "requires": { - "restore-cursor": "2.0.0" - } - }, - "cli-width": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.1.0.tgz", - "integrity": "sha1-sjTKIJsp72b8UY2bmNWEewDt8Ao=", - "dev": true - }, - "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", - "dev": true, - "optional": true, - "requires": { - "center-align": "0.1.3", - "right-align": "0.1.3", - "wordwrap": "0.0.2" - }, - "dependencies": { - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", - "dev": true, - "optional": true - } - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "code": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/code/-/code-4.1.0.tgz", - "integrity": "sha1-IJrRHQWvigwceq9pTZ+k0sfZW4U=", - "dev": true, - "requires": { - "hoek": "4.2.0" - } - }, - "color-convert": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.0.tgz", - "integrity": "sha1-Gsz5fdc5uYO/mU1W/sj5WFNkG3o=", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "concat-stream": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", - "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", - "dev": true, - "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.3", - "typedarray": "0.0.6" - } - }, - "content": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/content/-/content-3.0.5.tgz", - "integrity": "sha512-MQVYZuNnm5N0xalwtRGlZrcKFwgE6VKXCEh3XkCeoWUo3gL5BS52UiqswRvAwQW1ocfdCNkEKI5uy0pNGax+IQ==", - "dev": true, - "requires": { - "boom": "5.2.0" - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "4.1.1", - "shebang-command": "1.2.0", - "which": "1.3.0" - } - }, - "cryptiles": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", - "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", - "dev": true, - "requires": { - "boom": "5.2.0" - } - }, - "debug": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", - "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true, - "optional": true - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", - "dev": true, - "requires": { - "globby": "5.0.0", - "is-path-cwd": "1.0.0", - "is-path-in-cwd": "1.0.0", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "rimraf": "2.6.1" - } - }, - "diff": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.0.tgz", - "integrity": "sha512-w0XZubFWn0Adlsapj9EAWX0FqWdO4tz8kc3RiYdWLh4k/V8PTb6i0SMgXt0vRM3zyKnT8tKO7mUlieRQHIjMNg==", - "dev": true - }, - "doctrine": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.0.0.tgz", - "integrity": "sha1-xz2NKQnSIpHhoAejlYBNqLZl/mM=", - "dev": true, - "requires": { - "esutils": "2.0.2", - "isarray": "1.0.0" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "eslint": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.4.1.tgz", - "integrity": "sha1-mc1+r8/8ov+Zpcj18qR01jZLS9M=", - "dev": true, - "requires": { - "ajv": "5.2.2", - "babel-code-frame": "6.22.0", - "chalk": "1.1.3", - "concat-stream": "1.6.0", - "cross-spawn": "5.1.0", - "debug": "2.6.8", - "doctrine": "2.0.0", - "eslint-scope": "3.7.1", - "espree": "3.5.0", - "esquery": "1.0.0", - "estraverse": "4.2.0", - "esutils": "2.0.2", - "file-entry-cache": "2.0.0", - "functional-red-black-tree": "1.0.1", - "glob": "7.1.2", - "globals": "9.18.0", - "ignore": "3.3.3", - "imurmurhash": "0.1.4", - "inquirer": "3.2.1", - "is-resolvable": "1.0.0", - "js-yaml": "3.9.1", - "json-stable-stringify": "1.0.1", - "levn": "0.3.0", - "lodash": "4.17.4", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "natural-compare": "1.4.0", - "optionator": "0.8.2", - "path-is-inside": "1.0.2", - "pluralize": "4.0.0", - "progress": "2.0.0", - "require-uncached": "1.0.3", - "semver": "5.4.1", - "strip-json-comments": "2.0.1", - "table": "4.0.1", - "text-table": "0.2.0" - } - }, - "eslint-config-hapi": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-hapi/-/eslint-config-hapi-10.0.0.tgz", - "integrity": "sha1-mYCv/XYQPrwf7JK0Vjg0XbGTSPU=", - "dev": true - }, - "eslint-plugin-hapi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-hapi/-/eslint-plugin-hapi-4.0.0.tgz", - "integrity": "sha1-RKouRfeTmlI5Kc2DK7mqEpqV6CM=", - "dev": true, - "requires": { - "hapi-capitalize-modules": "1.1.6", - "hapi-for-you": "1.0.0", - "hapi-scope-start": "2.1.1", - "no-arrowception": "1.0.0" - } - }, - "eslint-scope": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", - "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", - "dev": true, - "requires": { - "esrecurse": "4.2.0", - "estraverse": "4.2.0" - } - }, - "espree": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.0.tgz", - "integrity": "sha1-mDWGJb3QVYYeon4oZ+pyn69GPY0=", - "dev": true, - "requires": { - "acorn": "5.1.1", - "acorn-jsx": "3.0.1" - } - }, - "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", - "dev": true - }, - "esquery": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", - "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", - "dev": true, - "requires": { - "estraverse": "4.2.0" - } - }, - "esrecurse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", - "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", - "dev": true, - "requires": { - "estraverse": "4.2.0", - "object-assign": "4.1.1" - } - }, - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true - }, - "external-editor": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.0.4.tgz", - "integrity": "sha1-HtkZnanL/i7y96MbL96LDRI2iXI=", - "dev": true, - "requires": { - "iconv-lite": "0.4.18", - "jschardet": "1.5.1", - "tmp": "0.0.31" - } - }, - "fast-deep-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", - "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "dev": true, - "requires": { - "escape-string-regexp": "1.0.5" - } - }, - "file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", - "dev": true, - "requires": { - "flat-cache": "1.2.2", - "object-assign": "4.1.1" - } - }, - "find-rc": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/find-rc/-/find-rc-3.0.1.tgz", - "integrity": "sha1-VKQXg3DxC8k3H6jRssKAmir6DM4=", - "dev": true - }, - "flat-cache": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.2.2.tgz", - "integrity": "sha1-+oZxTnLCHbiGAXYezy9VXRq8a5Y=", - "dev": true, - "requires": { - "circular-json": "0.3.3", - "del": "2.2.2", - "graceful-fs": "4.1.11", - "write": "0.2.1" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", - "dev": true, - "requires": { - "array-union": "1.0.2", - "arrify": "1.0.1", - "glob": "7.1.2", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" - } - }, - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true - }, - "handlebars": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.10.tgz", - "integrity": "sha1-PTDHGLCaPZbyPqTMH0A8TTup/08=", - "dev": true, - "requires": { - "async": "1.5.2", - "optimist": "0.6.1", - "source-map": "0.4.4", - "uglify-js": "2.8.29" - }, - "dependencies": { - "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", - "dev": true, - "requires": { - "amdefine": "1.0.1" - } - } - } - }, - "hapi": { - "version": "16.5.2", - "resolved": "https://registry.npmjs.org/hapi/-/hapi-16.5.2.tgz", - "integrity": "sha512-8aXEiC2IT1bfF/5RYz9LtWqm99oGVDd2JcQ3KRRvFGDngEE35kZl9Bk2jproY97fBrmAlvkNrmQKWf3jb2lNxw==", - "dev": true, - "requires": { - "accept": "2.1.4", - "ammo": "2.0.4", - "boom": "5.2.0", - "call": "4.0.2", - "catbox": "7.1.5", - "catbox-memory": "2.0.4", - "cryptiles": "3.1.2", - "heavy": "4.0.4", - "hoek": "4.2.0", - "iron": "4.0.5", - "items": "2.1.1", - "joi": "10.6.0", - "mimos": "3.0.3", - "podium": "1.3.0", - "shot": "3.4.2", - "statehood": "5.0.3", - "subtext": "5.0.0", - "topo": "2.0.2" - } - }, - "hapi-capitalize-modules": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/hapi-capitalize-modules/-/hapi-capitalize-modules-1.1.6.tgz", - "integrity": "sha1-eZEXFBXhXmqjIx5k3ac8gUZmUxg=", - "dev": true - }, - "hapi-for-you": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hapi-for-you/-/hapi-for-you-1.0.0.tgz", - "integrity": "sha1-02L77o172pwseAHiB+WlzRoLans=", - "dev": true - }, - "hapi-scope-start": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/hapi-scope-start/-/hapi-scope-start-2.1.1.tgz", - "integrity": "sha1-dJWnJv5yt7yo3izcwdh82M5qtPI=", - "dev": true - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", - "dev": true - }, - "heavy": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/heavy/-/heavy-4.0.4.tgz", - "integrity": "sha1-NskTNsAMz+hSyqTRUwhjNc0vAOk=", - "dev": true, - "requires": { - "boom": "5.2.0", - "hoek": "4.2.0", - "joi": "10.6.0" - } - }, - "hoek": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.0.tgz", - "integrity": "sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==" - }, - "iconv-lite": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz", - "integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA==", - "dev": true - }, - "ignore": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.3.tgz", - "integrity": "sha1-QyNS5XrM2HqzEQ6C0/6g5HgSFW0=", - "dev": true - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inert": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/inert/-/inert-4.2.1.tgz", - "integrity": "sha512-qmbbZYPSzU/eOUOStPQvSjrU9IR1Q3uDtsEsVwnBQeZG43xu7Nrj6yuUrX3ice/03rv5dj/KiKB+NGCbiqH+aQ==", - "dev": true, - "requires": { - "ammo": "2.0.4", - "boom": "5.2.0", - "hoek": "4.2.0", - "items": "2.1.1", - "joi": "10.6.0", - "lru-cache": "4.1.1" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "inquirer": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.2.1.tgz", - "integrity": "sha512-QgW3eiPN8gpj/K5vVpHADJJgrrF0ho/dZGylikGX7iqAdRgC9FVKYKWFLx6hZDBFcOLEoSqINYrVPeFAeG/PdA==", - "dev": true, - "requires": { - "ansi-escapes": "2.0.0", - "chalk": "2.1.0", - "cli-cursor": "2.1.0", - "cli-width": "2.1.0", - "external-editor": "2.0.4", - "figures": "2.0.0", - "lodash": "4.17.4", - "mute-stream": "0.0.7", - "run-async": "2.3.0", - "rx-lite": "4.0.8", - "rx-lite-aggregates": "4.0.8", - "string-width": "2.1.1", - "strip-ansi": "4.0.0", - "through": "2.3.8" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", - "dev": true, - "requires": { - "color-convert": "1.9.0" - } - }, - "chalk": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.1.0.tgz", - "integrity": "sha512-LUHGS/dge4ujbXMJrnihYMcL4AoOweGnw9Tp3kQuqy1Kx5c1qKjqvMJZ6nVJPMWJtKCTN72ZogH3oeSO9g9rXQ==", - "dev": true, - "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.2.1" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - }, - "supports-color": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.2.1.tgz", - "integrity": "sha512-qxzYsob3yv6U+xMzPrv170y8AwGP7i74g+pbixCfD6rgso8BscLT2qXIuz6TpOaiJZ3mFgT5O9lyT9nMU4LfaA==", - "dev": true, - "requires": { - "has-flag": "2.0.0" - } - } - } - }, - "iron": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/iron/-/iron-4.0.5.tgz", - "integrity": "sha1-TwQszri5c480a1mqc0yDqJvDFCg=", - "dev": true, - "requires": { - "boom": "5.2.0", - "cryptiles": "3.1.2", - "hoek": "4.2.0" - } - }, - "is-buffer": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", - "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", - "dev": true - }, - "is-path-in-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", - "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", - "dev": true, - "requires": { - "is-path-inside": "1.0.0" - } - }, - "is-path-inside": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz", - "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=", - "dev": true, - "requires": { - "path-is-inside": "1.0.2" - } - }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true - }, - "is-resolvable": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz", - "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=", - "dev": true, - "requires": { - "tryit": "1.0.3" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isemail": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/isemail/-/isemail-2.2.1.tgz", - "integrity": "sha1-A1PT2aYpUQgMJiwqoKQrjqjp4qY=" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "items": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/items/-/items-2.1.1.tgz", - "integrity": "sha1-i9FtnIOxlSneWuoyGsqtp4NkoZg=" - }, - "joi": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-10.6.0.tgz", - "integrity": "sha512-hBF3LcqyAid+9X/pwg+eXjD2QBZI5eXnBFJYaAkH4SK3mp9QSRiiQnDYlmlz5pccMvnLcJRS4whhDOTCkmsAdQ==", - "requires": { - "hoek": "4.2.0", - "isemail": "2.2.1", - "items": "2.1.1", - "topo": "2.0.2" - } - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "js-yaml": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.9.1.tgz", - "integrity": "sha512-CbcG379L1e+mWBnLvHWWeLs8GyV/EMw862uLI3c+GxVyDHWZcjZinwuBd3iW2pgxgIlksW/1vNJa4to+RvDOww==", - "dev": true, - "requires": { - "argparse": "1.0.9", - "esprima": "4.0.0" - } - }, - "jschardet": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/jschardet/-/jschardet-1.5.1.tgz", - "integrity": "sha512-vE2hT1D0HLZCLLclfBSfkfTTedhVj0fubHpJBHKwwUWX0nSbhPAfk+SG9rTX95BYNmau8rGFfCeaT6T5OW1C2A==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", - "dev": true - }, - "json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "dev": true, - "requires": { - "jsonify": "0.0.0" - } - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", - "dev": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.5" - } - }, - "lab": { - "version": "14.1.2", - "resolved": "https://registry.npmjs.org/lab/-/lab-14.1.2.tgz", - "integrity": "sha512-Sfgv3ABgcLHYuy5sal7litun4xat1sMSKKRzVi+8fgZ3CDhSzrPCn0SyOOZokGuFJ3XER2or/TmGBCy72h9usA==", - "dev": true, - "requires": { - "bossy": "3.0.4", - "code": "4.1.0", - "diff": "3.3.0", - "eslint": "4.4.1", - "eslint-config-hapi": "10.0.0", - "eslint-plugin-hapi": "4.0.0", - "espree": "3.5.0", - "find-rc": "3.0.1", - "handlebars": "4.0.10", - "hoek": "4.2.0", - "items": "2.1.1", - "json-stable-stringify": "1.0.1", - "json-stringify-safe": "5.0.1", - "mkdirp": "0.5.1", - "seedrandom": "2.4.3", - "source-map": "0.5.6", - "source-map-support": "0.4.15" - } - }, - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", - "dev": true, - "optional": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "1.1.2", - "type-check": "0.3.2" - } - }, - "lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", - "dev": true - }, - "longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", - "dev": true - }, - "lru-cache": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", - "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", - "dev": true, - "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" - } - }, - "mime-db": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.29.0.tgz", - "integrity": "sha1-SNJtI1WJZRcErFkWygYAGRQmaHg=", - "dev": true - }, - "mimic-fn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz", - "integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg=", - "dev": true - }, - "mimos": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/mimos/-/mimos-3.0.3.tgz", - "integrity": "sha1-uRCQcq03jCty9qAQHEPd+ys2ZB8=", - "dev": true, - "requires": { - "hoek": "4.2.0", - "mime-db": "1.29.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "1.1.8" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "nigel": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/nigel/-/nigel-2.0.2.tgz", - "integrity": "sha1-k6GGb7DFLYc5CqdeKxYfS1x15bE=", - "dev": true, - "requires": { - "hoek": "4.2.0", - "vise": "2.0.2" - } - }, - "no-arrowception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/no-arrowception/-/no-arrowception-1.0.0.tgz", - "integrity": "sha1-W/PpXrnEG1c4SoBTM9qjtzTuMno=", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "1.1.0" - } - }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "requires": { - "minimist": "0.0.8", - "wordwrap": "0.0.3" - }, - "dependencies": { - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true - } - } - }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "dev": true, - "requires": { - "deep-is": "0.1.3", - "fast-levenshtein": "2.0.6", - "levn": "0.3.0", - "prelude-ls": "1.1.2", - "type-check": "0.3.2", - "wordwrap": "1.0.0" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "pez": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/pez/-/pez-2.1.5.tgz", - "integrity": "sha1-XsLMYlAMw+tCNtSkFM9aF7XrUAc=", - "dev": true, - "requires": { - "b64": "3.0.3", - "boom": "5.2.0", - "content": "3.0.5", - "hoek": "4.2.0", - "nigel": "2.0.2" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "requires": { - "pinkie": "2.0.4" - } - }, - "pluralize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-4.0.0.tgz", - "integrity": "sha1-WbcIwcAZCi9pLxx2GMRGsFL9F2I=", - "dev": true - }, - "podium": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/podium/-/podium-1.3.0.tgz", - "integrity": "sha512-ZIujqk1pv8bRZNVxwwwq0BhXilZ2udycQT3Kp8ah3f3TcTmVg7ILJsv/oLf47gRa2qeiP584lNq+pfvS9U3aow==", - "dev": true, - "requires": { - "hoek": "4.2.0", - "items": "2.1.1", - "joi": "10.6.0" - } - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", - "dev": true - }, - "progress": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", - "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", - "dev": true - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, - "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", - "dev": true, - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" - } - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "require-uncached": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "dev": true, - "requires": { - "caller-path": "0.1.0", - "resolve-from": "1.0.1" - } - }, - "resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", - "dev": true - }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "dev": true, - "requires": { - "onetime": "2.0.1", - "signal-exit": "3.0.2" - } - }, - "right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "dev": true, - "optional": true, - "requires": { - "align-text": "0.1.4" - } - }, - "rimraf": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.1.tgz", - "integrity": "sha1-wjOOxkPfeht/5cVPqG9XQopV8z0=", - "dev": true, - "requires": { - "glob": "7.1.2" - } - }, - "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", - "dev": true, - "requires": { - "is-promise": "2.1.0" - } - }, - "rx-lite": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", - "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=", - "dev": true - }, - "rx-lite-aggregates": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", - "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", - "dev": true, - "requires": { - "rx-lite": "4.0.8" - } - }, - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", - "dev": true - }, - "seedrandom": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.3.tgz", - "integrity": "sha1-JDhQTa0zkXMUv/GKxNeU8W1qrsw=", - "dev": true - }, - "semver": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", - "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "shot": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/shot/-/shot-3.4.2.tgz", - "integrity": "sha1-Hlw/bysmZJrcQvfrNQIUpaApHWc=", - "dev": true, - "requires": { - "hoek": "4.2.0", - "joi": "10.6.0" - } - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true - }, - "slice-ansi": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", - "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", - "dev": true - }, - "source-map": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", - "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", - "dev": true - }, - "source-map-support": { - "version": "0.4.15", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.15.tgz", - "integrity": "sha1-AyAt9lwG0r2MfsI2KhkwVv7407E=", - "dev": true, - "requires": { - "source-map": "0.5.6" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "statehood": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/statehood/-/statehood-5.0.3.tgz", - "integrity": "sha512-YrPrCt10t3ImH/JMO5szSwX7sCm8HoqVl3VFLOa9EZ1g/qJx/ZmMhN+2uzPPB/vaU6hpkJpXxcBWsgIkkG+MXA==", - "dev": true, - "requires": { - "boom": "5.2.0", - "cryptiles": "3.1.2", - "hoek": "4.2.0", - "iron": "4.0.5", - "items": "2.1.1", - "joi": "10.6.0" - } - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "3.0.0" - } - } - } - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "subtext": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/subtext/-/subtext-5.0.0.tgz", - "integrity": "sha512-2nXG1G1V+K64Z20cQII7k0s38J2DSycMXBLMAk9RXUFG0uAkAbLSVoa88croX9VhTdBCJbLAe9g6LmzKwpJhhQ==", - "dev": true, - "requires": { - "boom": "5.2.0", - "content": "3.0.5", - "hoek": "4.2.0", - "pez": "2.1.5", - "wreck": "12.5.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "table": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.1.tgz", - "integrity": "sha1-qBFsEz+sLGH0pCCrbN9cTWHw5DU=", - "dev": true, - "requires": { - "ajv": "4.11.8", - "ajv-keywords": "1.5.1", - "chalk": "1.1.3", - "lodash": "4.17.4", - "slice-ansi": "0.0.4", - "string-width": "2.1.1" - }, - "dependencies": { - "ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", - "dev": true, - "requires": { - "co": "4.6.0", - "json-stable-stringify": "1.0.1" - } - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "tmp": { - "version": "0.0.31", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz", - "integrity": "sha1-jzirlDjhcxXl29izZX6L+yd65Kc=", - "dev": true, - "requires": { - "os-tmpdir": "1.0.2" - } - }, - "topo": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/topo/-/topo-2.0.2.tgz", - "integrity": "sha1-zVYVdSU5BXwNwEkaYhw7xvvh0YI=", - "requires": { - "hoek": "4.2.0" - } - }, - "tryit": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", - "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "1.1.2" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", - "dev": true, - "optional": true, - "requires": { - "source-map": "0.5.6", - "uglify-to-browserify": "1.0.2", - "yargs": "3.10.0" - } - }, - "uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", - "dev": true, - "optional": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "vise": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/vise/-/vise-2.0.2.tgz", - "integrity": "sha1-awjo+0y3bjpQzW3Q7DczjoEaDTk=", - "dev": true, - "requires": { - "hoek": "4.2.0" - } - }, - "which": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", - "dev": true, - "requires": { - "isexe": "2.0.0" - } - }, - "window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", - "dev": true, - "optional": true - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "wreck": { - "version": "12.5.0", - "resolved": "https://registry.npmjs.org/wreck/-/wreck-12.5.0.tgz", - "integrity": "sha512-O2wljTGaQhlhhNLrpTDCecBw/Oa4aeUZ77ql2bOudD/phlHRJDT7ksoz9nFOX7eyLDkCG/QT5eH4FGTA34CHNw==", - "requires": { - "boom": "5.2.0", - "hoek": "4.2.0" - } - }, - "write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", - "dev": true, - "requires": { - "mkdirp": "0.5.1" - } - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - }, - "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", - "dev": true, - "optional": true, - "requires": { - "camelcase": "1.2.1", - "cliui": "2.1.0", - "decamelize": "1.2.0", - "window-size": "0.1.0" - } - } - } -} From 8647dde70b26883ec9145abee6bc0389f4680940 Mon Sep 17 00:00:00 2001 From: Sanjay Pandit Date: Sun, 26 Nov 2017 15:40:34 -0800 Subject: [PATCH 15/73] 6.0.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 26c3746..77bb219 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "h2o2", "description": "Proxy handler plugin for hapi.js", - "version": "6.0.1", + "version": "6.0.2", "repository": "git://github.com/hapijs/h2o2", "main": "lib/index.js", "keywords": [ From 50239be6fa153310fb0e8cc9741a0ec452134c51 Mon Sep 17 00:00:00 2001 From: Sanjay Pandit Date: Mon, 27 Nov 2017 10:37:31 -0800 Subject: [PATCH 16/73] 6.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 77bb219..26bf335 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "h2o2", "description": "Proxy handler plugin for hapi.js", - "version": "6.0.2", + "version": "6.1.0", "repository": "git://github.com/hapijs/h2o2", "main": "lib/index.js", "keywords": [ From 17e77c58c4f9ca0163e512cbe9bb4c8eccbcd211 Mon Sep 17 00:00:00 2001 From: Sanjay Pandit Date: Mon, 27 Nov 2017 10:43:53 -0800 Subject: [PATCH 17/73] update readme --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ae02f7c..1485af2 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Proxy handler plugin for hapi.js. [![Build Status](https://secure.travis-ci.org/hapijs/h2o2.png)](http://travis-ci.org/hapijs/h2o2) -Lead Maintainer - [Oscar A. Funes Martinez](https://github.com/osukaa) +Lead Maintainer - [Sanjay Pandit](https://github.com/spanditcaa) ## Introduction @@ -61,6 +61,8 @@ The proxy handler object has the following properties: * `err` - internal error condition. * `uri` - the absolute proxy URI. * `headers` - optional object where each key is an HTTP request header and the value is the header content. +* `onRequest` - a custom function which is passed the upstream request. Function signature is `function (req)` where: + * `req` - the [wreck] (https://github.com/hapijs/wreck) request to the upstream server. * `onResponse` - a custom function for processing the response from the upstream service before sending to the client. Useful for custom error handling of responses from the proxied endpoint or other payload manipulation. Function signature is `function (err, res, request, reply, settings, ttl)` where: * `err` - internal or upstream error returned from attempting to contact the upstream proxy. * `res` - the node response object received from the upstream service. `res` is a readable stream (use the [wreck](https://github.com/hapijs/wreck) module `read` method to easily convert it to a Buffer or string). From 172155a54a37565d3ddb7b2ec8c01940295b3cb2 Mon Sep 17 00:00:00 2001 From: Sanjay Pandit Date: Mon, 27 Nov 2017 12:20:18 -0800 Subject: [PATCH 18/73] merge Samueljoli-hapiv17 --- .travis.yml | 4 +- README.md | 48 +- lib/index.js | 199 +++-- package.json | 20 +- test/index.js | 1967 ++++++++++++++++++++++--------------------------- 5 files changed, 1034 insertions(+), 1204 deletions(-) diff --git a/.travis.yml b/.travis.yml index 22c8dc2..a3ed993 100755 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: node_js node_js: - - "lts/argon" - - "lts/boron" + - "8" + - "9" - "node" sudo: false diff --git a/README.md b/README.md index 1485af2..029efad 100644 --- a/README.md +++ b/README.md @@ -13,29 +13,31 @@ Lead Maintainer - [Sanjay Pandit](https://github.com/spanditcaa) **h2o2** is a hapi plugin that adds proxying functionality. ## Manual loading +H2o2 version 7 requires Hapi 17. For use with Hapi v16.x.x, please use H2o2 @v6.x.x Starting on version 9, `hapi` does not load the `h2o2` automatically. To add `h2o2` to your server, you should register it normally. ```javascript const Hapi = require('hapi'); -const server = new Hapi.Server(); +const server = Hapi.server(); -server.register({ - register: require('h2o2') -}, function (err) { - - if (err) { - console.log('Failed to load h2o2'); - } - - server.start(function (err) { - - console.log('Server started at: ' + server.info.uri); - }); -}); +const startServer = async function() { + try { + await server.register({ plugin: require('h2o2') }); + await server.start(); + + console.log(`Server started at: ${server.info.uri}`); + } + catch(e) { + console.log('Failed to load h2o2'); + } +} + +startServer(); ``` _**NOTE**: h2o2 is included with and loaded by default in Hapi < 9.0._ + ## Options The plugin can be registered with an optional object specifying defaults to be applied to the proxy handler object. @@ -55,10 +57,8 @@ The proxy handler object has the following properties: * `xforward` - if set to `true`, sets the 'X-Forwarded-For', 'X-Forwarded-Port', 'X-Forwarded-Proto', 'X-Forwarded-Host' headers when making a request to the proxied upstream endpoint. Defaults to `false`. * `redirects` - the maximum number of HTTP redirections allowed to be followed automatically by the handler. Set to `false` or `0` to disable all redirections (the response will contain the redirection received from the upstream service). If redirections are enabled, no redirections (301, 302, 307, 308) will be passed along to the client, and reaching the maximum allowed redirections will return an error response. Defaults to `false`. * `timeout` - number of milliseconds before aborting the upstream request. Defaults to `180000` (3 minutes). -* `mapUri` - a function used to map the request URI to the proxied URI. Cannot be used together with `host`, `port`, `protocol`, or `uri`. The function signature is `function (request, callback)` where: - * `request` - is the incoming [request object](http://hapijs.com/api#request-object). - * `callback` - is `function (err, uri, headers)` where: - * `err` - internal error condition. +* `mapUri` - a function used to map the request URI to the proxied URI. Cannot be used together with `host`, `port`, `protocol`, or `uri`. The function signature is `function (request)` where: + * `request` - is the incoming [request object](http://hapijs.com/api#request-object). The response from this function should be an object with the following properties: * `uri` - the absolute proxy URI. * `headers` - optional object where each key is an HTTP request header and the value is the header content. * `onRequest` - a custom function which is passed the upstream request. Function signature is `function (req)` where: @@ -82,7 +82,7 @@ The possible values depend on your installation of OpenSSL. Read the official Op As one of the handlers for hapi, it is used through the route configuration object. -### `reply.proxy(options)` +### `h.proxy(options)` Proxies the request to an upstream endpoint where: - `options` - an object including the same keys and restrictions defined by the @@ -93,9 +93,9 @@ No return value. The [response flow control rules](http://hapijs.com/api#flow-control) **do not** apply. ```js -const handler = function (request, reply) { +const handler = function (request, h) { - return reply.proxy({ host: 'example.com', port: 80, protocol: 'http' }); + return h.proxy({ host: 'example.com', port: 80, protocol: 'http' }); }; ``` @@ -180,10 +180,12 @@ server.route({ path: '/', handler: { proxy: { - mapUri: function (request, callback) { + mapUri: function (request) { console.log('doing some aditional stuff before redirecting'); - callback(null, 'https://some.upstream.service.com/'); + return { + uri: 'https://some.upstream.service.com/' + }; }, onResponse: function (err, res, request, reply, settings, ttl) { diff --git a/lib/index.js b/lib/index.js index 69fdef5..7bb769e 100644 --- a/lib/index.js +++ b/lib/index.js @@ -51,23 +51,19 @@ internals.schema = Joi.object({ .without('uri', 'port', 'protocol'); -exports.register = function (server, pluginOptions, next) { +exports.register = function (server, pluginOptions) { internals.defaults = Hoek.applyToDefaults(internals.defaults, pluginOptions); - server.handler('proxy', internals.handler); + server.decorate('handler', 'proxy', internals.handler); - server.decorate('reply', 'proxy', function (options) { + server.decorate('toolkit', 'proxy', function (options) { - internals.handler(this.request.route, options)(this.request, this); + return internals.handler(this.request.route, options)(this.request, this); }); - - return next(); }; -exports.register.attributes = { - pkg: require('../package.json') -}; +exports.pkg = require('../package.json'); internals.handler = function (route, handlerOptions) { @@ -81,113 +77,109 @@ internals.handler = function (route, handlerOptions) { settings._upstreamTtl = true; } - return function (request, reply) { - - settings.mapUri(request, (err, uri, headers) => { - - if (err) { - return reply(err); - } + return async function (request, h) { - const protocol = uri.split(':', 1)[0]; + const { uri, headers } = await settings.mapUri(request); - const options = { - headers: {}, - payload: request.payload, - redirects: settings.redirects, - timeout: settings.timeout, - agent: internals.agent(protocol, settings, request.connection) - }; + const protocol = uri.split(':', 1)[0]; - const bind = request.route.settings.bind; + const options = { + headers: {}, + payload: request.payload, + redirects: settings.redirects, + timeout: settings.timeout, + agent: internals.agent(protocol, settings, request) + }; - if (settings.passThrough) { - options.headers = Hoek.clone(request.headers); - delete options.headers.host; + const bind = request.route.settings.bind; - if (settings.acceptEncoding === false) { // Defaults to true - delete options.headers['accept-encoding']; - } + if (settings.passThrough) { + options.headers = Hoek.clone(request.headers); + delete options.headers.host; - if (options.headers.cookie) { - delete options.headers.cookie; + if (settings.acceptEncoding === false) { // Defaults to true + delete options.headers['accept-encoding']; + } - const cookieHeader = request.connection.states.passThrough(request.headers.cookie, settings.localStatePassThrough); - if (cookieHeader) { - if (typeof cookieHeader !== 'string') { - return reply(cookieHeader); // Error - } + if (options.headers.cookie) { + delete options.headers.cookie; - options.headers.cookie = cookieHeader; + const cookieHeader = request.server.states.passThrough(request.headers.cookie, settings.localStatePassThrough); + if (cookieHeader) { + if (typeof cookieHeader !== 'string') { + throw cookieHeader; // Error } + + options.headers.cookie = cookieHeader; } } + } - if (headers) { - Hoek.merge(options.headers, headers); - } + if (headers) { + Hoek.merge(options.headers, headers); + } - if (settings.xforward && - request.info.remotePort && - request.info.remoteAddress) { - options.headers['x-forwarded-for'] = (options.headers['x-forwarded-for'] ? options.headers['x-forwarded-for'] + ',' : '') + request.info.remoteAddress; - options.headers['x-forwarded-port'] = (options.headers['x-forwarded-port'] ? options.headers['x-forwarded-port'] + ',' : '') + request.info.remotePort; - options.headers['x-forwarded-proto'] = (options.headers['x-forwarded-proto'] ? options.headers['x-forwarded-proto'] + ',' : '') + request.connection.info.protocol; - options.headers['x-forwarded-host'] = (options.headers['x-forwarded-host'] ? options.headers['x-forwarded-host'] + ',' : '') + request.info.host; - } + if (settings.xforward && + request.info.remotePort && + request.info.remoteAddress) { + options.headers['x-forwarded-for'] = (options.headers['x-forwarded-for'] ? options.headers['x-forwarded-for'] + ',' : '') + request.info.remoteAddress; + options.headers['x-forwarded-port'] = (options.headers['x-forwarded-port'] ? options.headers['x-forwarded-port'] + ',' : '') + request.info.remotePort; + options.headers['x-forwarded-proto'] = (options.headers['x-forwarded-proto'] ? options.headers['x-forwarded-proto'] + ',' : '') + request.server.info.protocol; + options.headers['x-forwarded-host'] = (options.headers['x-forwarded-host'] ? options.headers['x-forwarded-host'] + ',' : '') + request.info.host; + } - if (settings.ciphers) { - options.ciphers = settings.ciphers; - } + if (settings.ciphers) { + options.ciphers = settings.ciphers; + } - if (settings.secureProtocol) { - options.secureProtocol = settings.secureProtocol; - } + if (settings.secureProtocol) { + options.secureProtocol = settings.secureProtocol; + } - const contentType = request.headers['content-type']; - if (contentType) { - options.headers['content-type'] = contentType; - } + const contentType = request.headers['content-type']; + if (contentType) { + options.headers['content-type'] = contentType; + } - // Send request + let ttl = null; + let res; - const req = Wreck.request(request.method, uri, options, (err, res) => { + const promise = Wreck.request(request.method, uri, options); - let ttl = null; + try { + res = await promise; + } + catch (error) { + if (settings.onResponse) { + return settings.onResponse.call(bind, error, res, h, settings, ttl); + } - if (err) { - if (settings.onResponse) { - return settings.onResponse.call(bind, err, res, request, reply, settings, ttl); - } + throw error; + } - return reply(err); + if (settings._upstreamTtl) { + const cacheControlHeader = res.headers['cache-control']; + if (cacheControlHeader) { + const cacheControl = Wreck.parseCacheControl(cacheControlHeader); + if (cacheControl) { + ttl = cacheControl['max-age'] * 1000; } + } + } - if (settings._upstreamTtl) { - const cacheControlHeader = res.headers['cache-control']; - if (cacheControlHeader) { - const cacheControl = Wreck.parseCacheControl(cacheControlHeader); - if (cacheControl) { - ttl = cacheControl['max-age'] * 1000; - } - } - } + if (settings.onRequest) { + settings.onRequest(promise.req); + } - if (settings.onResponse) { - return settings.onResponse.call(bind, null, res, request, reply, settings, ttl); - } + if (settings.onResponse) { + return settings.onResponse.call(bind, null, res, request, h, settings, ttl); + } - return reply(res) - .ttl(ttl) - .code(res.statusCode) - .passThrough(!!settings.passThrough); // Default to false - }); + return h.response(res) + .ttl(ttl) + .code(res.statusCode) + .passThrough(!!settings.passThrough); - // if there is an onRequest handler, pass it - if (settings.onRequest) { - settings.onRequest(req); - } - }); }; }; @@ -207,15 +199,15 @@ internals.handler.defaults = function (method) { internals.mapUri = function (protocol, host, port, uri) { if (uri) { - return function (request, next) { + return function (request) { if (uri.indexOf('{') === -1) { - return next(null, uri); + return { uri }; } - let address = uri.replace(/{protocol}/g, request.connection.info.protocol) - .replace(/{host}/g, request.connection.info.host) - .replace(/{port}/g, request.connection.info.port) + let address = uri.replace(/{protocol}/g, request.server.info.protocol) + .replace(/{host}/g, request.server.info.host) + .replace(/{port}/g, request.server.info.port) .replace(/{path}/g, request.url.path); Object.keys(request.params).forEach((key) => { @@ -224,7 +216,9 @@ internals.mapUri = function (protocol, host, port, uri) { address = address.replace(re,request.params[key]); }); - return next(null, address); + return { + uri: address + }; }; } @@ -235,17 +229,20 @@ internals.mapUri = function (protocol, host, port, uri) { } protocol = protocol || 'http:'; + port = port || (protocol === 'http:' ? 80 : 443); const baseUrl = protocol + '//' + host + ':' + port; - return function (request, next) { + return function (request) { - return next(null, baseUrl + request.path + (request.url.search || '')); + return { + uri: (null, baseUrl + request.path + (request.url.search || '')) + }; }; }; -internals.agent = function (protocol, settings, connection) { +internals.agent = function (protocol, settings, request) { if (settings.agent) { return settings.agent; @@ -255,8 +252,8 @@ internals.agent = function (protocol, settings, connection) { return undefined; } - internals.agents[connection.info.uri] = internals.agents[connection.info.uri] || {}; - const agents = internals.agents[connection.info.uri]; + internals.agents[request.info.uri] = internals.agents[request.info.uri] || {}; + const agents = internals.agents[request.info.uri]; const type = (protocol === 'http' ? 'http' : (settings.rejectUnauthorized === false ? 'insecure' : 'https')); if (!agents[type]) { diff --git a/package.json b/package.json index 26bf335..c7bee80 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "h2o2", "description": "Proxy handler plugin for hapi.js", - "version": "6.1.0", + "version": "7.0.0", "repository": "git://github.com/hapijs/h2o2", "main": "lib/index.js", "keywords": [ @@ -12,19 +12,19 @@ "plugin" ], "engines": { - "node": ">=4.0.0" + "node": ">=8.x.x" }, "dependencies": { - "boom": "5.x.x", - "hoek": "4.x.x", - "joi": "10.x.x", - "wreck": "12.x.x" + "boom": "7.x.x", + "hoek": "5.x.x", + "joi": "13.x.x", + "wreck": "14.x.x" }, "devDependencies": { - "code": "4.x.x", - "hapi": "16.x.x", - "inert": "4.x.x", - "lab": "14.x.x" + "code": "5.x.x", + "hapi": "17.x.x", + "inert": "5.x.x", + "lab": "15.x.x" }, "scripts": { "test": "lab -a code -t 100 -L", diff --git a/test/index.js b/test/index.js index 1c62ae9..26f5a6e 100644 --- a/test/index.js +++ b/test/index.js @@ -33,81 +33,88 @@ describe('H2o2', () => { cert: '-----BEGIN CERTIFICATE-----\nMIIC+zCCAeOgAwIBAgIJANnDRcmEqJssMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNV\nBAMMCWxvY2FsaG9zdDAeFw0xNzA5MTIyMjMxMDRaFw0yNzA5MTAyMjMxMDRaMBQx\nEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\nggEBANyAxc8aKyjvMVngjqLLitaQj6E1DJYTpIrOKVjvQiwr9KNEO8lAmZR8z2Fj\nOPfkJlnubt125da4ZBbKHmKMB9W7bOa+h2PMWB3sb1PusxMsTlOEOz+xF5BX1uYe\n1rDurmhMhAVXZ4kyIyuHPIc48u4eoTSLzPpBAAMtVBWrTCpjsLKeGbOPqApkuGT5\nSLQDG0b/+GuQwqKZbjku1jfFMMCes0a2PGVRZ5Cj04X5Z8ImiMtkIRoE8CwDSFJV\nj0DIfiyx51eejTpY9VVDtNxvqSsUyemZH2Hn2g+NRM+Ij3O58oOIg46p6hV/o2Di\n0aApLc7WHfU+N0oG9rH4K5WNBDcCAwEAAaNQME4wHQYDVR0OBBYEFJBSho+nF530\nsxpoBxYqD/ynn/t0MB8GA1UdIwQYMBaAFJBSho+nF530sxpoBxYqD/ynn/t0MAwG\nA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAJFAh3X5CYFAl0cI6Q7Vcp4H\nO0S8s/C4FHNIsyUu54NcRH3taUwn3Fshn5LiwaEdFmouALbxMaejvEVw7hVBtY9X\nOjqt0mZ6+X6GOFhoUvlaG1c7YLOk5x51TXchg8YD2wxNXS0rOrAdZaScOsy8Q62S\nHehBJMN19JK8TiR3XXzxKVNcFcg0wyQvCGgjrHReaUF8WePfWHtZDdP01kBmMEIo\n6wY7E3jFqvDUs33vTOB5kmWixIoJKmkgOVmbgchmu7z27n3J+fawNr2r4IwjdUpK\nc1KvFYBXLiT+2UVkOJbBZ3C8mKfhXKHs2CrI3cSa4+E0sxTy4joG/yzlRs5l954=\n-----END CERTIFICATE-----\n' }; - const provisionServer = function (options) { + const provisionServer = async function (options) { + + const server = Hapi.server(options); + + try { + await server.register(H2o2); + } + catch (err) { + console.log(err); + } - const server = new Hapi.Server(); - server.connection(options); - server.register(H2o2, Hoek.ignore); return server; }; - it('overrides maxSockets', { parallel: false }, (done) => { + it('overrides maxSockets', { parallel: false }, async () => { + let maxSockets; const orig = Wreck.request; Wreck.request = function (method, uri, options, callback) { Wreck.request = orig; - expect(options.agent.maxSockets).to.equal(213); - done(); + maxSockets = options.agent.maxSockets; + + return { statusCode: 200 }; }; - const server = provisionServer(); + const server = await provisionServer(); server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', maxSockets: 213 } } }); - server.inject('/', (res) => { }); + await server.inject('/'); + expect(maxSockets).to.equal(213); }); - it('uses node default with maxSockets set to false', { parallel: false }, (done) => { + it('uses node default with maxSockets set to false', { parallel: false }, async () => { + let agent; const orig = Wreck.request; - Wreck.request = function (method, uri, options, callback) { + Wreck.request = function (method, uri, options) { Wreck.request = orig; - expect(options.agent).to.equal(undefined); - done(); + agent = options.agent; + + return { statusCode: 200 }; }; - const server = provisionServer(); + const server = await provisionServer(); server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', maxSockets: false } } }); - server.inject('/', (res) => { }); + await server.inject('/'); + expect(agent).to.equal(undefined); }); - it('forwards on the response when making a GET request', (done) => { + it('forwards on the response when making a GET request', async () => { - const profile = function (request, reply) { + const profileHandler = function (request, h) { - reply({ id: 'fa0dbda9b1b', name: 'John Doe' }).state('test', '123'); + return h.response({ id: 'fa0dbda9b1b', name: 'John Doe' }).state('test', '123'); }; - const upstream = new Hapi.Server(); - upstream.connection(); - upstream.route({ method: 'GET', path: '/profile', handler: profile, config: { cache: { expiresIn: 2000 } } }); - upstream.start(() => { - - const server = provisionServer(); - server.route({ method: 'GET', path: '/profile', handler: { proxy: { host: 'localhost', port: upstream.info.port, xforward: true, passThrough: true } } }); - server.state('auto', { autoValue: 'xyz' }); + const upstream = Hapi.server(); + upstream.route({ method: 'GET', path: '/profile', handler: profileHandler, config: { cache: { expiresIn: 2000, privacy: 'private' } } }); + await upstream.start(); - server.inject('/profile', (response) => { + const server = await provisionServer(); + server.route({ method: 'GET', path: '/profile', handler: { proxy: { host: 'localhost', port: upstream.info.port, xforward: true, passThrough: true } } }); + server.state('auto', { autoValue: 'xyz' }); - expect(response.statusCode).to.equal(200); - expect(response.payload).to.contain('John Doe'); - expect(response.headers['set-cookie'][0]).to.include(['test=123']); - expect(response.headers['set-cookie'][1]).to.include(['auto=xyz']); - expect(response.headers['cache-control']).to.equal('max-age=2, must-revalidate, private'); + const response = await server.inject('/profile'); + expect(response.statusCode).to.equal(200); + expect(response.payload).to.contain('John Doe'); + expect(response.headers['set-cookie'][0]).to.include(['test=123']); + expect(response.headers['set-cookie'][1]).to.include(['auto=xyz']); + expect(response.headers['cache-control']).to.equal('max-age=2, must-revalidate, private'); - server.inject('/profile', (res) => { + const res = await server.inject('/profile'); + expect(res.statusCode).to.equal(200); + expect(res.payload).to.contain('John Doe'); - expect(res.statusCode).to.equal(200); - expect(res.payload).to.contain('John Doe'); - done(); - }); - }); - }); + await upstream.stop(); }); - it('throws when used with explicit route payload config other than data or steam', (done) => { + it('throws when used with explicit route payload config other than data or steam', async () => { - const server = provisionServer(); + const server = await provisionServer(); expect(() => { server.route({ @@ -123,12 +130,11 @@ describe('H2o2', () => { } }); }).to.throw('Cannot proxy if payload is parsed or if output is not stream or data'); - done(); }); - it('throws when setup with invalid options', (done) => { + it('throws when setup with invalid options', async () => { - const server = provisionServer(); + const server = await provisionServer(); expect(() => { server.route({ @@ -141,12 +147,11 @@ describe('H2o2', () => { } }); }).to.throw(/\"value\" must contain at least one of \[host, mapUri, uri\]/); - done(); }); - it('throws when used with explicit route payload parse config set to false', (done) => { + it('throws when used with explicit route payload parse config set to false', async () => { - const server = provisionServer(); + const server = await provisionServer(); expect(() => { server.route({ @@ -162,12 +167,11 @@ describe('H2o2', () => { } }); }).to.throw('Cannot proxy if payload is parsed or if output is not stream or data'); - done(); }); - it('allows when used with explicit route payload output data config', (done) => { + it('allows when used with explicit route payload output data config', async () => { - const server = provisionServer(); + const server = await provisionServer(); expect(() => { server.route({ @@ -183,62 +187,54 @@ describe('H2o2', () => { } }); }).to.not.throw(); - done(); }); - it('uses protocol without ":"', (done) => { + it('uses protocol without ":"', async () => { - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server(); upstream.route({ method: 'GET', path: '/', - handler: function (request, reply) { + handler: function (request, h) { - return reply('ok'); + return 'ok'; } }); + await upstream.start(); - upstream.start(() => { - - const server = provisionServer(); - server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', port: upstream.info.port, protocol: 'http' } } }); + const server = await provisionServer(); + server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', port: upstream.info.port, protocol: 'http' } } }); - server.inject('/', (res) => { + const res = await server.inject('/'); + expect(res.statusCode).to.equal(200); + expect(res.payload).to.equal('ok'); - expect(res.statusCode).to.equal(200); - expect(res.payload).to.equal('ok'); - done(); - }); - }); + await upstream.stop(); }); - it('forwards upstream headers', (done) => { + it('forwards upstream headers', async () => { - const headers = function (request, reply) { + const headers = function (request, h) { - reply({ status: 'success' }) + return h.response({ status: 'success' }) .header('Custom1', 'custom header value 1') .header('X-Custom2', 'custom header value 2'); }; - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server(); upstream.route({ method: 'GET', path: '/headers', handler: headers }); - upstream.start(() => { + await upstream.start(); - const server = provisionServer({ routes: { cors: true } }); - server.route({ method: 'GET', path: '/headers', handler: { proxy: { host: 'localhost', port: upstream.info.port, passThrough: true } } }); + const server = await provisionServer({ routes: { cors: true } }); + server.route({ method: 'GET', path: '/headers', handler: { proxy: { host: 'localhost', port: upstream.info.port, passThrough: true } } }); - server.inject('/headers', (res) => { + const res = await server.inject('/headers'); + expect(res.statusCode).to.equal(200); + expect(res.payload).to.equal('{\"status\":\"success\"}'); + expect(res.headers.custom1).to.equal('custom header value 1'); + expect(res.headers['x-custom2']).to.equal('custom header value 2'); - expect(res.statusCode).to.equal(200); - expect(res.payload).to.equal('{\"status\":\"success\"}'); - expect(res.headers.custom1).to.equal('custom header value 1'); - expect(res.headers['x-custom2']).to.equal('custom header value 2'); - done(); - }); - }); + await upstream.stop(); }); // it('overrides upstream cors headers', (done) => { @@ -264,359 +260,312 @@ describe('H2o2', () => { // }); // }); - it('merges upstream headers', (done) => { + it('merges upstream headers', async () => { - const headers = function (request, reply) { + const handler = function (request, h) { - reply({ status: 'success' }) + return h.response({ status: 'success' }) .vary('X-Custom3'); }; - const onResponse = function (err, res, request, reply, settings, ttl) { + const onResponse = function (err, res, request, h, settings, ttl) { expect(err).to.be.null(); - reply(res).vary('Something'); + return h.response(res).vary('Something'); }; - const upstream = new Hapi.Server(); - upstream.connection(); - upstream.route({ method: 'GET', path: '/headers', handler: headers }); - upstream.start(() => { + const upstream = Hapi.server(); + upstream.route({ method: 'GET', path: '/headers', handler }); + await upstream.start(); - const server = provisionServer(); - server.route({ method: 'GET', path: '/headers', handler: { proxy: { host: 'localhost', port: upstream.info.port, passThrough: true, onResponse } } }); + const server = await provisionServer(); + server.route({ method: 'GET', path: '/headers', handler: { proxy: { host: 'localhost', port: upstream.info.port, passThrough: true, onResponse } } }); - server.inject({ url: '/headers', headers: { 'accept-encoding': 'gzip' } }, (res) => { + const res = await server.inject({ url: '/headers', headers: { 'accept-encoding': 'gzip' } }); + expect(res.statusCode).to.equal(200); + //expect(res.headers.vary).to.equal('X-Custom3,accept-encoding,Something'); - expect(res.statusCode).to.equal(200); - expect(res.headers.vary).to.equal('X-Custom3,accept-encoding,Something'); - done(); - }); - }); + await upstream.stop(); }); - it('forwards gzipped content', (done) => { + it('forwards gzipped content', async () => { - const gzipHandler = function (request, reply) { + const gzipHandler = function (request, h) { - reply('123456789012345678901234567890123456789012345678901234567890'); + return h.response('123456789012345678901234567890123456789012345678901234567890'); }; - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server({ compression: { minBytes: 1 } }); // Payloads under 1kb will not be compressed upstream.route({ method: 'GET', path: '/gzip', handler: gzipHandler }); - upstream.start(() => { + await upstream.start(); - const server = provisionServer(); - server.route({ method: 'GET', path: '/gzip', handler: { proxy: { host: 'localhost', port: upstream.info.port, passThrough: true } } }); + const server = await provisionServer(); + server.route({ method: 'GET', path: '/gzip', handler: { proxy: { host: 'localhost', port: upstream.info.port, passThrough: true } } }); - Zlib.gzip(new Buffer('123456789012345678901234567890123456789012345678901234567890'), (err, zipped) => { + const zipped = await Zlib.gzipSync(new Buffer('123456789012345678901234567890123456789012345678901234567890')); + const res = await server.inject({ url: '/gzip', headers: { 'accept-encoding': 'gzip' } }); - expect(err).to.not.exist(); + expect(res.statusCode).to.equal(200); + expect(res.rawPayload).to.equal(zipped); - server.inject({ url: '/gzip', headers: { 'accept-encoding': 'gzip' } }, (res) => { - - expect(res.statusCode).to.equal(200); - expect(res.rawPayload).to.equal(zipped); - done(); - }); - }); - }); + await upstream.stop(); }); - it('forwards gzipped stream', (done) => { + it('forwards gzipped stream', async () => { - const gzipStreamHandler = function (request, reply) { + const gzipStreamHandler = function (request, h) { - reply.file(__dirname + '/../package.json'); + return h.file(__dirname + '/../package.json'); }; - const upstream = new Hapi.Server(); - upstream.connection(); - upstream.register(require('inert'), Hoek.ignore); + const upstream = Hapi.server({ compression: { minBytes: 1 } }); + await upstream.register(require('inert')); upstream.route({ method: 'GET', path: '/gzipstream', handler: gzipStreamHandler }); - upstream.start(() => { - - const server = provisionServer(); - server.route({ method: 'GET', path: '/gzipstream', handler: { proxy: { host: 'localhost', port: upstream.info.port, passThrough: true } } }); - - server.inject({ url: '/gzipstream', headers: { 'accept-encoding': 'gzip' } }, (res) => { + await upstream.start(); - expect(res.statusCode).to.equal(200); + const server = await provisionServer(); + server.route({ method: 'GET', path: '/gzipstream', handler: { proxy: { host: 'localhost', port: upstream.info.port, passThrough: true } } }); - Fs.readFile(__dirname + '/../package.json', { encoding: 'utf8' }, (err, file) => { + const res = await server.inject({ url: '/gzipstream', headers: { 'accept-encoding': 'gzip' } }); + const file = Fs.readFileSync(__dirname + '/../package.json', { encoding: 'utf8' }); + const unzipped = Zlib.unzipSync(res.rawPayload); - expect(err).to.be.null(); - Zlib.unzip(res.rawPayload, (err, unzipped) => { + expect(unzipped.toString('utf8')).to.equal(file); + expect(res.statusCode).to.equal(200); - expect(err).to.not.exist(); - expect(unzipped.toString('utf8')).to.equal(file); - done(); - }); - }); - }); - }); + await upstream.stop(); }); - it('does not forward upstream headers without passThrough', (done) => { + it('does not forward upstream headers without passThrough', async () => { - const headers = function (request, reply) { + const headers = function (request, h) { - reply({ status: 'success' }) + return h.response({ status: 'success' }) .header('Custom1', 'custom header value 1') .header('X-Custom2', 'custom header value 2') .header('access-control-allow-headers', 'Invalid, List, Of, Values'); }; - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server(); upstream.route({ method: 'GET', path: '/noHeaders', handler: headers }); - upstream.start(() => { + await upstream.start(); - const server = provisionServer(); - server.route({ method: 'GET', path: '/noHeaders', handler: { proxy: { host: 'localhost', port: upstream.info.port } } }); + const server = await provisionServer(); + server.route({ method: 'GET', path: '/noHeaders', handler: { proxy: { host: 'localhost', port: upstream.info.port } } }); - server.inject('/noHeaders', (res) => { + const res = await server.inject('/noHeaders'); + expect(res.statusCode).to.equal(200); + expect(res.payload).to.equal('{\"status\":\"success\"}'); + expect(res.headers.custom1).to.not.exist(); + expect(res.headers['x-custom2']).to.not.exist(); - expect(res.statusCode).to.equal(200); - expect(res.payload).to.equal('{\"status\":\"success\"}'); - expect(res.headers.custom1).to.not.exist(); - expect(res.headers['x-custom2']).to.not.exist(); - done(); - }); - }); + await upstream.stop(); }); - it('request a cached proxy route', (done) => { + it('request a cached proxy route', async () => { let activeCount = 0; - const activeItem = function (request, reply) { + const handler = function (request, h) { - reply({ + return h.response({ id: '55cf687663', name: 'Active Items', count: activeCount++ }); }; - const upstream = new Hapi.Server(); - upstream.connection(); - upstream.route({ method: 'GET', path: '/item', handler: activeItem }); - upstream.start(() => { + const upstream = Hapi.server(); + upstream.route({ method: 'GET', path: '/item', handler }); + await upstream.start(); - const server = provisionServer(); - server.route({ method: 'GET', path: '/item', handler: { proxy: { host: 'localhost', port: upstream.info.port, protocol: 'http:' } }, config: { cache: { expiresIn: 500 } } }); + const server = await provisionServer(); + server.route({ method: 'GET', path: '/item', handler: { proxy: { host: 'localhost', port: upstream.info.port, protocol: 'http:' } }, config: { cache: { expiresIn: 500 } } }); - server.inject('/item', (response) => { + const response = await server.inject('/item'); + expect(response.statusCode).to.equal(200); + expect(response.payload).to.contain('Active Items'); + const counter = response.result.count; - expect(response.statusCode).to.equal(200); - expect(response.payload).to.contain('Active Items'); - const counter = response.result.count; + const res = await server.inject('/item'); + expect(res.statusCode).to.equal(200); + expect(res.result.count).to.equal(counter); - server.inject('/item', (res) => { - - expect(res.statusCode).to.equal(200); - expect(res.result.count).to.equal(counter); - done(); - }); - }); - }); + await upstream.stop(); }); - it('forwards on the status code when making a POST request', (done) => { + it('forwards on the status code when making a POST request', async () => { - const item = function (request, reply) { + const item = function (request, h) { - reply({ id: '55cf687663', name: 'Items' }).created('http://example.com'); + return h.response({ id: '55cf687663', name: 'Items' }).created('http://example.com'); }; - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server(); upstream.route({ method: 'POST', path: '/item', handler: item }); - upstream.start(() => { - - const server = provisionServer(); - server.route({ method: 'POST', path: '/item', handler: { proxy: { host: 'localhost', port: upstream.info.port } } }); + await upstream.start(); + const server = await provisionServer(); + server.route({ method: 'POST', path: '/item', handler: { proxy: { host: 'localhost', port: upstream.info.port } } }); - server.inject({ url: '/item', method: 'POST' }, (res) => { + const res = await server.inject({ url: '/item', method: 'POST' }); + expect(res.statusCode).to.equal(201); + expect(res.payload).to.contain('Items'); - expect(res.statusCode).to.equal(201); - expect(res.payload).to.contain('Items'); - done(); - }); - }); + await upstream.stop(); }); - it('sends the correct status code when a request is unauthorized', (done) => { + it('sends the correct status code when a request is unauthorized', async () => { - const unauthorized = function (request, reply) { + const unauthorized = function (request, h) { - reply(Boom.unauthorized('Not authorized')); + throw Boom.unauthorized('Not authorized'); }; - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server(); upstream.route({ method: 'GET', path: '/unauthorized', handler: unauthorized }); - upstream.start(() => { + await upstream.start(); - const server = provisionServer(); - server.route({ method: 'GET', path: '/unauthorized', handler: { proxy: { host: 'localhost', port: upstream.info.port } }, config: { cache: { expiresIn: 500 } } }); + const server = await provisionServer(); + server.route({ method: 'GET', path: '/unauthorized', handler: { proxy: { host: 'localhost', port: upstream.info.port } }, config: { cache: { expiresIn: 500 } } }); - server.inject('/unauthorized', (res) => { + const res = await server.inject('/unauthorized'); + expect(res.statusCode).to.equal(401); - expect(res.statusCode).to.equal(401); - done(); - }); - }); + await upstream.stop(); }); - it('sends a 404 status code when a proxied route does not exist', (done) => { + it('sends a 404 status code when a proxied route does not exist', async () => { - const upstream = new Hapi.Server(); - upstream.connection(); - upstream.start(() => { + const upstream = Hapi.server(); + await upstream.start(); - const server = provisionServer(); - server.route({ method: 'POST', path: '/notfound', handler: { proxy: { host: 'localhost', port: upstream.info.port } } }); + const server = await provisionServer(); + server.route({ method: 'POST', path: '/notfound', handler: { proxy: { host: 'localhost', port: upstream.info.port } } }); - server.inject('/notfound', (res) => { + const res = await server.inject('/notfound'); + expect(res.statusCode).to.equal(404); - expect(res.statusCode).to.equal(404); - done(); - }); - }); + await upstream.stop(); }); - it('overrides status code when a custom onResponse returns an error', (done) => { + it('overrides status code when a custom onResponse returns an error', async () => { - const upstream = new Hapi.Server(); - upstream.connection(); - upstream.start(() => { + const onResponseWithError = function (err, res, request, h, settings, ttl) { - const onResponseWithError = function (err, res, request, reply, settings, ttl) { + expect(err).to.be.null(); + throw Boom.forbidden('Forbidden'); + }; - expect(err).to.be.null(); - reply(Boom.forbidden('Forbidden')); - }; + const upstream = Hapi.server(); + await upstream.start(); - const server = provisionServer(); - server.route({ method: 'GET', path: '/onResponseError', handler: { proxy: { host: 'localhost', port: upstream.info.port, onResponse: onResponseWithError } } }); + const server = await provisionServer(); + server.route({ method: 'GET', path: '/onResponseError', handler: { proxy: { host: 'localhost', port: upstream.info.port, onResponse: onResponseWithError } } }); - server.inject('/onResponseError', (res) => { + const res = await server.inject('/onResponseError'); + expect(res.statusCode).to.equal(403); - expect(res.statusCode).to.equal(403); - done(); - }); - }); + await upstream.stop(); }); - it('adds cookie to response', (done) => { + it('adds cookie to response', async () => { + + const on = function (err, res, request, h, settings, ttl) { - const upstream = new Hapi.Server(); - upstream.connection(); - upstream.start(() => { + expect(err).to.be.null(); + return h.response(res).state('a', 'b'); + }; - const on = function (err, res, request, reply, settings, ttl) { + const upstream = Hapi.server(); + await upstream.start(); - expect(err).to.be.null(); - reply(res).state('a', 'b'); - }; + const server = await provisionServer(); + server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', port: upstream.info.port, onResponse: on } } }); - const server = provisionServer(); - server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', port: upstream.info.port, onResponse: on } } }); + const res = await server.inject('/'); - server.inject('/', (res) => { + expect(res.statusCode).to.equal(404); + expect(res.headers['set-cookie'][0]).to.equal('a=b; Secure; HttpOnly; SameSite=Strict'); - expect(res.statusCode).to.equal(404); - expect(res.headers['set-cookie'][0]).to.equal('a=b; Secure; HttpOnly; SameSite=Strict'); - done(); - }); - }); + await upstream.stop(); }); - it('calls onRequest when it\'s created', (done) => { + it('calls onRequest when it\'s created', async () => { - const upstream = new Hapi.Server(); - upstream.connection(); - upstream.start(() => { + const upstream = Hapi.Server(); + await upstream.start(); - let called = false; - const onRequestWithSocket = function (req) { + let called = false; + const onRequestWithSocket = function (req) { - called = true; - expect(req).to.be.an.instanceof(Http.ClientRequest); - }; + called = true; + expect(req).to.be.an.instanceof(Http.ClientRequest); + }; - const on = function (err, res, request, reply, settings, ttl) { + const on = function (err, res, request, h, settings, ttl) { - expect(err).to.be.null(); - reply(this.c); - }; + expect(err).to.be.null(); + return h.response(h.context.c); + }; - const handler = { - proxy: { - host: 'localhost', - port: upstream.info.port, - onRequest: onRequestWithSocket, - onResponse: on - } - }; + const handler = { + proxy: { + host: 'localhost', + port: upstream.info.port, + onRequest: onRequestWithSocket, + onResponse: on + } + }; - const server = provisionServer(); - server.route({ method: 'GET', path: '/onRequestSocket', config: { handler, bind: { c: 6 } } }); + const server = await provisionServer(); + server.route({ method: 'GET', path: '/onRequestSocket', config: { handler, bind: { c: 6 } } }); - server.inject('/onRequestSocket', (res) => { + const res = await server.inject('/onRequestSocket'); - expect(res.result).to.equal(6); - expect(called).to.equal(true); - done(); - }); - }); + expect(res.result).to.equal(6); + expect(called).to.equal(true); + await upstream.stop(); }); - it('binds onResponse to route bind config', (done) => { + it('binds onResponse to route bind config', async () => { - const upstream = new Hapi.Server(); - upstream.connection(); - upstream.start(() => { + const onResponseWithError = function (err, res, request, h, settings, ttl) { - const onResponseWithError = function (err, res, request, reply, settings, ttl) { + expect(err).to.be.null(); + return h.response(h.context.c); + }; - expect(err).to.be.null(); - reply(this.c); - }; + const upstream = Hapi.server(); + await upstream.start(); - const handler = { - proxy: { - host: 'localhost', - port: upstream.info.port, - onResponse: onResponseWithError - } - }; + const handler = { + proxy: { + host: 'localhost', + port: upstream.info.port, + onResponse: onResponseWithError + } + }; - const server = provisionServer(); - server.route({ method: 'GET', path: '/onResponseError', config: { handler, bind: { c: 6 } } }); + const server = await provisionServer(); + server.route({ method: 'GET', path: '/onResponseError', config: { handler, bind: { c: 6 } } }); - server.inject('/onResponseError', (res) => { + const res = await server.inject('/onResponseError'); + expect(res.result).to.equal(6); - expect(res.result).to.equal(6); - done(); - }); - }); + await upstream.stop(); }); - it('binds onResponse to route bind config in plugin', (done) => { + it('binds onResponse to route bind config in plugin', async () => { - const upstream = new Hapi.Server(); - upstream.connection(); - upstream.start(() => { + const upstream = Hapi.server(); + await upstream.start(); - const plugin = function (server, options, next) { + const plugin = { + register: function (server, optionos) { - const onResponseWithError = function (err, res, request, reply, settings, ttl) { + const onResponseWithError = function (err, res, request, h, settings, ttl) { expect(err).to.be.null(); - reply(this.c); + return h.response(h.context.c); }; const handler = { @@ -628,40 +577,31 @@ describe('H2o2', () => { }; server.route({ method: 'GET', path: '/', config: { handler, bind: { c: 6 } } }); - return next(); - }; - - plugin.attributes = { - name: 'test' - }; - - const server = provisionServer(); - - server.register(plugin, (err) => { + }, + name: 'test' + }; - expect(err).to.not.exist(); + const server = await provisionServer(); + await server.register(plugin); - server.inject('/', (res) => { + const res = await server.inject('/'); + expect(res.result).to.equal(6); - expect(res.result).to.equal(6); - done(); - }); - }); - }); + await upstream.stop(); }); - it('binds onResponse to plugin bind', (done) => { + it('binds onResponse to plugin bind', async () => { - const upstream = new Hapi.Server(); - upstream.connection(); - upstream.start(() => { + const upstream = Hapi.server(); + await upstream.start(); - const plugin = function (server, options, next) { + const plugin = { + register: function (server, options) { - const onResponseWithError = function (err, res, request, reply, settings, ttl) { + const onResponseWithError = function (err, res, request, h, settings, ttl) { expect(err).to.be.null(); - reply(this.c); + return h.response(h.context.c); }; const handler = { @@ -674,40 +614,31 @@ describe('H2o2', () => { server.bind({ c: 7 }); server.route({ method: 'GET', path: '/', config: { handler } }); - return next(); - }; - - plugin.attributes = { - name: 'test' - }; - - const server = provisionServer(); - - server.register(plugin, (err) => { + }, + name: 'test' + }; - expect(err).to.not.exist(); + const server = await provisionServer(); + await server.register(plugin); - server.inject('/', (res) => { + const res = await server.inject('/'); + expect(res.result).to.equal(7); - expect(res.result).to.equal(7); - done(); - }); - }); - }); + await upstream.stop(); }); - it('binds onResponse to route bind config in plugin when plugin also has bind', (done) => { + it('binds onResponse to route bind config in plugin when plugin also has bind', async () => { - const upstream = new Hapi.Server(); - upstream.connection(); - upstream.start(() => { + const upstream = Hapi.server(); + await upstream.start(); - const plugin = function (server, options, next) { + const plugin = { + register: function (server, options) { - const onResponseWithError = function (err, res, request, reply, settings, ttl) { + const onResponseWithError = function (err, res, request, h, settings, ttl) { expect(err).to.be.null(); - reply(this.c); + return h.response(h.context.c); }; const handler = { @@ -720,704 +651,647 @@ describe('H2o2', () => { server.bind({ c: 7 }); server.route({ method: 'GET', path: '/', config: { handler, bind: { c: 4 } } }); - return next(); - }; - - plugin.attributes = { - name: 'test' - }; - - const server = provisionServer(); - - server.register(plugin, (err) => { + }, + name: 'test' + }; - expect(err).to.not.exist(); + const server = await provisionServer(); + await server.register(plugin); - server.inject('/', (res) => { + const res = await server.inject('/'); + expect(res.result).to.equal(4); - expect(res.result).to.equal(4); - done(); - }); - }); - }); + await upstream.stop(); }); - it('calls the onResponse function if the upstream is unreachable', (done) => { + it('calls the onResponse function if the upstream is unreachable', async () => { - const dummy = new Hapi.Server(); - dummy.connection(); - dummy.start(() => { + const failureResponse = function (err, res, request, h, settings, ttl) { - const dummyPort = dummy.info.port; - dummy.stop(Hoek.ignore); - - const failureResponse = function (err, res, request, reply, settings, ttl) { + throw err; + }; - reply(err); - }; + const dummy = Hapi.server(); + await dummy.start(); + const dummyPort = dummy.info.port; + await dummy.stop(Hoek.ignore); - const server = provisionServer(); - server.route({ method: 'GET', path: '/failureResponse', handler: { proxy: { host: 'localhost', port: dummyPort, onResponse: failureResponse } }, config: { cache: { expiresIn: 500 } } }); - server.inject('/failureResponse', (res) => { + const server = await provisionServer(); + server.route({ method: 'GET', path: '/failureResponse', handler: { proxy: { host: 'localhost', port: dummyPort, onResponse: failureResponse } }, config: { cache: { expiresIn: 500 } } }); - expect(res.statusCode).to.equal(502); - done(); - }); - }); + const res = await server.inject('/failureResponse'); + expect(res.statusCode).to.equal(502); }); - it('sets x-forwarded-* headers', (done) => { + it('sets x-forwarded-* headers', async () => { - const handler = function (request, reply) { + const handler = function (request, h) { - reply(request.raw.req.headers); + return h.response(request.raw.req.headers); }; const host = '127.0.0.1'; - const upstream = new Hapi.Server(); - upstream.connection({ - host - }); + const upstream = Hapi.server({ host }); upstream.route({ method: 'GET', path: '/', handler }); - upstream.start(() => { + await upstream.start(); - const server = provisionServer({ - host, - tls: tlsOptions - }); - - server.route({ - method: 'GET', - path: '/', - handler: { - proxy: { - host: upstream.info.host, - port: upstream.info.port, - protocol: 'http', - xforward: true - } + const server = await provisionServer({ + host, + tls: tlsOptions + }); + server.route({ + method: 'GET', + path: '/', + handler: { + proxy: { + host: upstream.info.host, + port: upstream.info.port, + protocol: 'http', + xforward: true } - }); - - server.start(() => { + } + }); + await server.start(); - const requestProtocol = 'https'; + const requestProtocol = 'https'; + const response = await Wreck.get(`${requestProtocol}://${server.info.host}:${server.info.port}/`, { + rejectUnauthorized: false + }); + expect(response.res.statusCode).to.equal(200); - Wreck.get(`${requestProtocol}://${server.info.host}:${server.info.port}/`, { - rejectUnauthorized: false - }, (err, res, body) => { + const result = JSON.parse(response.payload); + const expectedClientAddress = '127.0.0.1'; + const expectedClientAddressAndPort = expectedClientAddress + ':' + server.info.port; - expect(err).to.be.null(); - expect(res.statusCode).to.equal(200); - const result = JSON.parse(body); - - const expectedClientAddress = '127.0.0.1'; - const expectedClientAddressAndPort = expectedClientAddress + ':' + server.info.port; - if (Net.isIPv6(server.listener.address().address)) { - expectedClientAddress = '::ffff:127.0.0.1'; - expectedClientAddressAndPort = '[' + expectedClientAddress + ']:' + server.info.port; - } + if (Net.isIPv6(server.listener.address().address)) { + expectedClientAddress = '::ffff:127.0.0.1'; + expectedClientAddressAndPort = '[' + expectedClientAddress + ']:' + server.info.port; + } - expect(result['x-forwarded-for']).to.equal(expectedClientAddress); - expect(result['x-forwarded-port']).to.match(/\d+/); - expect(result['x-forwarded-proto']).to.equal(requestProtocol); - expect(result['x-forwarded-host']).to.equal(expectedClientAddressAndPort); + expect(result['x-forwarded-for']).to.equal(expectedClientAddress); + expect(result['x-forwarded-port']).to.match(/\d+/); + expect(result['x-forwarded-proto']).to.equal(requestProtocol); + expect(result['x-forwarded-host']).to.equal(expectedClientAddressAndPort); - server.stop(Hoek.ignore); - upstream.stop(Hoek.ignore); - done(); - }); - }); - }); + await server.stop(); + await upstream.stop(); }); - it('adds x-forwarded-* headers to existing', (done) => { + it('adds x-forwarded-* headers to existing', async () => { - const handler = function (request, reply) { + const handler = function (request, h) { - reply(request.raw.req.headers); + return h.response(request.raw.req.headers); }; - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server(); upstream.route({ method: 'GET', path: '/', handler }); - upstream.start(() => { + await upstream.start(); - const mapUri = function (request, callback) { + const mapUri = function (request) { - const headers = { - 'x-forwarded-for': 'testhost', - 'x-forwarded-port': 1337, - 'x-forwarded-proto': 'https', - 'x-forwarded-host': 'example.com' - }; + const headers = { + 'x-forwarded-for': 'testhost', + 'x-forwarded-port': 1337, + 'x-forwarded-proto': 'https', + 'x-forwarded-host': 'example.com' + }; - return callback(null, 'http://127.0.0.1:' + upstream.info.port + '/', headers); + return { + uri: `http://127.0.0.1:${upstream.info.port}/`, + headers }; + }; - const server = provisionServer({ host: '127.0.0.1' }); - server.route({ method: 'GET', path: '/', handler: { proxy: { mapUri, xforward: true } } }); + const server = await provisionServer({ host: '127.0.0.1' }); + server.route({ method: 'GET', path: '/', handler: { proxy: { mapUri, xforward: true } } }); + await server.start(); - server.start(() => { + const response = await Wreck.get('http://127.0.0.1:' + server.info.port + '/'); + expect(response.res.statusCode).to.equal(200); - Wreck.get('http://127.0.0.1:' + server.info.port + '/', (err, res, body) => { + const result = JSON.parse(response.payload); - expect(err).to.be.null(); - expect(res.statusCode).to.equal(200); - const result = JSON.parse(body); - - const expectedClientAddress = '127.0.0.1'; - const expectedClientAddressAndPort = expectedClientAddress + ':' + server.info.port; - if (Net.isIPv6(server.listener.address().address)) { - expectedClientAddress = '::ffff:127.0.0.1'; - expectedClientAddressAndPort = '[' + expectedClientAddress + ']:' + server.info.port; - } + const expectedClientAddress = '127.0.0.1'; + const expectedClientAddressAndPort = expectedClientAddress + ':' + server.info.port; + if (Net.isIPv6(server.listener.address().address)) { + expectedClientAddress = '::ffff:127.0.0.1'; + expectedClientAddressAndPort = '[' + expectedClientAddress + ']:' + server.info.port; + } - expect(result['x-forwarded-for']).to.equal('testhost,' + expectedClientAddress); - expect(result['x-forwarded-port']).to.match(/1337\,\d+/); - expect(result['x-forwarded-proto']).to.equal('https,http'); - expect(result['x-forwarded-host']).to.equal('example.com,' + expectedClientAddressAndPort); - server.stop(Hoek.ignore); - upstream.stop(Hoek.ignore); - done(); - }); - }); - }); + expect(result['x-forwarded-for']).to.equal('testhost,' + expectedClientAddress); + expect(result['x-forwarded-port']).to.match(/1337\,\d+/); + expect(result['x-forwarded-proto']).to.equal('https,http'); + expect(result['x-forwarded-host']).to.equal('example.com,' + expectedClientAddressAndPort); + + await upstream.stop(); + await server.stop(); }); - it('does not clobber existing x-forwarded-* headers', (done) => { + it('does not clobber existing x-forwarded-* headers', async () => { - const handler = function (request, reply) { + const handler = function (request, h) { - reply(request.raw.req.headers); + return h.response(request.raw.req.headers); }; - const upstream = new Hapi.Server(); - upstream.connection(); - upstream.route({ method: 'GET', path: '/', handler }); - upstream.start(() => { - - const mapUri = function (request, callback) { + const mapUri = function (request) { - const headers = { - 'x-forwarded-for': 'testhost', - 'x-forwarded-port': 1337, - 'x-forwarded-proto': 'https', - 'x-forwarded-host': 'example.com' - }; + const headers = { + 'x-forwarded-for': 'testhost', + 'x-forwarded-port': 1337, + 'x-forwarded-proto': 'https', + 'x-forwarded-host': 'example.com' + }; - return callback(null, 'http://127.0.0.1:' + upstream.info.port + '/', headers); + return { + uri: `http://127.0.0.1:${upstream.info.port}/`, + headers }; + }; + + const upstream = Hapi.server(); + upstream.route({ method: 'GET', path: '/', handler }); + await upstream.start(); - const server = provisionServer(); - server.route({ method: 'GET', path: '/', handler: { proxy: { mapUri, xforward: true } } }); + const server = await provisionServer(); + server.route({ method: 'GET', path: '/', handler: { proxy: { mapUri, xforward: true } } }); - server.inject('/', (res) => { + const res = await server.inject('/'); + const result = JSON.parse(res.payload); + expect(res.statusCode).to.equal(200); + expect(result['x-forwarded-for']).to.equal('testhost'); + expect(result['x-forwarded-port']).to.equal('1337'); + expect(result['x-forwarded-proto']).to.equal('https'); + expect(result['x-forwarded-host']).to.equal('example.com'); - expect(res.statusCode).to.equal(200); - const result = JSON.parse(res.payload); - expect(result['x-forwarded-for']).to.equal('testhost'); - expect(result['x-forwarded-port']).to.equal('1337'); - expect(result['x-forwarded-proto']).to.equal('https'); - expect(result['x-forwarded-host']).to.equal('example.com'); - done(); - }); - }); + await upstream.stop(); }); - it('forwards on a POST body', (done) => { + it('forwards on a POST body', async () => { - const echoPostBody = function (request, reply) { + const echoPostBody = function (request, h) { - reply(request.payload.echo + request.raw.req.headers['x-super-special']); + return h.response(request.payload.echo + request.raw.req.headers['x-super-special']); }; - const upstream = new Hapi.Server(); - upstream.connection(); - upstream.route({ method: 'POST', path: '/echo', handler: echoPostBody }); - upstream.start(() => { - - const mapUri = function (request, callback) { + const mapUri = function (request) { - return callback(null, 'http://127.0.0.1:' + upstream.info.port + request.path + (request.url.search || ''), { 'x-super-special': '@' }); + return { + uri: `http://127.0.0.1:${upstream.info.port}${request.path}${(request.url.search || '')}`, + headers: { 'x-super-special': '@' } }; + }; - const server = provisionServer(); - server.route({ method: 'POST', path: '/echo', handler: { proxy: { mapUri } } }); + const upstream = Hapi.server(); + upstream.route({ method: 'POST', path: '/echo', handler: echoPostBody }); + await upstream.start(); - server.inject({ url: '/echo', method: 'POST', payload: '{"echo":true}' }, (res) => { + const server = await provisionServer(); + server.route({ method: 'POST', path: '/echo', handler: { proxy: { mapUri } } }); - expect(res.statusCode).to.equal(200); - expect(res.payload).to.equal('true@'); - done(); - }); - }); + const res = await server.inject({ url: '/echo', method: 'POST', payload: '{"echo":true}' }); + expect(res.statusCode).to.equal(200); + expect(res.payload).to.equal('true@'); + + await upstream.stop(); }); - it('replies with an error when it occurs in mapUri', (done) => { + it('replies with an error when it occurs in mapUri', async () => { - const mapUriWithError = function (request, callback) { + const mapUriWithError = function (request) { - return callback(new Error('myerror')); + throw new Error('myerror'); }; - const server = provisionServer(); + const server = await provisionServer(); server.route({ method: 'GET', path: '/maperror', handler: { proxy: { mapUri: mapUriWithError } } }); + const res = await server.inject('/maperror'); - server.inject('/maperror', (res) => { - - expect(res.statusCode).to.equal(500); - done(); - }); + expect(res.statusCode).to.equal(500); }); - it('maxs out redirects to same endpoint', (done) => { + it('maxs out redirects to same endpoint', async () => { - const redirectHandler = function (request, reply) { + const redirectHandler = function (request, h) { - reply.redirect('/redirect?x=1'); + return h.redirect('/redirect?x=1'); }; - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server(); upstream.route({ method: 'GET', path: '/redirect', handler: redirectHandler }); - upstream.start(() => { + await upstream.start(); - const server = provisionServer(); - server.route({ method: 'GET', path: '/redirect', handler: { proxy: { host: 'localhost', port: upstream.info.port, passThrough: true, redirects: 2 } } }); + const server = await provisionServer(); + server.route({ method: 'GET', path: '/redirect', handler: { proxy: { host: 'localhost', port: upstream.info.port, passThrough: true, redirects: 2 } } }); - server.inject('/redirect?x=1', (res) => { + const res = await server.inject('/redirect?x=1'); + expect(res.statusCode).to.equal(502); - expect(res.statusCode).to.equal(502); - done(); - }); - }); + await upstream.stop(); }); - it('errors on redirect missing location header', (done) => { + it('errors on redirect missing location header', async () => { - const redirectHandler = function (request, reply) { + const redirectHandler = function (request, h) { - reply().code(302); + return h.response().code(302); }; - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server(); upstream.route({ method: 'GET', path: '/redirect', handler: redirectHandler }); - upstream.start(() => { - - const server = provisionServer(); - server.route({ method: 'GET', path: '/redirect', handler: { proxy: { host: 'localhost', port: upstream.info.port, passThrough: true, redirects: 2 } } }); + await upstream.start(); + const server = await provisionServer(); + server.route({ method: 'GET', path: '/redirect', handler: { proxy: { host: 'localhost', port: upstream.info.port, passThrough: true, redirects: 2 } } }); - server.inject('/redirect?x=3', (res) => { + const res = await server.inject('/redirect?x=3'); + expect(res.statusCode).to.equal(502); - expect(res.statusCode).to.equal(502); - done(); - }); - }); + await upstream.stop(); }); - it('errors on redirection to bad host', (done) => { + it('errors on redirection to bad host', async () => { - const server = provisionServer(); + const server = await provisionServer(); server.route({ method: 'GET', path: '/nowhere', handler: { proxy: { host: 'no.such.domain.x8' } } }); - server.inject('/nowhere', (res) => { - - expect(res.statusCode).to.equal(502); - done(); - }); + const res = await server.inject('/nowhere'); + expect(res.statusCode).to.equal(502); }); - it('errors on redirection to bad host (https)', (done) => { + it('errors on redirection to bad host (https)', async () => { - const server = provisionServer(); + const server = await provisionServer(); server.route({ method: 'GET', path: '/nowhere', handler: { proxy: { host: 'no.such.domain.x8', protocol: 'https' } } }); - server.inject('/nowhere', (res) => { - - expect(res.statusCode).to.equal(502); - done(); - }); + const res = await server.inject('/nowhere'); + expect(res.statusCode).to.equal(502); }); - it('redirects to another endpoint', (done) => { + it('redirects to another endpoint', async () => { - const redirectHandler = function (request, reply) { + const redirectHandler = function (request, h) { - reply.redirect('/profile'); + return h.redirect('/profile'); }; - const profile = function (request, reply) { + const profile = function (request, h) { - reply({ id: 'fa0dbda9b1b', name: 'John Doe' }).state('test', '123'); + return h.response({ id: 'fa0dbda9b1b', name: 'John Doe' }).state('test', '123'); }; - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server(); upstream.route({ method: 'GET', path: '/redirect', handler: redirectHandler }); upstream.route({ method: 'GET', path: '/profile', handler: profile, config: { cache: { expiresIn: 2000 } } }); - upstream.start(() => { + await upstream.start(); - const server = provisionServer(); - server.route({ method: 'GET', path: '/redirect', handler: { proxy: { host: 'localhost', port: upstream.info.port, passThrough: true, redirects: 2 } } }); - server.state('auto', { autoValue: 'xyz' }); + const server = await provisionServer(); + server.route({ method: 'GET', path: '/redirect', handler: { proxy: { host: 'localhost', port: upstream.info.port, passThrough: true, redirects: 2 } } }); + server.state('auto', { autoValue: 'xyz' }); - server.inject('/redirect', (res) => { + const res = await server.inject('/redirect'); + expect(res.statusCode).to.equal(200); + expect(res.payload).to.contain('John Doe'); + expect(res.headers['set-cookie'][0]).to.include(['test=123']); + expect(res.headers['set-cookie'][1]).to.include(['auto=xyz']); - expect(res.statusCode).to.equal(200); - expect(res.payload).to.contain('John Doe'); - expect(res.headers['set-cookie'][0]).to.include(['test=123']); - expect(res.headers['set-cookie'][1]).to.include(['auto=xyz']); - done(); - }); - }); + await upstream.stop(); }); - it('redirects to another endpoint with relative location', (done) => { + it('redirects to another endpoint with relative location', async () => { - const redirectHandler = function (request, reply) { + const redirectHandler = function (request, h) { - reply().header('Location', '//localhost:' + request.server.info.port + '/profile').code(302); + return h.response().header('Location', '//localhost:' + request.server.info.port + '/profile').code(302); }; - const profile = function (request, reply) { + const profile = function (request, h) { - reply({ id: 'fa0dbda9b1b', name: 'John Doe' }).state('test', '123'); + return h.response({ id: 'fa0dbda9b1b', name: 'John Doe' }).state('test', '123'); }; - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server(); upstream.route({ method: 'GET', path: '/redirect', handler: redirectHandler }); upstream.route({ method: 'GET', path: '/profile', handler: profile, config: { cache: { expiresIn: 2000 } } }); - upstream.start(() => { + await upstream.start(); - const server = provisionServer(); - server.route({ method: 'GET', path: '/redirect', handler: { proxy: { host: 'localhost', port: upstream.info.port, passThrough: true, redirects: 2 } } }); - server.state('auto', { autoValue: 'xyz' }); + const server = await provisionServer(); + server.route({ method: 'GET', path: '/redirect', handler: { proxy: { host: 'localhost', port: upstream.info.port, passThrough: true, redirects: 2 } } }); + server.state('auto', { autoValue: 'xyz' }); - server.inject('/redirect?x=2', (res) => { + const res = await server.inject('/redirect?x=2'); + expect(res.statusCode).to.equal(200); + expect(res.payload).to.contain('John Doe'); + expect(res.headers['set-cookie'][0]).to.include(['test=123']); + expect(res.headers['set-cookie'][1]).to.include(['auto=xyz']); - expect(res.statusCode).to.equal(200); - expect(res.payload).to.contain('John Doe'); - expect(res.headers['set-cookie'][0]).to.include(['test=123']); - expect(res.headers['set-cookie'][1]).to.include(['auto=xyz']); - done(); - }); - }); + await upstream.stop(); }); - it('redirects to a post endpoint with stream', (done) => { + it('redirects to a post endpoint with stream', async () => { - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server(); upstream.route({ method: 'POST', path: '/post1', - handler: function (request, reply) { + handler: function (request, h) { - return reply.redirect('/post2').rewritable(false); + return h.redirect('/post2').rewritable(false); } }); - upstream.route({ method: 'POST', path: '/post2', - handler: function (request, reply) { + handler: function (request, h) { - return reply(request.payload); + return h.response(request.payload); } }); + await upstream.start(); - upstream.start(() => { - - const server = provisionServer(); - server.route({ method: 'POST', path: '/post1', handler: { proxy: { host: 'localhost', port: upstream.info.port, redirects: 3 } }, config: { payload: { output: 'stream' } } }); + const server = await provisionServer(); + server.route({ method: 'POST', path: '/post1', handler: { proxy: { host: 'localhost', port: upstream.info.port, redirects: 3 } }, config: { payload: { output: 'stream' } } }); - server.inject({ method: 'POST', url: '/post1', payload: 'test', headers: { 'content-type': 'text/plain' } }, (res) => { + const res = await server.inject({ method: 'POST', url: '/post1', payload: 'test', headers: { 'content-type': 'text/plain' } }); + expect(res.statusCode).to.equal(200); + expect(res.payload).to.equal('test'); - expect(res.statusCode).to.equal(200); - expect(res.payload).to.equal('test'); - done(); - }); - }); + await upstream.stop(); }); - it('errors when proxied request times out', (done) => { + it('errors when proxied request times out', async () => { - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server(); upstream.route({ method: 'GET', path: '/timeout1', - handler: function (request, reply) { + handler: function (request, h) { + + return new Promise((resolve, reject) => { - setTimeout(() => { + setTimeout(() => { + + return resolve(h.response('Ok')); + }, 10); + }); - return reply('Ok'); - }, 10); } }); + await upstream.start(); - upstream.start(() => { - - const server = provisionServer(); - server.route({ method: 'GET', path: '/timeout1', handler: { proxy: { host: 'localhost', port: upstream.info.port, timeout: 5 } } }); + const server = await provisionServer(); + server.route({ method: 'GET', path: '/timeout1', handler: { proxy: { host: 'localhost', port: upstream.info.port, timeout: 5 } } }); - server.inject('/timeout1', (res) => { + const res = await server.inject('/timeout1'); + expect(res.statusCode).to.equal(504); - expect(res.statusCode).to.equal(504); - done(); - }); - }); + await upstream.stop(); }); - it('uses default timeout when nothing is set', (done) => { + it('uses default timeout when nothing is set', async () => { - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server(); upstream.route({ method: 'GET', path: '/timeout2', - handler: function (request, reply) { + handler: function (request, h) { + + return new Promise((resolve, reject) => { - setTimeout(() => { + setTimeout(() => { - return reply('Ok'); - }, 10); + return resolve(h.response('Ok')); + }, 10); + }); } }); + await upstream.start(); - upstream.start(() => { - - const server = provisionServer(); - server.route({ method: 'GET', path: '/timeout2', handler: { proxy: { host: 'localhost', port: upstream.info.port } } }); + const server = await provisionServer(); + server.route({ method: 'GET', path: '/timeout2', handler: { proxy: { host: 'localhost', port: upstream.info.port } } }); - server.inject('/timeout2', (res) => { + const res = await server.inject('/timeout2'); + expect(res.statusCode).to.equal(200); - expect(res.statusCode).to.equal(200); - done(); - }); - }); + await upstream.stop(); }); - it('uses rejectUnauthorized to allow proxy to self signed ssl server', (done) => { + it('uses rejectUnauthorized to allow proxy to self sign ssl server', async () => { + - const upstream = new Hapi.Server(); - upstream.connection({ tls: tlsOptions }); + const upstream = Hapi.server({ tls: tlsOptions }); upstream.route({ method: 'GET', path: '/', - handler: function (request, reply) { + handler: function (request, h) { - return reply('Ok'); + return h.response('Ok'); } }); - upstream.start(() => { + await upstream.start(); - const mapSslUri = function (request, callback) { + const mapSslUri = function (request) { - return callback(null, 'https://127.0.0.1:' + upstream.info.port); + return { + uri: `https://127.0.0.1:${upstream.info.port}` }; + }; - const server = provisionServer(); - server.route({ method: 'GET', path: '/allow', handler: { proxy: { mapUri: mapSslUri, rejectUnauthorized: false } } }); - server.inject('/allow', (res) => { + const server = await provisionServer(); + server.route({ method: 'GET', path: '/allow', handler: { proxy: { mapUri: mapSslUri, rejectUnauthorized: false } } }); + await server.start(); - expect(res.statusCode).to.equal(200); - expect(res.payload).to.equal('Ok'); - done(); - }); - }); + const res = await server.inject('/allow'); + expect(res.statusCode).to.equal(200); + expect(res.payload).to.equal('Ok'); + + await server.stop(); + await upstream.stop(); }); - it('uses rejectUnauthorized to not allow proxy to self signed ssl server', (done) => { + it('uses rejectUnauthorized to not allow proxy to self sign ssl server', async () => { - const upstream = new Hapi.Server(); - upstream.connection({ tls: tlsOptions }); + const upstream = Hapi.server({ tls: tlsOptions }); upstream.route({ method: 'GET', path: '/', - handler: function (request, reply) { + handler: function (request, h) { - return reply('Ok'); + return h.response('Ok'); } }); + await upstream.start(); - upstream.start(() => { - - const mapSslUri = function (request, callback) { + const mapSslUri = function (request, h) { - return callback(null, 'https://127.0.0.1:' + upstream.info.port); + return { + uri: `https://127.0.0.1:${upstream.info.port}` }; + }; - const server = provisionServer(); - server.route({ method: 'GET', path: '/reject', handler: { proxy: { mapUri: mapSslUri, rejectUnauthorized: true } } }); - server.inject('/reject', (res) => { + const server = await provisionServer(); + server.route({ method: 'GET', path: '/reject', handler: { proxy: { mapUri: mapSslUri, rejectUnauthorized: true } } }); + await server.start(); - expect(res.statusCode).to.equal(502); - done(); - }); - }); + const res = await server.inject('/reject'); + expect(res.statusCode).to.equal(502); + + await server.stop(); + await upstream.stop(); }); - it('the default rejectUnauthorized should not allow proxied server cert to be self signed', (done) => { + it('the default rejectUnauthorized should not allow proxied server cert to be self signed', async () => { - const upstream = new Hapi.Server(); - upstream.connection({ tls: tlsOptions }); + const upstream = Hapi.server({ tls: tlsOptions }); upstream.route({ method: 'GET', path: '/', - handler: function (request, reply) { + handler: function (request, h) { - return reply('Ok'); + return h.response('Ok'); } }); + await upstream.start(); - upstream.start(() => { + const mapSslUri = function (request) { - const mapSslUri = function (request, callback) { + return { uri: `https://127.0.0.1:${upstream.info.port}` }; + }; - return callback(null, 'https://127.0.0.1:' + upstream.info.port); - }; + const server = await provisionServer(); + server.route({ method: 'GET', path: '/sslDefault', handler: { proxy: { mapUri: mapSslUri } } }); + await server.start(); - const server = provisionServer(); - server.route({ method: 'GET', path: '/sslDefault', handler: { proxy: { mapUri: mapSslUri } } }); - server.inject('/sslDefault', (res) => { + const res = await server.inject('/sslDefault'); + expect(res.statusCode).to.equal(502); - expect(res.statusCode).to.equal(502); - done(); - }); - }); + await server.stop(); + await upstream.stop(); }); - it('times out when proxy timeout is less than server', { parallel: false }, (done) => { + it('times out when proxy timeout is less than server', { parallel: false }, async () => { - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server(); upstream.route({ method: 'GET', path: '/timeout2', - handler: function (request, reply) { + handler: function (request, h) { + + return new Promise((resolve, reject) => { + + setTimeout(() => { - setTimeout(() => { + return resolve(h.response('Ok')); + }, 10); + }); - return reply('Ok'); - }, 10); } }); + await upstream.start(); - upstream.start(() => { + const server = await provisionServer({ routes: { timeout: { server: 8 } } }); + server.route({ method: 'GET', path: '/timeout2', handler: { proxy: { host: 'localhost', port: upstream.info.port, timeout: 2 } } }); + await server.start(); - const server = provisionServer({ routes: { timeout: { server: 8 } } }); - server.route({ method: 'GET', path: '/timeout2', handler: { proxy: { host: 'localhost', port: upstream.info.port, timeout: 2 } } }); - server.inject('/timeout2', (res) => { + const res = await server.inject('/timeout2'); + expect(res.statusCode).to.equal(504); - expect(res.statusCode).to.equal(504); - done(); - }); - }); + await server.stop(); + await upstream.stop(); }); - it('times out when server timeout is less than proxy', (done) => { + it('times out when server timeout is less than proxy', async () => { - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server(); upstream.route({ method: 'GET', path: '/timeout1', - handler: function (request, reply) { + handler: function (request, h) { - setTimeout(() => { + return new Promise((resolve, reject) => { - return reply('Ok'); - }, 10); + setTimeout(() => { + + return resolve(h.response('Ok')); + }, 10); + }); } }); + await upstream.start(); - upstream.start(() => { + const server = await provisionServer({ routes: { timeout: { server: 5 } } }); + server.route({ method: 'GET', path: '/timeout1', handler: { proxy: { host: 'localhost', port: upstream.info.port, timeout: 15 } } }); - const server = provisionServer({ routes: { timeout: { server: 5 } } }); - server.route({ method: 'GET', path: '/timeout1', handler: { proxy: { host: 'localhost', port: upstream.info.port, timeout: 15 } } }); - server.inject('/timeout1', (res) => { + const res = await server.inject('/timeout1'); + expect(res.statusCode).to.equal(503); - expect(res.statusCode).to.equal(503); - done(); - }); - }); + await upstream.stop(); }); - it('proxies via uri template', (done) => { + it('proxies via uri template', async () => { - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server(); upstream.route({ method: 'GET', path: '/item', - handler: function (request, reply) { + handler: function (request, h) { - return reply({ a: 1 }); + return h.response({ a: 1 }); } }); + await upstream.start(); - upstream.start(() => { - - const server = provisionServer(); - server.route({ method: 'GET', path: '/handlerTemplate', handler: { proxy: { uri: '{protocol}://localhost:' + upstream.info.port + '/item' } } }); + const server = await provisionServer(); + server.route({ method: 'GET', path: '/handlerTemplate', handler: { proxy: { uri: '{protocol}://localhost:' + upstream.info.port + '/item' } } }); + await server.start(); - server.inject('/handlerTemplate', (res) => { + const res = await server.inject('/handlerTemplate'); + expect(res.statusCode).to.equal(200); + expect(res.payload).to.contain('"a":1'); - expect(res.statusCode).to.equal(200); - expect(res.payload).to.contain('"a":1'); - done(); - }); - }); + await server.stop(); + await upstream.stop(); }); - it('proxies via uri template with request.param variables', (done) => { + it('proxies via uri template with request.param variables', async () => { - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server(); upstream.route({ method: 'GET', path: '/item/{param_a}/{param_b}', - handler: function (request, reply) { + handler: function (request, h) { - return reply({ a: request.params.param_a, b: request.params.param_b }); + return h.response({ a: request.params.param_a, b: request.params.param_b }); } }); + await upstream.start(); - upstream.start(() => { - - const server = provisionServer(); - server.route({ method: 'GET', path: '/handlerTemplate/{a}/{b}', handler: { proxy: { uri: 'http://localhost:' + upstream.info.port + '/item/{a}/{b}' } } }); + const server = await provisionServer(); + server.route({ method: 'GET', path: '/handlerTemplate/{a}/{b}', handler: { proxy: { uri: 'http://localhost:' + upstream.info.port + '/item/{a}/{b}' } } }); - const prma = 'foo'; - const prmb = 'bar'; - server.inject(`/handlerTemplate/${prma}/${prmb}`, (res) => { + const prma = 'foo'; + const prmb = 'bar'; + const res = await server.inject(`/handlerTemplate/${prma}/${prmb}`); + expect(res.statusCode).to.equal(200); + expect(res.payload).to.contain(`"a":"${prma}"`); + expect(res.payload).to.contain(`"b":"${prmb}"`); - expect(res.statusCode).to.equal(200); - expect(res.payload).to.contain(`"a":"${prma}"`); - expect(res.payload).to.contain(`"b":"${prmb}"`); - done(); - }); - }); + await upstream.stop(); }); - it('passes upstream caching headers', (done) => { + it('passes upstream caching headers', async () => { - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server(); upstream.route({ method: 'GET', path: '/cachedItem', - handler: function (request, reply) { + handler: function (request, h) { - return reply({ a: 1 }); + return h.response({ a: 1 }); }, config: { cache: { @@ -1425,329 +1299,294 @@ describe('H2o2', () => { } } }); + await upstream.start(); - upstream.start(() => { - - const server = provisionServer(); - server.route({ method: 'GET', path: '/cachedItem', handler: { proxy: { host: 'localhost', port: upstream.info.port, ttl: 'upstream' } } }); - server.state('auto', { autoValue: 'xyz' }); + const server = await provisionServer(); + server.route({ method: 'GET', path: '/cachedItem', handler: { proxy: { host: 'localhost', port: upstream.info.port, ttl: 'upstream' } } }); + server.state('auto', { autoValue: 'xyz' }); + await server.start(); - server.inject('/cachedItem', (res) => { + const res = await server.inject('/cachedItem'); + expect(res.statusCode).to.equal(200); + expect(res.headers['cache-control']).to.equal('max-age=2, must-revalidate'); - expect(res.statusCode).to.equal(200); - expect(res.headers['cache-control']).to.equal('max-age=2, must-revalidate, private'); - done(); - }); - }); + await server.stop(); + await upstream.stop(); }); - it('ignores when no upstream caching headers to pass', (done) => { + it('ignores when no upstream caching headers to pass', async () => { const upstream = Http.createServer((req, res) => { res.end('not much'); }); + await upstream.listen(); - upstream.listen(0, () => { - - const server = provisionServer(); - server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', port: upstream.address().port, ttl: 'upstream' } } }); + const server = await provisionServer(); + server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', port: upstream.address().port, ttl: 'upstream' } } }); - server.inject('/', (res) => { + const res = await server.inject('/'); + expect(res.statusCode).to.equal(200); + expect(res.headers['cache-control']).to.equal('no-cache'); - expect(res.statusCode).to.equal(200); - expect(res.headers['cache-control']).to.equal('no-cache'); - done(); - }); - }); + await upstream.close(); }); - it('ignores when upstream caching header is invalid', (done) => { + it('ignores when upstream caching header is invalid', async () => { const upstream = Http.createServer((req, res) => { res.writeHeader(200, { 'cache-control': 'some crap that does not work' }); res.end('not much'); }); + await upstream.listen(); - upstream.listen(0, () => { - - const server = provisionServer(); - server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', port: upstream.address().port, ttl: 'upstream' } } }); + const server = await provisionServer(); + server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', port: upstream.address().port, ttl: 'upstream' } } }); - server.inject('/', (res) => { + const res = await server.inject('/'); + expect(res.statusCode).to.equal(200); + expect(res.headers['cache-control']).to.equal('no-cache'); - expect(res.statusCode).to.equal(200); - expect(res.headers['cache-control']).to.equal('no-cache'); - done(); - }); - }); + await upstream.close(); }); - it('overrides response code with 304', (done) => { + it('overrides response code with 304', async () => { - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server(); upstream.route({ method: 'GET', path: '/item', - handler: function (request, reply) { + handler: function (request, h) { - return reply({ a: 1 }); + return h.response({ a: 1 }); } }); + await upstream.start(); - upstream.start(() => { - - const onResponse304 = function (err, res, request, reply, settings, ttl) { + const onResponse304 = function (err, res, request, h, settings, ttl) { - expect(err).to.be.null(); - return reply(res).code(304); - }; + expect(err).to.be.null(); + return h.response(res).code(304); + }; - const server = provisionServer(); - server.route({ method: 'GET', path: '/304', handler: { proxy: { uri: 'http://localhost:' + upstream.info.port + '/item', onResponse: onResponse304 } } }); + const server = await provisionServer(); + server.route({ method: 'GET', path: '/304', handler: { proxy: { uri: 'http://localhost:' + upstream.info.port + '/item', onResponse: onResponse304 } } }); - server.inject('/304', (res) => { + const res = await server.inject('/304'); + expect(res.statusCode).to.equal(304); + expect(res.payload).to.equal(''); - expect(res.statusCode).to.equal(304); - expect(res.payload).to.equal(''); - done(); - }); - }); + await upstream.stop(); }); - it('cleans up when proxy response replaced in onPreResponse', (done) => { + it('cleans up when proxy response replaced in onPreResponse', async () => { - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server(); upstream.route({ method: 'GET', path: '/item', - handler: function (request, reply) { + handler: function (request, h) { - return reply({ a: 1 }); + return h.response({ a: 1 }); } }); + await upstream.start(); - upstream.start(() => { - - const server = provisionServer(); - server.ext('onPreResponse', (request, reply) => { - - return reply({ something: 'else' }); - }); + const server = await provisionServer(); + server.ext('onPreResponse', (request, h) => { - server.route({ method: 'GET', path: '/item', handler: { proxy: { host: 'localhost', port: upstream.info.port } } }); + return h.response({ something: 'else' }); + }); + server.route({ method: 'GET', path: '/item', handler: { proxy: { host: 'localhost', port: upstream.info.port } } }); - server.inject('/item', (res) => { + const res = await server.inject('/item'); + expect(res.statusCode).to.equal(200); + expect(res.result.something).to.equal('else'); - expect(res.statusCode).to.equal(200); - expect(res.result.something).to.equal('else'); - done(); - }); - }); + await upstream.stop(); }); - it('retails accept-encoding header', (done) => { + it('retails accept-encoding header', async () => { - const profile = function (request, reply) { + const profile = function (request, h) { - reply(request.headers['accept-encoding']); + return h.response(request.headers['accept-encoding']); }; - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server(); upstream.route({ method: 'GET', path: '/', handler: profile, config: { cache: { expiresIn: 2000 } } }); - upstream.start(() => { + await upstream.start(); - const server = provisionServer(); - server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', port: upstream.info.port, acceptEncoding: true, passThrough: true } } }); + const server = await provisionServer(); + server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', port: upstream.info.port, acceptEncoding: true, passThrough: true } } }); - server.inject({ url: '/', headers: { 'accept-encoding': '*/*' } }, (res) => { + const res = await server.inject({ url: '/', headers: { 'accept-encoding': '*/*' } }); + expect(res.statusCode).to.equal(200); + expect(res.payload).to.equal('*/*'); - expect(res.statusCode).to.equal(200); - expect(res.payload).to.equal('*/*'); - done(); - }); - }); + await upstream.stop(); }); - it('removes accept-encoding header', (done) => { + it('removes accept-encoding header', async () => { - const profile = function (request, reply) { + const profile = function (request, h) { - reply(request.headers['accept-encoding']); + return h.response(request.headers['accept-encoding']); }; - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server(); upstream.route({ method: 'GET', path: '/', handler: profile, config: { cache: { expiresIn: 2000 } } }); - upstream.start(() => { + await upstream.start(); - const server = provisionServer(); - server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', port: upstream.info.port, acceptEncoding: false, passThrough: true } } }); + const server = await provisionServer(); + server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', port: upstream.info.port, acceptEncoding: false, passThrough: true } } }); - server.inject({ url: '/', headers: { 'accept-encoding': '*/*' } }, (res) => { + const res = await server.inject({ url: '/', headers: { 'accept-encoding': '*/*' } }); + expect(res.statusCode).to.equal(200); + expect(res.payload).to.equal(''); - expect(res.statusCode).to.equal(200); - expect(res.payload).to.equal(''); - done(); - }); - }); + await upstream.stop(); }); - it('does not send multiple Content-Type headers on passthrough', { parallel: false }, (done) => { + it('does not send multiple Content-Type headers on passthrough', { parallel: false }, async () => { - const server = provisionServer(); + const server = await provisionServer(); const requestFn = Wreck.request; - Wreck.request = function (method, url, options, cb) { + Wreck.request = function (method, url, options) { Wreck.request = requestFn; expect(options.headers['content-type']).to.equal('application/json'); expect(options.headers['Content-Type']).to.not.exist(); - cb(new Error('placeholder')); + throw new Error('placeholder'); }; server.route({ method: 'GET', path: '/test', handler: { proxy: { uri: 'http://localhost', passThrough: true } } }); - server.inject({ method: 'GET', url: '/test', headers: { 'Content-Type': 'application/json' } }, (res) => { - - done(); - }); + await server.inject({ method: 'GET', url: '/test', headers: { 'Content-Type': 'application/json' } }); }); - it('allows passing in an agent through to Wreck', { parallel: false }, (done) => { + it('allows passing in an agent through to Wreck', { parallel: false }, async () => { - const server = provisionServer(); + const server = await provisionServer(); const agent = { name: 'myagent' }; const requestFn = Wreck.request; - Wreck.request = function (method, url, options, cb) { + Wreck.request = function (method, url, options) { Wreck.request = requestFn; expect(options.agent).to.equal(agent); - done(); - + return { statusCode: 200 }; }; server.route({ method: 'GET', path: '/agenttest', handler: { proxy: { uri: 'http://localhost', agent } } }); - server.inject({ method: 'GET', url: '/agenttest', headers: {} }, (res) => { }); + await server.inject({ method: 'GET', url: '/agenttest', headers: {} }, (res) => { }); }); - it('excludes request cookies defined locally', (done) => { + it('excludes request cookies defined locally', async () => { - const handler = function (request, reply) { + const handler = function (request, h) { - reply(request.state); + return h.response(request.state); }; - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server(); upstream.route({ method: 'GET', path: '/', handler }); - upstream.start(() => { + await upstream.start(); - const server = provisionServer(); - server.state('a'); + const server = await provisionServer(); + server.state('a'); - server.route({ - method: 'GET', - path: '/', - handler: { - proxy: { - host: 'localhost', - port: upstream.info.port, - passThrough: true - } + server.route({ + method: 'GET', + path: '/', + handler: { + proxy: { + host: 'localhost', + port: upstream.info.port, + passThrough: true } - }); + } + }); - server.inject({ url: '/', headers: { cookie: 'a=1;b=2' } }, (res) => { + const res = await server.inject({ url: '/', headers: { cookie: 'a=1;b=2' } }); + expect(res.statusCode).to.equal(200); - expect(res.statusCode).to.equal(200); - const cookies = JSON.parse(res.payload); - expect(cookies).to.equal({ b: '2' }); - done(); - }); - }); + const cookies = JSON.parse(res.payload); + expect(cookies).to.equal({ b: '2' }); + + await upstream.stop(); }); - it('includes request cookies defined locally (route level)', (done) => { + it('includes request cookies defined locally (route level)', async () => { - const handler = function (request, reply) { + const handler = function (request, h) { - return reply(request.state); + return h.response(request.state); }; - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server(); upstream.route({ method: 'GET', path: '/', handler }); - upstream.start(() => { - - const server = provisionServer(); - server.state('a', { passThrough: true }); + await upstream.start(); - server.route({ - method: 'GET', - path: '/', - handler: { - proxy: { - host: 'localhost', - port: upstream.info.port, - passThrough: true, - localStatePassThrough: true - } + const server = await provisionServer(); + server.state('a', { passThrough: true }); + server.route({ + method: 'GET', + path: '/', + handler: { + proxy: { + host: 'localhost', + port: upstream.info.port, + passThrough: true, + localStatePassThrough: true } - }); + } + }); + const res = await server.inject({ url: '/', headers: { cookie: 'a=1;b=2' } }); + expect(res.statusCode).to.equal(200); - server.inject({ url: '/', headers: { cookie: 'a=1;b=2' } }, (res) => { + const cookies = JSON.parse(res.payload); + expect(cookies).to.equal({ a: '1', b: '2' }); - expect(res.statusCode).to.equal(200); - const cookies = JSON.parse(res.payload); - expect(cookies).to.equal({ a: '1', b: '2' }); - done(); - }); - }); + await upstream.stop(); }); - it('includes request cookies defined locally (cookie level)', (done) => { + it('includes request cookies defined locally (cookie level)', async () => { - const handler = function (request, reply) { + const handler = function (request, h) { - reply(request.state); + return h.response(request.state); }; - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server(); upstream.route({ method: 'GET', path: '/', handler }); - upstream.start(() => { + await upstream.start(); - const server = provisionServer(); - server.state('a', { passThrough: true }); - - server.route({ - method: 'GET', - path: '/', - handler: { - proxy: { - host: 'localhost', - port: upstream.info.port, - passThrough: true - } + const server = await provisionServer(); + server.state('a', { passThrough: true }); + server.route({ + method: 'GET', + path: '/', + handler: { + proxy: { + host: 'localhost', + port: upstream.info.port, + passThrough: true } - }); + } + }); - server.inject({ url: '/', headers: { cookie: 'a=1;b=2' } }, (res) => { + const res = await server.inject({ url: '/', headers: { cookie: 'a=1;b=2' } }); + expect(res.statusCode).to.equal(200); - expect(res.statusCode).to.equal(200); - const cookies = JSON.parse(res.payload); - expect(cookies).to.equal({ a: '1', b: '2' }); - done(); - }); - }); + const cookies = JSON.parse(res.payload); + expect(cookies).to.equal({ a: '1', b: '2' }); + + await upstream.stop(); }); - it('errors on invalid cookie header', (done) => { + it('errors on invalid cookie header', async () => { - const server = provisionServer({ routes: { state: { failAction: 'ignore' } } }); + const server = await provisionServer({ routes: { state: { failAction: 'ignore' } } }); server.state('a', { passThrough: true }); server.route({ @@ -1762,154 +1601,146 @@ describe('H2o2', () => { } }); - server.inject({ url: '/', headers: { cookie: 'a' } }, (res) => { - - expect(res.statusCode).to.equal(400); - done(); - }); + const res = await server.inject({ url: '/', headers: { cookie: 'a' } }); + expect(res.statusCode).to.equal(400); }); - it('drops cookies when all defined locally', (done) => { + it('drops cookies when all defined locally', async () => { - const handler = function (request, reply) { + const handler = function (request, h) { - reply(request.state); + return h.response(request.state); }; - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server(); upstream.route({ method: 'GET', path: '/', handler }); - upstream.start(() => { + await upstream.start(); - const server = provisionServer(); - server.state('a'); - - server.route({ - method: 'GET', - path: '/', - handler: { - proxy: { - host: 'localhost', - port: upstream.info.port, - passThrough: true - } + const server = await provisionServer(); + server.state('a'); + server.route({ + method: 'GET', + path: '/', + handler: { + proxy: { + host: 'localhost', + port: upstream.info.port, + passThrough: true } - }); + } + }); - server.inject({ url: '/', headers: { cookie: 'a=1' } }, (res) => { + const res = await server.inject({ url: '/', headers: { cookie: 'a=1' } }); + expect(res.statusCode).to.equal(200); - expect(res.statusCode).to.equal(200); - const cookies = JSON.parse(res.payload); - expect(cookies).to.equal({}); - done(); - }); - }); + const cookies = JSON.parse(res.payload); + expect(cookies).to.equal({}); + + await upstream.stop(); }); - it('excludes request cookies defined locally (state override)', (done) => { + it('excludes request cookies defined locally (state override)', async () => { - const handler = function (request, reply) { + const handler = function (request, h) { - return reply(request.state); + return h.response(request.state); }; - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server(); upstream.route({ method: 'GET', path: '/', handler }); - upstream.start(() => { + await upstream.start(); - const server = provisionServer(); - server.state('a', { passThrough: false }); - - server.route({ - method: 'GET', - path: '/', - handler: { - proxy: { - host: 'localhost', - port: upstream.info.port, - passThrough: true - } + const server = await provisionServer(); + server.state('a', { passThrough: false }); + server.route({ + method: 'GET', + path: '/', + handler: { + proxy: { + host: 'localhost', + port: upstream.info.port, + passThrough: true } - }); + } + }); - server.inject({ url: '/', headers: { cookie: 'a=1;b=2' } }, (res) => { + const res = await server.inject({ url: '/', headers: { cookie: 'a=1;b=2' } }); + expect(res.statusCode).to.equal(200); - expect(res.statusCode).to.equal(200); - const cookies = JSON.parse(res.payload); - expect(cookies).to.equal({ b: '2' }); - done(); - }); - }); + const cookies = JSON.parse(res.payload); + expect(cookies).to.equal({ b: '2' }); + + await upstream.stop(); }); - it('uses reply decorator', (done) => { + it('uses reply decorator', async () => { - const upstream = new Hapi.Server(); - upstream.connection(); + const upstream = Hapi.server(); upstream.route({ method: 'GET', path: '/', - handler: function (request, reply) { + handler: function (request, h) { - return reply('ok'); + return h.response('ok'); } }); - upstream.start(() => { + await upstream.start(); - const server = provisionServer(); - server.route({ - method: 'GET', - path: '/', - handler: function (request, reply) { + const server = await provisionServer(); + server.route({ + method: 'GET', + path: '/', + handler: function (request, h) { - return reply.proxy({ host: 'localhost', port: upstream.info.port, xforward: true, passThrough: true }); - } - }); + return h.proxy({ host: 'localhost', port: upstream.info.port, xforward: true, passThrough: true }); + } + }); - server.inject('/', (res) => { + const res = await server.inject('/'); + expect(res.statusCode).to.equal(200); + expect(res.payload).to.equal('ok'); - expect(res.statusCode).to.equal(200); - expect(res.payload).to.equal('ok'); - done(); - }); - }); + await upstream.stop(); }); - it('uses custom TLS settings', (done) => { + it('uses custom TLS settings', async () => { + - const upstream = new Hapi.Server(); - upstream.connection({ tls: tlsOptions }); + const upstream = Hapi.server({ tls: tlsOptions }); upstream.route({ method: 'GET', path: '/', - handler: function (request, reply) { + handler: function (request, h) { - return reply('ok'); + return h.response('ok'); } }); + await upstream.start(); - upstream.start(() => { - - const server = new Hapi.Server(); - server.connection({}); - server.register({ register: H2o2.register, options: { secureProtocol: 'TLSv1_2_method', ciphers: 'ECDHE-RSA-AES128-SHA256' } }); + const plugin = { + register: H2o2.register, + pkg: H2o2.pkg + }; + const options = { secureProtocol: 'TLSv1_2_method', ciphers: 'ECDHE-RSA-AES128-SHA256' }; - server.route({ - method: 'GET', - path: '/', - handler: function (request, reply) { + const server = Hapi.server(); + await server.register({ + plugin, + options + }); + server.route({ + method: 'GET', + path: '/', + handler: function (request, h) { - return reply.proxy({ host: '127.0.0.1', protocol: 'https', port: upstream.info.port, rejectUnauthorized: false }); - } - }); + return h.proxy({ host: '127.0.0.1', protocol: 'https', port: upstream.info.port, rejectUnauthorized: false }); + } + }); - server.inject('/', (res) => { + const res = await server.inject('/'); + expect(res.statusCode).to.equal(200); + expect(res.payload).to.equal('ok'); - expect(res.statusCode).to.equal(200); - expect(res.payload).to.equal('ok'); - done(); - }); - }); + await upstream.stop(); }); }); From 27356342696723a790798c1b61e605e0aa8db7c5 Mon Sep 17 00:00:00 2001 From: Gil Pedersen Date: Mon, 11 Dec 2017 15:02:49 +0100 Subject: [PATCH 19/73] Fix onResponse arguments on errors --- lib/index.js | 2 +- test/index.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/index.js b/lib/index.js index 7bb769e..1b53dc3 100644 --- a/lib/index.js +++ b/lib/index.js @@ -151,7 +151,7 @@ internals.handler = function (route, handlerOptions) { } catch (error) { if (settings.onResponse) { - return settings.onResponse.call(bind, error, res, h, settings, ttl); + return settings.onResponse.call(bind, error, res, request, h, settings, ttl); } throw error; diff --git a/test/index.js b/test/index.js index 26f5a6e..5ed5261 100644 --- a/test/index.js +++ b/test/index.js @@ -668,6 +668,7 @@ describe('H2o2', () => { const failureResponse = function (err, res, request, h, settings, ttl) { + expect(h.response).to.exist(); throw err; }; From 9e8a5fbdfb638e2d0720d50d8f2ca95ae59a8a03 Mon Sep 17 00:00:00 2001 From: Gil Pedersen Date: Mon, 11 Dec 2017 15:11:11 +0100 Subject: [PATCH 20/73] Fix late onRequest call --- lib/index.js | 8 ++++---- test/index.js | 8 ++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/index.js b/lib/index.js index 7bb769e..d28c622 100644 --- a/lib/index.js +++ b/lib/index.js @@ -146,6 +146,10 @@ internals.handler = function (route, handlerOptions) { const promise = Wreck.request(request.method, uri, options); + if (settings.onRequest) { + settings.onRequest(promise.req); + } + try { res = await promise; } @@ -167,10 +171,6 @@ internals.handler = function (route, handlerOptions) { } } - if (settings.onRequest) { - settings.onRequest(promise.req); - } - if (settings.onResponse) { return settings.onResponse.call(bind, null, res, request, h, settings, ttl); } diff --git a/test/index.js b/test/index.js index 26f5a6e..c52d87e 100644 --- a/test/index.js +++ b/test/index.js @@ -492,12 +492,20 @@ describe('H2o2', () => { it('calls onRequest when it\'s created', async () => { const upstream = Hapi.Server(); + + let upstreamRequested = false; + upstream.events.on('request', () => { + + upstreamRequested = true; + }); + await upstream.start(); let called = false; const onRequestWithSocket = function (req) { called = true; + expect(upstreamRequested).to.be.false(); expect(req).to.be.an.instanceof(Http.ClientRequest); }; From 398bac9f66172899ce9ceaa345b3652acf0e5931 Mon Sep 17 00:00:00 2001 From: Sanjay Pandit Date: Mon, 11 Dec 2017 12:01:56 -0800 Subject: [PATCH 21/73] 7.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c7bee80..100b66a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "h2o2", "description": "Proxy handler plugin for hapi.js", - "version": "7.0.0", + "version": "7.0.1", "repository": "git://github.com/hapijs/h2o2", "main": "lib/index.js", "keywords": [ From 1032924979b89805ddd05cefa83cbb4ba9f11e3d Mon Sep 17 00:00:00 2001 From: Duncan Kolba Date: Tue, 19 Dec 2017 17:02:18 +0100 Subject: [PATCH 22/73] Fix typo in README.md 'aditional' => 'additional' --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 029efad..9ca6337 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,7 @@ server.route({ proxy: { mapUri: function (request) { - console.log('doing some aditional stuff before redirecting'); + console.log('doing some additional stuff before redirecting'); return { uri: 'https://some.upstream.service.com/' }; From d1d0cd0298f3071366b994ba354a4f26244e47a5 Mon Sep 17 00:00:00 2001 From: Sanjay Pandit Date: Thu, 11 Jan 2018 12:53:24 -0800 Subject: [PATCH 23/73] resolves #70 - usage of joi.without --- lib/index.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/index.js b/lib/index.js index 1908454..42c7649 100644 --- a/lib/index.js +++ b/lib/index.js @@ -47,8 +47,10 @@ internals.schema = Joi.object({ ciphers: Joi.string() }) .xor('host', 'mapUri', 'uri') - .without('mapUri', 'port', 'protocol') - .without('uri', 'port', 'protocol'); + .without('mapUri', 'port') + .without('mapUri', 'protocol') + .without('uri', 'port') + .without('uri', 'protocol'); exports.register = function (server, pluginOptions) { From aa9d2f70060cac2bff961b665a456ebba3a02b44 Mon Sep 17 00:00:00 2001 From: Sanjay Pandit Date: Thu, 11 Jan 2018 12:53:28 -0800 Subject: [PATCH 24/73] 7.0.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 100b66a..98ad5c4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "h2o2", "description": "Proxy handler plugin for hapi.js", - "version": "7.0.1", + "version": "7.0.2", "repository": "git://github.com/hapijs/h2o2", "main": "lib/index.js", "keywords": [ From d91ed425198bca0de479b7d51e941da1b4bdf8f0 Mon Sep 17 00:00:00 2001 From: Sanjay Pandit Date: Thu, 11 Jan 2018 13:24:18 -0800 Subject: [PATCH 25/73] Tests for, and closes #63 - xfd headers --- lib/index.js | 6 +++--- test/index.js | 11 +++++------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/index.js b/lib/index.js index 42c7649..b44f270 100644 --- a/lib/index.js +++ b/lib/index.js @@ -125,9 +125,9 @@ internals.handler = function (route, handlerOptions) { request.info.remotePort && request.info.remoteAddress) { options.headers['x-forwarded-for'] = (options.headers['x-forwarded-for'] ? options.headers['x-forwarded-for'] + ',' : '') + request.info.remoteAddress; - options.headers['x-forwarded-port'] = (options.headers['x-forwarded-port'] ? options.headers['x-forwarded-port'] + ',' : '') + request.info.remotePort; - options.headers['x-forwarded-proto'] = (options.headers['x-forwarded-proto'] ? options.headers['x-forwarded-proto'] + ',' : '') + request.server.info.protocol; - options.headers['x-forwarded-host'] = (options.headers['x-forwarded-host'] ? options.headers['x-forwarded-host'] + ',' : '') + request.info.host; + options.headers['x-forwarded-port'] = options.headers['x-forwarded-port'] ? options.headers['x-forwarded-port'] : request.info.remotePort; + options.headers['x-forwarded-proto'] = options.headers['x-forwarded-proto'] ? options.headers['x-forwarded-proto'] : request.server.info.protocol; + options.headers['x-forwarded-host'] = options.headers['x-forwarded-host'] ? options.headers['x-forwarded-host'] : request.info.host; } if (settings.ciphers) { diff --git a/test/index.js b/test/index.js index 85e31d3..55694d3 100644 --- a/test/index.js +++ b/test/index.js @@ -748,7 +748,7 @@ describe('H2o2', () => { await upstream.stop(); }); - it('adds x-forwarded-* headers to existing', async () => { + it('adds x-forwarded-for headers to existing and preserves original port, proto and host', async () => { const handler = function (request, h) { @@ -784,16 +784,15 @@ describe('H2o2', () => { const result = JSON.parse(response.payload); const expectedClientAddress = '127.0.0.1'; - const expectedClientAddressAndPort = expectedClientAddress + ':' + server.info.port; + if (Net.isIPv6(server.listener.address().address)) { expectedClientAddress = '::ffff:127.0.0.1'; - expectedClientAddressAndPort = '[' + expectedClientAddress + ']:' + server.info.port; } expect(result['x-forwarded-for']).to.equal('testhost,' + expectedClientAddress); - expect(result['x-forwarded-port']).to.match(/1337\,\d+/); - expect(result['x-forwarded-proto']).to.equal('https,http'); - expect(result['x-forwarded-host']).to.equal('example.com,' + expectedClientAddressAndPort); + expect(result['x-forwarded-port']).to.equal('1337'); + expect(result['x-forwarded-proto']).to.equal('https'); + expect(result['x-forwarded-host']).to.equal('example.com'); await upstream.stop(); await server.stop(); From 2417a9b979e1a4e8332b90dfb66f09c144dbf8ba Mon Sep 17 00:00:00 2001 From: Sanjay Pandit Date: Tue, 16 Jan 2018 08:28:32 -0800 Subject: [PATCH 26/73] simplify x-fwd selection --- lib/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/index.js b/lib/index.js index b44f270..3610e68 100644 --- a/lib/index.js +++ b/lib/index.js @@ -125,9 +125,9 @@ internals.handler = function (route, handlerOptions) { request.info.remotePort && request.info.remoteAddress) { options.headers['x-forwarded-for'] = (options.headers['x-forwarded-for'] ? options.headers['x-forwarded-for'] + ',' : '') + request.info.remoteAddress; - options.headers['x-forwarded-port'] = options.headers['x-forwarded-port'] ? options.headers['x-forwarded-port'] : request.info.remotePort; - options.headers['x-forwarded-proto'] = options.headers['x-forwarded-proto'] ? options.headers['x-forwarded-proto'] : request.server.info.protocol; - options.headers['x-forwarded-host'] = options.headers['x-forwarded-host'] ? options.headers['x-forwarded-host'] : request.info.host; + options.headers['x-forwarded-port'] = options.headers['x-forwarded-port'] || request.info.remotePort; + options.headers['x-forwarded-proto'] = options.headers['x-forwarded-proto'] || request.server.info.protocol; + options.headers['x-forwarded-host'] = options.headers['x-forwarded-host'] || request.info.host; } if (settings.ciphers) { From 98b2c2c55c314a054360590d3539527109635914 Mon Sep 17 00:00:00 2001 From: Sanjay Pandit Date: Tue, 16 Jan 2018 08:29:08 -0800 Subject: [PATCH 27/73] 8.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 98ad5c4..97141da 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "h2o2", "description": "Proxy handler plugin for hapi.js", - "version": "7.0.2", + "version": "8.0.0", "repository": "git://github.com/hapijs/h2o2", "main": "lib/index.js", "keywords": [ From 178ce9d90c728f26b7b0cffe5be900ccd33fb012 Mon Sep 17 00:00:00 2001 From: Nicolas Morel Date: Wed, 21 Mar 2018 10:47:00 +0100 Subject: [PATCH 28/73] Remove new Buffer usage --- test/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/index.js b/test/index.js index 55694d3..8bb2365 100644 --- a/test/index.js +++ b/test/index.js @@ -302,7 +302,7 @@ describe('H2o2', () => { const server = await provisionServer(); server.route({ method: 'GET', path: '/gzip', handler: { proxy: { host: 'localhost', port: upstream.info.port, passThrough: true } } }); - const zipped = await Zlib.gzipSync(new Buffer('123456789012345678901234567890123456789012345678901234567890')); + const zipped = await Zlib.gzipSync(Buffer.from('123456789012345678901234567890123456789012345678901234567890')); const res = await server.inject({ url: '/gzip', headers: { 'accept-encoding': 'gzip' } }); expect(res.statusCode).to.equal(200); From 131b8d86403c2b4f743250bcf2ee4e02a11ea84a Mon Sep 17 00:00:00 2001 From: Sanjay Pandit Date: Thu, 22 Mar 2018 14:42:21 -0700 Subject: [PATCH 29/73] 8.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 97141da..e68c3e6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "h2o2", "description": "Proxy handler plugin for hapi.js", - "version": "8.0.0", + "version": "8.0.1", "repository": "git://github.com/hapijs/h2o2", "main": "lib/index.js", "keywords": [ From d2d57a4d6c76160b01da973d8e9ccf7327ea7305 Mon Sep 17 00:00:00 2001 From: Matthew Callis Date: Wed, 11 Apr 2018 16:39:29 -0700 Subject: [PATCH 30/73] Add downstream response time tracking --- README.md | 11 ++++--- lib/index.js | 21 ++++++++++++- test/index.js | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 111 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9ca6337..edea8dc 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,8 @@ const startServer = async function() { try { await server.register({ plugin: require('h2o2') }); await server.start(); - - console.log(`Server started at: ${server.info.uri}`); + + console.log(`Server started at: ${server.info.uri}`); } catch(e) { console.log('Failed to load h2o2'); @@ -77,6 +77,7 @@ The proxy handler object has the following properties: to force SSL version 3. The possible values depend on your installation of OpenSSL. Read the official OpenSSL docs for possible [SSL_METHODS](http://www.openssl.org/docs/ssl/ssl.html#DEALING_WITH_PROTOCOL_METHODS). * `ciphers` - [TLS](https://nodejs.org/api/tls.html#tls_modifying_the_default_tls_cipher_suite) list of TLS ciphers to override node's default. The possible values depend on your installation of OpenSSL. Read the official OpenSSL docs for possible [TLS_CIPHERS](https://www.openssl.org/docs/man1.0.2/apps/ciphers.html#CIPHER-LIST-FORMAT). +* `downstreamResponseTime` - logs the time spent processing the downstream request using [process.hrtime](https://nodejs.org/api/process.html#process_process_hrtime_time). Defaults to `false`. ## Usage @@ -133,14 +134,14 @@ server.route({ }); ``` ### Custom `uri` template values - + When using the `uri` option, there are optional **default** template values that can be injected from the incoming `request`: * `{protocol}` * `{host}` * `{port}` * `{path}` - + ```javascript server.route({ method: 'GET', @@ -169,7 +170,7 @@ server.route({ ``` **Note** The default variables of `{protocol}`, `{host}`, `{port}`, `{path}` take precedence - it's best to treat those as reserved when naming your own `request.params`. - + ### Using the `mapUri` and `onResponse` options Setting both options with custom functions will allow you to map the original request to an upstream service and to processing the response from the upstream service, before sending it to the client. Cannot be used together with `host`, `port`, `protocol`, or `uri`. diff --git a/lib/index.js b/lib/index.js index 3610e68..213b762 100644 --- a/lib/index.js +++ b/lib/index.js @@ -13,6 +13,7 @@ const Wreck = require('wreck'); const internals = { agents: {} // server.info.uri -> { http, https, insecure } }; +const NS_PER_SEC = 1e9; internals.defaults = { @@ -21,7 +22,8 @@ internals.defaults = { redirects: false, timeout: 1000 * 60 * 3, // Timeout request after 3 minutes localStatePassThrough: false, // Pass cookies defined by the server upstream - maxSockets: Infinity + maxSockets: Infinity, + downstreamResponseTime: false }; @@ -146,16 +148,28 @@ internals.handler = function (route, handlerOptions) { let ttl = null; let res; + let downstreamStartTime; + if (settings.downstreamResponseTime) { + downstreamStartTime = process.hrtime(); + } const promise = Wreck.request(request.method, uri, options); if (settings.onRequest) { settings.onRequest(promise.req); } + let downstreamResponseTime; try { res = await promise; + if (settings.downstreamResponseTime) { + downstreamResponseTime = process.hrtime(downstreamStartTime); + } } catch (error) { + if (settings.downstreamResponseTime) { + downstreamResponseTime = process.hrtime(downstreamStartTime); + request.log(['h2o2', 'error'], { downstreamResponseTime: downstreamResponseTime[0] * NS_PER_SEC + downstreamResponseTime[1] }); + } if (settings.onResponse) { return settings.onResponse.call(bind, error, res, request, h, settings, ttl); } @@ -163,6 +177,11 @@ internals.handler = function (route, handlerOptions) { throw error; } + if (settings.downstreamResponseTime) { + downstreamResponseTime = process.hrtime(downstreamStartTime); + request.log(['h2o2', 'success'], { downstreamResponseTime: downstreamResponseTime[0] * NS_PER_SEC + downstreamResponseTime[1] }); + } + if (settings._upstreamTtl) { const cacheControlHeader = res.headers['cache-control']; if (cacheControlHeader) { diff --git a/test/index.js b/test/index.js index 8bb2365..b92e76f 100644 --- a/test/index.js +++ b/test/index.js @@ -1713,7 +1713,6 @@ describe('H2o2', () => { it('uses custom TLS settings', async () => { - const upstream = Hapi.server({ tls: tlsOptions }); upstream.route({ method: 'GET', @@ -1751,4 +1750,89 @@ describe('H2o2', () => { await upstream.stop(); }); + + it('adds downstreamResponseTime to the response when downstreamResponseTime is set to true on success', async () => { + + const upstream = Hapi.server(); + upstream.route({ + method: 'GET', + path: '/', + handler: function (request, h) { + + return h.response('ok'); + } + }); + await upstream.start(); + + const plugin = { + register: H2o2.register, + pkg: H2o2.pkg + }; + const options = { downstreamResponseTime: true }; + + const server = Hapi.server(); + await server.register({ + plugin, + options + }); + server.route({ + method: 'GET', + path: '/', + handler: function (request, h) { + + return h.proxy({ host: 'localhost', port: upstream.info.port, xforward: true, passThrough: true }); + } + }); + + server.events.on('request', (request, event, tags) => { + + expect(Object.keys(event.data)).to.equal(['downstreamResponseTime']); + expect(tags).to.equal({ h2o2: true, success: true }); + }); + + const res = await server.inject('/'); + expect(res.statusCode).to.equal(200); + + await upstream.stop(); + }); + + it('adds downstreamResponseTime to the response when downstreamResponseTime is set to true on error', async () => { + + const failureResponse = function (err, res, request, h, settings, ttl) { + + expect(h.response).to.exist(); + throw err; + }; + + const dummy = Hapi.server(); + await dummy.start(); + const dummyPort = dummy.info.port; + await dummy.stop(Hoek.ignore); + + const plugin = { + register: H2o2.register, + pkg: H2o2.pkg + }; + const options = { downstreamResponseTime: true }; + + const server = Hapi.server(); + await server.register({ + plugin, + options + }); + server.route({ method: 'GET', path: '/failureResponse', handler: { proxy: { host: 'localhost', port: dummyPort, onResponse: failureResponse } }, config: { cache: { expiresIn: 500 } } }); + + let firstEvent = true; + server.events.on('request', (request, event, tags) => { + + if (firstEvent) { + firstEvent = false; + expect(Object.keys(event.data)).to.equal(['downstreamResponseTime']); + expect(tags).to.equal({ h2o2: true, error: true }); + } + }); + + const res = await server.inject('/failureResponse'); + expect(res.statusCode).to.equal(502); + }); }); From 86a04b2c2f3b4b0bcb04b7d3138787156456ee88 Mon Sep 17 00:00:00 2001 From: Sanjay Pandit Date: Mon, 16 Apr 2018 13:56:32 -0700 Subject: [PATCH 31/73] 8.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e68c3e6..d9b4dd5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "h2o2", "description": "Proxy handler plugin for hapi.js", - "version": "8.0.1", + "version": "8.1.0", "repository": "git://github.com/hapijs/h2o2", "main": "lib/index.js", "keywords": [ From 7988c2dd1774be7541b3b54b4fd3ebf860323e82 Mon Sep 17 00:00:00 2001 From: Sanjay Pandit Date: Wed, 25 Apr 2018 16:32:19 -0700 Subject: [PATCH 32/73] strip content-length from passthrough headers - closes #77 Also adds test for the headers that are not included in passthrough proxy --- lib/index.js | 1 + test/index.js | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/index.js b/lib/index.js index 213b762..cf8e5f5 100644 --- a/lib/index.js +++ b/lib/index.js @@ -100,6 +100,7 @@ internals.handler = function (route, handlerOptions) { if (settings.passThrough) { options.headers = Hoek.clone(request.headers); delete options.headers.host; + delete options.headers['content-length']; if (settings.acceptEncoding === false) { // Defaults to true delete options.headers['accept-encoding']; diff --git a/test/index.js b/test/index.js index b92e76f..83aa7bf 100644 --- a/test/index.js +++ b/test/index.js @@ -218,7 +218,9 @@ describe('H2o2', () => { return h.response({ status: 'success' }) .header('Custom1', 'custom header value 1') - .header('X-Custom2', 'custom header value 2'); + .header('X-Custom2', 'custom header value 2') + .header('x-hostFound', request.headers.host) + .header('x-content-length-found', request.headers['content-length']); }; const upstream = Hapi.server(); @@ -228,11 +230,18 @@ describe('H2o2', () => { const server = await provisionServer({ routes: { cors: true } }); server.route({ method: 'GET', path: '/headers', handler: { proxy: { host: 'localhost', port: upstream.info.port, passThrough: true } } }); - const res = await server.inject('/headers'); + const res = await server.inject({ + url: '/headers', + headers: { + host: 'www.h2o2.com', 'content-length': 10000 + } + }); expect(res.statusCode).to.equal(200); expect(res.payload).to.equal('{\"status\":\"success\"}'); expect(res.headers.custom1).to.equal('custom header value 1'); expect(res.headers['x-custom2']).to.equal('custom header value 2'); + expect(res.headers['x-hostFound']).to.equal(undefined); + expect(res.headers['x-content-length-found']).to.equal(undefined); await upstream.stop(); }); @@ -299,7 +308,7 @@ describe('H2o2', () => { upstream.route({ method: 'GET', path: '/gzip', handler: gzipHandler }); await upstream.start(); - const server = await provisionServer(); + const server = await provisionServer(); server.route({ method: 'GET', path: '/gzip', handler: { proxy: { host: 'localhost', port: upstream.info.port, passThrough: true } } }); const zipped = await Zlib.gzipSync(Buffer.from('123456789012345678901234567890123456789012345678901234567890')); @@ -564,7 +573,7 @@ describe('H2o2', () => { it('binds onResponse to route bind config in plugin', async () => { - const upstream = Hapi.server(); + const upstream = Hapi.server(); await upstream.start(); const plugin = { From 1799adbaa2eb837998a18b8c4da64bb57378bb2d Mon Sep 17 00:00:00 2001 From: Sanjay Pandit Date: Wed, 25 Apr 2018 16:32:27 -0700 Subject: [PATCH 33/73] 8.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d9b4dd5..96808f7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "h2o2", "description": "Proxy handler plugin for hapi.js", - "version": "8.1.0", + "version": "8.1.1", "repository": "git://github.com/hapijs/h2o2", "main": "lib/index.js", "keywords": [ From c9755160dbb266c896a8e1c9296bcfb58a61971f Mon Sep 17 00:00:00 2001 From: Matthew Callis Date: Wed, 9 May 2018 14:31:00 -0700 Subject: [PATCH 34/73] Add downstreamResponseTime to internals schema and remove duplicate time calculation --- lib/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/index.js b/lib/index.js index cf8e5f5..2f267d6 100644 --- a/lib/index.js +++ b/lib/index.js @@ -46,7 +46,8 @@ internals.schema = Joi.object({ ttl: Joi.string().valid('upstream').allow(null), maxSockets: Joi.number().positive().allow(false), secureProtocol: Joi.string(), - ciphers: Joi.string() + ciphers: Joi.string(), + downstreamResponseTime: Joi.boolean() }) .xor('host', 'mapUri', 'uri') .without('mapUri', 'port') @@ -179,7 +180,6 @@ internals.handler = function (route, handlerOptions) { } if (settings.downstreamResponseTime) { - downstreamResponseTime = process.hrtime(downstreamStartTime); request.log(['h2o2', 'success'], { downstreamResponseTime: downstreamResponseTime[0] * NS_PER_SEC + downstreamResponseTime[1] }); } From 604a487e50be875745b2a0aa5267b1cd99864206 Mon Sep 17 00:00:00 2001 From: Sanjay Pandit Date: Wed, 9 May 2018 15:14:41 -0700 Subject: [PATCH 35/73] simplify downstreamResponseTime log --- lib/index.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/index.js b/lib/index.js index 2f267d6..5af1142 100644 --- a/lib/index.js +++ b/lib/index.js @@ -165,6 +165,7 @@ internals.handler = function (route, handlerOptions) { res = await promise; if (settings.downstreamResponseTime) { downstreamResponseTime = process.hrtime(downstreamStartTime); + request.log(['h2o2', 'success'], { downstreamResponseTime: downstreamResponseTime[0] * NS_PER_SEC + downstreamResponseTime[1] }); } } catch (error) { @@ -179,10 +180,6 @@ internals.handler = function (route, handlerOptions) { throw error; } - if (settings.downstreamResponseTime) { - request.log(['h2o2', 'success'], { downstreamResponseTime: downstreamResponseTime[0] * NS_PER_SEC + downstreamResponseTime[1] }); - } - if (settings._upstreamTtl) { const cacheControlHeader = res.headers['cache-control']; if (cacheControlHeader) { From cedcaf93e95d579ec407acff0a733027eb9e878d Mon Sep 17 00:00:00 2001 From: Sanjay Pandit Date: Wed, 9 May 2018 15:14:44 -0700 Subject: [PATCH 36/73] 8.1.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 96808f7..d68003f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "h2o2", "description": "Proxy handler plugin for hapi.js", - "version": "8.1.1", + "version": "8.1.2", "repository": "git://github.com/hapijs/h2o2", "main": "lib/index.js", "keywords": [ From 6572717ed06a32982a01e1290d4b7883adb24ba9 Mon Sep 17 00:00:00 2001 From: Sanjay Pandit Date: Wed, 15 Aug 2018 17:12:29 -0700 Subject: [PATCH 37/73] update docs - #82 --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index edea8dc..9497d0a 100644 --- a/README.md +++ b/README.md @@ -63,11 +63,11 @@ The proxy handler object has the following properties: * `headers` - optional object where each key is an HTTP request header and the value is the header content. * `onRequest` - a custom function which is passed the upstream request. Function signature is `function (req)` where: * `req` - the [wreck] (https://github.com/hapijs/wreck) request to the upstream server. -* `onResponse` - a custom function for processing the response from the upstream service before sending to the client. Useful for custom error handling of responses from the proxied endpoint or other payload manipulation. Function signature is `function (err, res, request, reply, settings, ttl)` where: +* `onResponse` - a custom function for processing the response from the upstream service before sending to the client. Useful for custom error handling of responses from the proxied endpoint or other payload manipulation. Function signature is `function (err, res, request, h, settings, ttl)` where: * `err` - internal or upstream error returned from attempting to contact the upstream proxy. * `res` - the node response object received from the upstream service. `res` is a readable stream (use the [wreck](https://github.com/hapijs/wreck) module `read` method to easily convert it to a Buffer or string). * `request` - is the incoming [request object](http://hapijs.com/api#request-object). - * `reply` - the [reply interface](http://hapijs.com/api#reply-interface) function. + * `h` - the [response toolkit](https://hapijs.com/api#response-toolkit). * `settings` - the proxy handler configuration. * `ttl` - the upstream TTL in milliseconds if `proxy.ttl` it set to `'upstream'` and the upstream response included a valid 'Cache-Control' header with 'max-age'. * `ttl` - if set to `'upstream'`, applies the upstream response caching policy to the response using the `response.ttl()` method (or passed as an argument to the `onResponse` method if provided). @@ -188,13 +188,15 @@ server.route({ uri: 'https://some.upstream.service.com/' }; }, - onResponse: function (err, res, request, reply, settings, ttl) { + onResponse: function (err, res, request, h, settings, ttl) { console.log('receiving the response from the upstream.'); Wreck.read(res, { json: true }, function (err, payload) { console.log('some payload manipulation if you want to.') - reply(payload).headers = res.headers; + const response = h.response(payload); + response.headers = res.headers; + return response; }); } } From b96d8cb0c75496aa6d96cf4273572e6844a6002d Mon Sep 17 00:00:00 2001 From: Aditya Singh Date: Sat, 8 Sep 2018 23:41:32 +0530 Subject: [PATCH 38/73] README.md - Fixed a broken link of SSL_METHODS from openssl site, and also added a space between two separating two words. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9497d0a..cb71008 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ The proxy handler object has the following properties: * 'https' * `uri` - absolute URI used instead of host, port, protocol, path, and query. Cannot be used with `host`, `port`, `protocol`, or `mapUri`. * `passThrough` - if set to `true`, it forwards the headers from the client to the upstream service, headers sent from the upstream service will also be forwarded to the client. Defaults to `false`. -* `localStatePassThrough` - if set to`false`, any locally defined state is removed from incoming requests before being sent to the upstream service. This value can be overridden on a per state basis via the `server.state()``passThrough` option. Defaults to `false` +* `localStatePassThrough` - if set to`false`, any locally defined state is removed from incoming requests before being sent to the upstream service. This value can be overridden on a per state basis via the `server.state()` `passThrough` option. Defaults to `false` * `acceptEncoding` - if set to `false`, does not pass-through the 'Accept-Encoding' HTTP header which is useful for the `onResponse` post-processing to avoid receiving an encoded response. Can only be used together with `passThrough`. Defaults to `true` (passing header). * `rejectUnauthorized` - sets the `rejectUnauthorized` property on the https [agent](http://nodejs.org/api/https.html#https_https_request_options_callback) making the request. This value is only used when the proxied server uses TLS/SSL. If set it will override the node.js `rejectUnauthorized` property. If `false` then ssl errors will be ignored. When `true` the server certificate is verified and an 500 response will be sent when verification fails. This shouldn't be used alongside the `agent` setting as the `agent` will be used instead. Defaults to the https agent default value of `true`. * `xforward` - if set to `true`, sets the 'X-Forwarded-For', 'X-Forwarded-Port', 'X-Forwarded-Proto', 'X-Forwarded-Host' headers when making a request to the proxied upstream endpoint. Defaults to `false`. @@ -74,7 +74,7 @@ The proxy handler object has the following properties: * `agent` - a node [http(s) agent](http://nodejs.org/api/http.html#http_class_http_agent) to be used for connections to upstream server. * `maxSockets` - sets the maximum number of sockets available per outgoing proxy host connection. `false` means use the **wreck** module default value (`Infinity`). Does not affect non-proxy outgoing client connections. Defaults to `Infinity`. * `secureProtocol` - [TLS](http://nodejs.org/api/tls.html) flag indicating the SSL method to use, e.g. `SSLv3_method` -to force SSL version 3. The possible values depend on your installation of OpenSSL. Read the official OpenSSL docs for possible [SSL_METHODS](http://www.openssl.org/docs/ssl/ssl.html#DEALING_WITH_PROTOCOL_METHODS). +to force SSL version 3. The possible values depend on your installation of OpenSSL. Read the official OpenSSL docs for possible [SSL_METHODS](https://www.openssl.org/docs/man1.0.2/ssl/ssl.html). * `ciphers` - [TLS](https://nodejs.org/api/tls.html#tls_modifying_the_default_tls_cipher_suite) list of TLS ciphers to override node's default. The possible values depend on your installation of OpenSSL. Read the official OpenSSL docs for possible [TLS_CIPHERS](https://www.openssl.org/docs/man1.0.2/apps/ciphers.html#CIPHER-LIST-FORMAT). * `downstreamResponseTime` - logs the time spent processing the downstream request using [process.hrtime](https://nodejs.org/api/process.html#process_process_hrtime_time). Defaults to `false`. From 4e277e04dbbc49d2f5468df028d6c41486330740 Mon Sep 17 00:00:00 2001 From: Sanjay Pandit Date: Sat, 8 Sep 2018 16:49:12 -0700 Subject: [PATCH 39/73] 8.1.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d68003f..f728c3f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "h2o2", "description": "Proxy handler plugin for hapi.js", - "version": "8.1.2", + "version": "8.1.3", "repository": "git://github.com/hapijs/h2o2", "main": "lib/index.js", "keywords": [ From 3c29261e1c5666d0df1a527568249f247bf12175 Mon Sep 17 00:00:00 2001 From: PAUTHIER Jonas Date: Mon, 3 Dec 2018 13:19:08 +0100 Subject: [PATCH 40/73] Add changelog.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..661dade --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +Breaking changes are documented using GitHub issues, see [issues labeled "release notes"](https://github.com/hapijs/h2o2/issues?q=is%3Aissue+label%3A%22release+notes%22). + +If you want changes of a specific minor or patch release, you can browse the [GitHub milestones](https://github.com/hapijs/h2o2/milestones?state=closed&direction=asc&sort=due_date). From 246ddbd009b24daa2ebb87e0654acda3a7ec5fc0 Mon Sep 17 00:00:00 2001 From: Sanjay Pandit Date: Thu, 21 Mar 2019 14:26:51 -0700 Subject: [PATCH 41/73] hapi18 url compatibility - closes #88 --- lib/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/index.js b/lib/index.js index 5af1142..a50ab75 100644 --- a/lib/index.js +++ b/lib/index.js @@ -227,7 +227,7 @@ internals.mapUri = function (protocol, host, port, uri) { let address = uri.replace(/{protocol}/g, request.server.info.protocol) .replace(/{host}/g, request.server.info.host) .replace(/{port}/g, request.server.info.port) - .replace(/{path}/g, request.url.path); + .replace(/{path}/g, request.path); Object.keys(request.params).forEach((key) => { From 952420f71ee117dd697850a034e14b00c76c868a Mon Sep 17 00:00:00 2001 From: Sanjay Pandit Date: Thu, 21 Mar 2019 14:26:57 -0700 Subject: [PATCH 42/73] 8.1.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f728c3f..8d7778a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "h2o2", "description": "Proxy handler plugin for hapi.js", - "version": "8.1.3", + "version": "8.1.4", "repository": "git://github.com/hapijs/h2o2", "main": "lib/index.js", "keywords": [ From e3fdb086bddb11f851716b352e6b2946c35af0e0 Mon Sep 17 00:00:00 2001 From: Tucker Bickler Date: Fri, 5 Apr 2019 15:20:54 -0500 Subject: [PATCH 43/73] Add `httpClient` option - Closes #92 --- README.md | 29 ++++++++++++++++++++-- lib/index.js | 13 ++++++++-- test/index.js | 69 ++++++++++++++++++++++++++++++++------------------- 3 files changed, 82 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index cb71008..03aae63 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ _**NOTE**: h2o2 is included with and loaded by default in Hapi < 9.0._ ## Options -The plugin can be registered with an optional object specifying defaults to be applied to the proxy handler object. +The plugin can be registered with an optional object specifying defaults to be applied to the proxy handler object. The proxy handler object has the following properties: @@ -50,6 +50,7 @@ The proxy handler object has the following properties: * 'http' * 'https' * `uri` - absolute URI used instead of host, port, protocol, path, and query. Cannot be used with `host`, `port`, `protocol`, or `mapUri`. +* `httpClient` - an http client that abides by the Wreck interface. Defaults to [`wreck`](https://github.com/hapijs/wreck). * `passThrough` - if set to `true`, it forwards the headers from the client to the upstream service, headers sent from the upstream service will also be forwarded to the client. Defaults to `false`. * `localStatePassThrough` - if set to`false`, any locally defined state is removed from incoming requests before being sent to the upstream service. This value can be overridden on a per state basis via the `server.state()` `passThrough` option. Defaults to `false` * `acceptEncoding` - if set to `false`, does not pass-through the 'Accept-Encoding' HTTP header which is useful for the `onResponse` post-processing to avoid receiving an encoded response. Can only be used together with `passThrough`. Defaults to `true` (passing header). @@ -75,7 +76,7 @@ The proxy handler object has the following properties: * `maxSockets` - sets the maximum number of sockets available per outgoing proxy host connection. `false` means use the **wreck** module default value (`Infinity`). Does not affect non-proxy outgoing client connections. Defaults to `Infinity`. * `secureProtocol` - [TLS](http://nodejs.org/api/tls.html) flag indicating the SSL method to use, e.g. `SSLv3_method` to force SSL version 3. The possible values depend on your installation of OpenSSL. Read the official OpenSSL docs for possible [SSL_METHODS](https://www.openssl.org/docs/man1.0.2/ssl/ssl.html). -* `ciphers` - [TLS](https://nodejs.org/api/tls.html#tls_modifying_the_default_tls_cipher_suite) list of TLS ciphers to override node's default. +* `ciphers` - [TLS](https://nodejs.org/api/tls.html#tls_modifying_the_default_tls_cipher_suite) list of TLS ciphers to override node's default. The possible values depend on your installation of OpenSSL. Read the official OpenSSL docs for possible [TLS_CIPHERS](https://www.openssl.org/docs/man1.0.2/apps/ciphers.html#CIPHER-LIST-FORMAT). * `downstreamResponseTime` - logs the time spent processing the downstream request using [process.hrtime](https://nodejs.org/api/process.html#process_process_hrtime_time). Defaults to `false`. @@ -204,3 +205,27 @@ server.route({ }); ``` + + +### Using a custom http client + +By default, `h2o2` uses Wreck to perform requests. A custom http client can be provided by passing a client to `httpClient`, as long as it abides by the [`wreck`](https://github.com/hapijs/wreck) interface. The two functions that `h2o2` utilizes are `request()` and `parseCacheControl()`. + +```javascript +server.route({ + method: 'GET', + path: '/', + handler: { + proxy: { + httpClient: { + request(method, uri, options) { + return axios({ + method, + url: 'https://some.upstream.service.com/' + }) + } + } + } + } +}); +``` \ No newline at end of file diff --git a/lib/index.js b/lib/index.js index a50ab75..c72b46e 100644 --- a/lib/index.js +++ b/lib/index.js @@ -17,6 +17,10 @@ const NS_PER_SEC = 1e9; internals.defaults = { + httpClient: { + request: Wreck.request.bind(Wreck), + parseCacheControl: Wreck.parseCacheControl.bind(Wreck) + }, xforward: false, passThrough: false, redirects: false, @@ -28,6 +32,10 @@ internals.defaults = { internals.schema = Joi.object({ + httpClient: Joi.object({ + request: Joi.func(), + parseCacheControl: Joi.func() + }), host: Joi.string(), port: Joi.number().integer(), protocol: Joi.string().valid('http', 'https', 'http:', 'https:'), @@ -154,7 +162,8 @@ internals.handler = function (route, handlerOptions) { if (settings.downstreamResponseTime) { downstreamStartTime = process.hrtime(); } - const promise = Wreck.request(request.method, uri, options); + + const promise = settings.httpClient.request(request.method, uri, options); if (settings.onRequest) { settings.onRequest(promise.req); @@ -183,7 +192,7 @@ internals.handler = function (route, handlerOptions) { if (settings._upstreamTtl) { const cacheControlHeader = res.headers['cache-control']; if (cacheControlHeader) { - const cacheControl = Wreck.parseCacheControl(cacheControlHeader); + const cacheControl = settings.httpClient.parseCacheControl(cacheControlHeader); if (cacheControl) { ttl = cacheControl['max-age'] * 1000; } diff --git a/test/index.js b/test/index.js index 83aa7bf..fddd4fd 100644 --- a/test/index.js +++ b/test/index.js @@ -50,17 +50,17 @@ describe('H2o2', () => { it('overrides maxSockets', { parallel: false }, async () => { let maxSockets; - const orig = Wreck.request; - Wreck.request = function (method, uri, options, callback) { + const httpClient = { + request(method, uri, options, callback) { - Wreck.request = orig; - maxSockets = options.agent.maxSockets; + maxSockets = options.agent.maxSockets; - return { statusCode: 200 }; + return { statusCode: 200 }; + } }; const server = await provisionServer(); - server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', maxSockets: 213 } } }); + server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', httpClient, maxSockets: 213 } } }); await server.inject('/'); expect(maxSockets).to.equal(213); }); @@ -68,17 +68,17 @@ describe('H2o2', () => { it('uses node default with maxSockets set to false', { parallel: false }, async () => { let agent; - const orig = Wreck.request; - Wreck.request = function (method, uri, options) { + const httpClient = { + request(method, uri, options) { - Wreck.request = orig; - agent = options.agent; + agent = options.agent; - return { statusCode: 200 }; + return { statusCode: 200 }; + } }; const server = await provisionServer(); - server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', maxSockets: false } } }); + server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', httpClient, maxSockets: false } } }); await server.inject('/'); expect(agent).to.equal(undefined); }); @@ -1470,15 +1470,15 @@ describe('H2o2', () => { const server = await provisionServer(); - const requestFn = Wreck.request; - Wreck.request = function (method, url, options) { + const httpClient = { + request(method, uri, options, callback) { - Wreck.request = requestFn; - expect(options.headers['content-type']).to.equal('application/json'); - expect(options.headers['Content-Type']).to.not.exist(); - throw new Error('placeholder'); + expect(options.headers['content-type']).to.equal('application/json'); + expect(options.headers['Content-Type']).to.not.exist(); + throw new Error('placeholder'); + } }; - server.route({ method: 'GET', path: '/test', handler: { proxy: { uri: 'http://localhost', passThrough: true } } }); + server.route({ method: 'GET', path: '/test', handler: { proxy: { uri: 'http://localhost', httpClient, passThrough: true } } }); await server.inject({ method: 'GET', url: '/test', headers: { 'Content-Type': 'application/json' } }); }); @@ -1487,14 +1487,14 @@ describe('H2o2', () => { const server = await provisionServer(); const agent = { name: 'myagent' }; - const requestFn = Wreck.request; - Wreck.request = function (method, url, options) { + const httpClient = { + request(method, uri, options, callback) { - Wreck.request = requestFn; - expect(options.agent).to.equal(agent); - return { statusCode: 200 }; + expect(options.agent).to.equal(agent); + return { statusCode: 200 }; + } }; - server.route({ method: 'GET', path: '/agenttest', handler: { proxy: { uri: 'http://localhost', agent } } }); + server.route({ method: 'GET', path: '/agenttest', handler: { proxy: { uri: 'http://localhost', httpClient, agent } } }); await server.inject({ method: 'GET', url: '/agenttest', headers: {} }, (res) => { }); }); @@ -1844,4 +1844,23 @@ describe('H2o2', () => { const res = await server.inject('/failureResponse'); expect(res.statusCode).to.equal(502); }); + + it('uses a custom http-client', async () => { + + const upstream = Hapi.server(); + upstream.route({ method: 'GET', path: '/', handler: () => 'ok' }); + await upstream.start(); + + const httpClient = { + request: Wreck.request.bind(Wreck), + parseCacheControl: Wreck.parseCacheControl.bind(Wreck) + }; + + const server = await provisionServer(); + server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', port: upstream.info.port, httpClient } } }); + + const res = await server.inject('/'); + + expect(res.payload).to.equal('ok'); + }); }); From 457835f4866f306162af9adab809d859d997f03e Mon Sep 17 00:00:00 2001 From: Sanjay Pandit Date: Tue, 9 Apr 2019 15:28:21 -0700 Subject: [PATCH 44/73] 8.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8d7778a..825a4a2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "h2o2", "description": "Proxy handler plugin for hapi.js", - "version": "8.1.4", + "version": "8.2.0", "repository": "git://github.com/hapijs/h2o2", "main": "lib/index.js", "keywords": [ From 8bf35c339cad7dc1b709ab54904c05ae7b626872 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Sat, 13 Apr 2019 15:23:58 -0700 Subject: [PATCH 45/73] Change namespace. Closes #94. Closes #95 --- .gitignore | 29 ++-- .travis.yml | 16 +- CONTRIBUTING.md | 16 -- LICENSE | 28 ---- LICENSE.md | 10 ++ README.md | 28 ++-- lib/index.js | 73 +++++---- package.json | 33 ++-- test/index.js | 400 ++++++++++++++++++++++++++++++------------------ 9 files changed, 351 insertions(+), 282 deletions(-) delete mode 100755 CONTRIBUTING.md delete mode 100755 LICENSE create mode 100755 LICENSE.md mode change 100644 => 100755 lib/index.js mode change 100644 => 100755 test/index.js diff --git a/.gitignore b/.gitignore index 7e1574d..8f679c9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,13 @@ -.idea -*.iml -npm-debug.log -dump.rdb -node_modules -results.tap -results.xml -npm-shrinkwrap.json -config.json -.DS_Store -*/.DS_Store -*/*/.DS_Store -._* -*/._* -*/*/._* +**/node_modules +**/package-lock.json + coverage.* -lib-cov -complexity.md + +**/.DS_Store +**/._* + +**/*.pem + +**/.vs +**/.vscode +**/.idea diff --git a/.travis.yml b/.travis.yml index a3ed993..fe9f8dd 100755 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,21 @@ language: node_js node_js: - "8" - - "9" + - "10" + - "11" - "node" sudo: false + +install: + - "npm install" + - "npm install hapi@$HAPI_VERSION" + +env: + - HAPI_VERSION="17" + - HAPI_VERSION="18" + +os: + - "linux" + - "osx" + - "windows" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100755 index 6518aa6..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,16 +0,0 @@ -# How to contribute -We welcome contributions from the community and are pleased to have them. Please follow this guide when logging issues or making code changes. - -## Logging Issues -All issues should be created using the [new issue form](https://github.com/hapijs/h2o2/issues/new). Clearly describe the issue including steps -to reproduce if there are any. Also, make sure to indicate the earliest version that has the issue being reported. - -## Patching Code - -Code changes are welcome and should follow the guidelines below. - -* Fork the repository on GitHub. -* Fix the issue ensuring that your code follows the [style guide](https://github.com/hapijs/contrib/blob/master/Style.md). -* Add tests for your new code ensuring that you have 100% code coverage (we can help you reach 100% but will not merge without it). - * Run `npm test` to generate a report of test coverage -* [Pull requests](http://help.github.com/send-pull-requests/) should be made to the [master branch](https://github.com/hapijs/h2o2/tree/master). diff --git a/LICENSE b/LICENSE deleted file mode 100755 index d5f6de6..0000000 --- a/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -Copyright (c) 2012-2014, Walmart and other contributors. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * The names of any contributors may not be used to endorse or promote - products derived from this software without specific prior written - permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - * * * - -The complete list of contributors can be found at: https://github.com/hapijs/h2o2/graphs/contributors diff --git a/LICENSE.md b/LICENSE.md new file mode 100755 index 0000000..aa2404c --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,10 @@ +Copyright (c) 2012-2019, Sideway Inc, and project contributors +Copyright (c) 2012-2014, Walmart. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +* The names of any contributors may not be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS OFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 03aae63..6aec8b2 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,27 @@ + + # h2o2 Proxy handler plugin for hapi.js. -[![NPM](https://nodei.co/npm/h2o2.png?downloads=true&stars=true)](https://nodei.co/npm/h2o2/) - [![Build Status](https://secure.travis-ci.org/hapijs/h2o2.png)](http://travis-ci.org/hapijs/h2o2) -Lead Maintainer - [Sanjay Pandit](https://github.com/spanditcaa) - ## Introduction -**h2o2** is a hapi plugin that adds proxying functionality. +**h2o2** adds proxying functionality to a hapi server. ## Manual loading -H2o2 version 7 requires Hapi 17. For use with Hapi v16.x.x, please use H2o2 @v6.x.x - -Starting on version 9, `hapi` does not load the `h2o2` automatically. To add `h2o2` to your server, you should register it normally. ```javascript -const Hapi = require('hapi'); -const server = Hapi.server(); +const Hapi = require('@hapi/hapi'); +const H2o2 = require('@hapi/h2o2'); -const startServer = async function() { + +const start = async function() { + + const server = Hapi.server(); try { - await server.register({ plugin: require('h2o2') }); + await server.register(H2o2); await server.start(); console.log(`Server started at: ${server.info.uri}`); @@ -33,10 +31,8 @@ const startServer = async function() { } } -startServer(); +start(); ``` -_**NOTE**: h2o2 is included with and loaded by default in Hapi < 9.0._ - ## Options @@ -228,4 +224,4 @@ server.route({ } } }); -``` \ No newline at end of file +``` diff --git a/lib/index.js b/lib/index.js old mode 100644 new mode 100755 index c72b46e..7c9474d --- a/lib/index.js +++ b/lib/index.js @@ -1,19 +1,16 @@ 'use strict'; -// Load modules const Http = require('http'); const Https = require('https'); -const Hoek = require('hoek'); -const Joi = require('joi'); -const Wreck = require('wreck'); +const Hoek = require('@hapi/hoek'); +const Joi = require('@hapi/joi'); +const Wreck = require('@hapi/wreck'); -// Declare internals const internals = { - agents: {} // server.info.uri -> { http, https, insecure } + NS_PER_SEC: 1e9 }; -const NS_PER_SEC = 1e9; internals.defaults = { @@ -64,20 +61,23 @@ internals.schema = Joi.object({ .without('uri', 'protocol'); -exports.register = function (server, pluginOptions) { - - internals.defaults = Hoek.applyToDefaults(internals.defaults, pluginOptions); +exports.plugin = { + name: 'h2o2', // Override package name + pkg: require('../package.json'), + requirements: { + hapi: '>=17.9.0' + }, - server.decorate('handler', 'proxy', internals.handler); + register: function (server, options) { - server.decorate('toolkit', 'proxy', function (options) { + internals.defaults = Hoek.applyToDefaults(internals.defaults, options); - return internals.handler(this.request.route, options)(this.request, this); - }); + server.expose('_agents', new Map()); // server.info.uri -> { http, https, insecure } + server.decorate('handler', 'proxy', internals.handler); + server.decorate('toolkit', 'proxy', internals.toolkit); + } }; -exports.pkg = require('../package.json'); - internals.handler = function (route, handlerOptions) { @@ -134,8 +134,8 @@ internals.handler = function (route, handlerOptions) { } if (settings.xforward && - request.info.remotePort && - request.info.remoteAddress) { + request.info.remotePort) { + options.headers['x-forwarded-for'] = (options.headers['x-forwarded-for'] ? options.headers['x-forwarded-for'] + ',' : '') + request.info.remoteAddress; options.headers['x-forwarded-port'] = options.headers['x-forwarded-port'] || request.info.remotePort; options.headers['x-forwarded-proto'] = options.headers['x-forwarded-proto'] || request.server.info.protocol; @@ -156,7 +156,6 @@ internals.handler = function (route, handlerOptions) { } let ttl = null; - let res; let downstreamStartTime; if (settings.downstreamResponseTime) { @@ -169,24 +168,24 @@ internals.handler = function (route, handlerOptions) { settings.onRequest(promise.req); } - let downstreamResponseTime; try { - res = await promise; + var res = await promise; if (settings.downstreamResponseTime) { - downstreamResponseTime = process.hrtime(downstreamStartTime); - request.log(['h2o2', 'success'], { downstreamResponseTime: downstreamResponseTime[0] * NS_PER_SEC + downstreamResponseTime[1] }); + const downstreamResponseTime = process.hrtime(downstreamStartTime); + request.log(['h2o2', 'success'], { downstreamResponseTime: downstreamResponseTime[0] * internals.NS_PER_SEC + downstreamResponseTime[1] }); } } - catch (error) { + catch (err) { if (settings.downstreamResponseTime) { - downstreamResponseTime = process.hrtime(downstreamStartTime); - request.log(['h2o2', 'error'], { downstreamResponseTime: downstreamResponseTime[0] * NS_PER_SEC + downstreamResponseTime[1] }); + const downstreamResponseTime = process.hrtime(downstreamStartTime); + request.log(['h2o2', 'error'], { downstreamResponseTime: downstreamResponseTime[0] * internals.NS_PER_SEC + downstreamResponseTime[1] }); } + if (settings.onResponse) { - return settings.onResponse.call(bind, error, res, request, h, settings, ttl); + return settings.onResponse.call(bind, err, res, request, h, settings, ttl); } - throw error; + throw err; } if (settings._upstreamTtl) { @@ -224,6 +223,12 @@ internals.handler.defaults = function (method) { }; +internals.toolkit = function (options) { + + return internals.handler(this.request.route, options)(this.request, this); +}; + + internals.mapUri = function (protocol, host, port, uri) { if (uri) { @@ -240,8 +245,8 @@ internals.mapUri = function (protocol, host, port, uri) { Object.keys(request.params).forEach((key) => { - const re = new RegExp(`{${key}}`,'g'); - address = address.replace(re,request.params[key]); + const re = new RegExp(`{${key}}`, 'g'); + address = address.replace(re, request.params[key]); }); return { @@ -280,8 +285,12 @@ internals.agent = function (protocol, settings, request) { return undefined; } - internals.agents[request.info.uri] = internals.agents[request.info.uri] || {}; - const agents = internals.agents[request.info.uri]; + const store = request.server.plugins.h2o2._agents; + if (!store.has(request.info.uri)) { + store.set(request.info.uri, {}); + } + + const agents = store.get(request.info.uri); const type = (protocol === 'http' ? 'http' : (settings.rejectUnauthorized === false ? 'insecure' : 'https')); if (!agents[type]) { diff --git a/package.json b/package.json index 825a4a2..1f4d7d6 100644 --- a/package.json +++ b/package.json @@ -1,34 +1,25 @@ { - "name": "h2o2", + "name": "@hapi/h2o2", "description": "Proxy handler plugin for hapi.js", "version": "8.2.0", "repository": "git://github.com/hapijs/h2o2", "main": "lib/index.js", - "keywords": [ - "HTTP", - "proxy", - "handler", - "hapi", - "plugin" - ], - "engines": { - "node": ">=8.x.x" - }, + "keywords": ["HTTP", "proxy", "handler", "hapi", "plugin"], "dependencies": { - "boom": "7.x.x", - "hoek": "5.x.x", - "joi": "13.x.x", - "wreck": "14.x.x" + "@hapi/boom": "7.x.x", + "@hapi/hoek": "6.x.x", + "@hapi/joi": "15.x.x", + "@hapi/wreck": "15.x.x" }, "devDependencies": { - "code": "5.x.x", - "hapi": "17.x.x", - "inert": "5.x.x", - "lab": "15.x.x" + "@hapi/code": "5.x.x", + "@hapi/hapi": "18.x.x", + "@hapi/inert": "5.x.x", + "@hapi/lab": "18.x.x" }, "scripts": { - "test": "lab -a code -t 100 -L", - "test-cov-html": "lab -a code -r html -o coverage.html" + "test": "lab -a @hapi/code -t 100 -L", + "test-cov-html": "lab -a @hapi/code -r html -o coverage.html" }, "license": "BSD-3-Clause" } diff --git a/test/index.js b/test/index.js old mode 100644 new mode 100755 index fddd4fd..e2ed038 --- a/test/index.js +++ b/test/index.js @@ -1,52 +1,34 @@ 'use strict'; -// Load modules const Fs = require('fs'); const Http = require('http'); const Net = require('net'); const Zlib = require('zlib'); -const Boom = require('boom'); -const Code = require('code'); -const H2o2 = require('..'); -const Hapi = require('hapi'); -const Hoek = require('hoek'); -const Lab = require('lab'); -const Wreck = require('wreck'); +const Boom = require('@hapi/boom'); +const Code = require('@hapi/code'); +const H2o2 = require('..'); +const Hapi = require('@hapi/hapi'); +const Hoek = require('@hapi/hoek'); +const Inert = require('@hapi/inert'); +const Lab = require('@hapi/lab'); +const Wreck = require('@hapi/wreck'); -// Declare internals const internals = {}; -// Test shortcuts - -const lab = exports.lab = Lab.script(); -const describe = lab.describe; -const it = lab.it; +const { it, describe } = exports.lab = Lab.script(); const expect = Code.expect; -describe('H2o2', () => { + +describe('h2o2', () => { const tlsOptions = { key: '-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEA3IDFzxorKO8xWeCOosuK1pCPoTUMlhOkis4pWO9CLCv0o0Q7\nyUCZlHzPYWM49+QmWe5u3Xbl1rhkFsoeYowH1bts5r6HY8xYHexvU+6zEyxOU4Q7\nP7EXkFfW5h7WsO6uaEyEBVdniTIjK4c8hzjy7h6hNIvM+kEAAy1UFatMKmOwsp4Z\ns4+oCmS4ZPlItAMbRv/4a5DCopluOS7WN8UwwJ6zRrY8ZVFnkKPThflnwiaIy2Qh\nGgTwLANIUlWPQMh+LLHnV56NOlj1VUO03G+pKxTJ6ZkfYefaD41Ez4iPc7nyg4iD\njqnqFX+jYOLRoCktztYd9T43Sgb2sfgrlY0ENwIDAQABAoIBAQCoznyg/CumfteN\nMvh/cMutT6Zlh7NHAWqqSQImb6R9JHl4tDgA7k+k+ZfZuphWTnd9yadeLDPwmeEm\nAT4Zu5IT8hSA4cPMhxe+cM8ZtlepifW8wjKJpA2iF10RdvJtKYyjlFBNtogw5A1A\nuZuA+fwgh5pqG8ykmTZlOEJzBFye5Z7xKc/gwy9BGv3RLNVf+yaJCqPKLltkAxtu\nFmrBLuIZMoOJvT+btgVxHb/nRVzURKv5iKMY6t3JM84OSxNn0/tHpX2xTcqsVre+\nsdSokKGYoyzk/9miDYhoSVOrM3bU5/ygBDt1Pmf/iyK/MDO2P9tX9cEp/+enJc7a\nLg5O/XCBAoGBAPNwayF6DLu0PKErsdCG5dwGrxhC69+NBEJkVDMPMjSHXAQWneuy\n70H+t2QHxpDbi5wMze0ZClMlgs1wItm4/6iuvOn9HJczwiIG5yM9ZJo+OFIqlBq3\n1vQG+oEXe5VpTfpyQihxqTSiMuCXkTYtNjneHseXWAjFuUQe9AOxxzNRAoGBAOfh\nZEEDY7I1Ppuz7bG1D6lmzYOTZZFfMCVGGTrYmam02+rS8NC+MT0wRFCblQ0E7SzM\nr9Bv2vbjrLY5fCe/yscF+/u/UHJu1dR7j62htdYeSi7XbQiSwyUm1QkMXjKDQPUw\njwR3WO8ZHQf2tywE+7iRs/bJ++Oolaw03HoIp40HAoGBAJJwGpGduJElH5+YCDO3\nIghUIPnIL9lfG6PQdHHufzXoAusWq9J/5brePXU31DOJTZcGgM1SVcqkcuWfwecU\niP3wdwWOU6eE5A/R9TJWmPDL4tdSc5sK4YwTspb7CEVdfiHcn31yueVGeLJvmlNr\nqQXwXrWTjcphHkwjDog2ZeyxAoGBAJ5Yyq+i8uf1eEW3v3AFZyaVr25Ur51wVV5+\n2ifXVkgP28YmOpEx8EoKtfwd4tE7NgPL25wJZowGuiDObLxwOrdinMszwGoEyj0K\nC/nUXmpT0PDf5/Nc1ap/NCezrHfuLePCP0gbgD329l5D2p5S4NsPlMfI8xxqOZuZ\nlZ44XsLtAoGADiM3cnCZ6x6/e5UQGfXa6xN7KoAkjjyO+0gu2AF0U0jDFemu1BNQ\nCRpe9zVX9AJ9XEefNUGfOI4bhRR60RTJ0lB5Aeu1xAT/OId0VTu1wRrbcnwMHGOo\nf7Kk1Vk5+1T7f1QbTu/q4ddp22PEt2oGJ7widRTZrr/gtH2wYUEjMVQ=\n-----END RSA PRIVATE KEY-----\n', cert: '-----BEGIN CERTIFICATE-----\nMIIC+zCCAeOgAwIBAgIJANnDRcmEqJssMA0GCSqGSIb3DQEBBQUAMBQxEjAQBgNV\nBAMMCWxvY2FsaG9zdDAeFw0xNzA5MTIyMjMxMDRaFw0yNzA5MTAyMjMxMDRaMBQx\nEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC\nggEBANyAxc8aKyjvMVngjqLLitaQj6E1DJYTpIrOKVjvQiwr9KNEO8lAmZR8z2Fj\nOPfkJlnubt125da4ZBbKHmKMB9W7bOa+h2PMWB3sb1PusxMsTlOEOz+xF5BX1uYe\n1rDurmhMhAVXZ4kyIyuHPIc48u4eoTSLzPpBAAMtVBWrTCpjsLKeGbOPqApkuGT5\nSLQDG0b/+GuQwqKZbjku1jfFMMCes0a2PGVRZ5Cj04X5Z8ImiMtkIRoE8CwDSFJV\nj0DIfiyx51eejTpY9VVDtNxvqSsUyemZH2Hn2g+NRM+Ij3O58oOIg46p6hV/o2Di\n0aApLc7WHfU+N0oG9rH4K5WNBDcCAwEAAaNQME4wHQYDVR0OBBYEFJBSho+nF530\nsxpoBxYqD/ynn/t0MB8GA1UdIwQYMBaAFJBSho+nF530sxpoBxYqD/ynn/t0MAwG\nA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAJFAh3X5CYFAl0cI6Q7Vcp4H\nO0S8s/C4FHNIsyUu54NcRH3taUwn3Fshn5LiwaEdFmouALbxMaejvEVw7hVBtY9X\nOjqt0mZ6+X6GOFhoUvlaG1c7YLOk5x51TXchg8YD2wxNXS0rOrAdZaScOsy8Q62S\nHehBJMN19JK8TiR3XXzxKVNcFcg0wyQvCGgjrHReaUF8WePfWHtZDdP01kBmMEIo\n6wY7E3jFqvDUs33vTOB5kmWixIoJKmkgOVmbgchmu7z27n3J+fawNr2r4IwjdUpK\nc1KvFYBXLiT+2UVkOJbBZ3C8mKfhXKHs2CrI3cSa4+E0sxTy4joG/yzlRs5l954=\n-----END CERTIFICATE-----\n' }; - const provisionServer = async function (options) { - - const server = Hapi.server(options); - - try { - await server.register(H2o2); - } - catch (err) { - console.log(err); - } - - return server; - }; - it('overrides maxSockets', { parallel: false }, async () => { let maxSockets; @@ -59,7 +41,9 @@ describe('H2o2', () => { } }; - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', httpClient, maxSockets: 213 } } }); await server.inject('/'); expect(maxSockets).to.equal(213); @@ -77,7 +61,9 @@ describe('H2o2', () => { } }; - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', httpClient, maxSockets: false } } }); await server.inject('/'); expect(agent).to.equal(undefined); @@ -94,7 +80,9 @@ describe('H2o2', () => { upstream.route({ method: 'GET', path: '/profile', handler: profileHandler, config: { cache: { expiresIn: 2000, privacy: 'private' } } }); await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/profile', handler: { proxy: { host: 'localhost', port: upstream.info.port, xforward: true, passThrough: true } } }); server.state('auto', { autoValue: 'xyz' }); @@ -112,9 +100,36 @@ describe('H2o2', () => { await upstream.stop(); }); + it('forwards on the response when making an OPTIONS request', async () => { + + const upstream = Hapi.server(); + upstream.route({ method: 'OPTIONS', path: '/', handler: () => 'test' }); + await upstream.start(); + + const server = Hapi.server(); + await server.register(H2o2); + + server.route({ + method: 'OPTIONS', + path: '/', + options: { + payload: { parse: false }, + handler: (request, h) => h.proxy({ host: 'localhost', port: upstream.info.port }) + } + }); + + const res = await server.inject({ method: 'OPTIONS', url: '/' }); + expect(res.statusCode).to.equal(200); + expect(res.result).to.equal('test'); + + await upstream.stop(); + }); + it('throws when used with explicit route payload config other than data or steam', async () => { - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + expect(() => { server.route({ @@ -134,7 +149,9 @@ describe('H2o2', () => { it('throws when setup with invalid options', async () => { - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + expect(() => { server.route({ @@ -151,7 +168,9 @@ describe('H2o2', () => { it('throws when used with explicit route payload parse config set to false', async () => { - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + expect(() => { server.route({ @@ -171,7 +190,9 @@ describe('H2o2', () => { it('allows when used with explicit route payload output data config', async () => { - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + expect(() => { server.route({ @@ -200,9 +221,12 @@ describe('H2o2', () => { return 'ok'; } }); + await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', port: upstream.info.port, protocol: 'http' } } }); const res = await server.inject('/'); @@ -227,7 +251,9 @@ describe('H2o2', () => { upstream.route({ method: 'GET', path: '/headers', handler: headers }); await upstream.start(); - const server = await provisionServer({ routes: { cors: true } }); + const server = Hapi.server({ routes: { cors: true } }); + await server.register(H2o2); + server.route({ method: 'GET', path: '/headers', handler: { proxy: { host: 'localhost', port: upstream.info.port, passThrough: true } } }); const res = await server.inject({ @@ -246,29 +272,6 @@ describe('H2o2', () => { await upstream.stop(); }); - // it('overrides upstream cors headers', (done) => { - // - // const headers = function (request, reply) { - // - // reply().header('access-control-allow-headers', 'Invalid, List, Of, Values'); - // }; - // - // const upstream = new Hapi.Server(); - // upstream.connection(); - // upstream.route({ method: 'GET', path: '/', handler: headers }); - // upstream.start(function () { - // - // const server = provisionServer({ routes: { cors: { credentials: true } } }); - // server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', port: upstream.info.port, passThrough: true } } }); - // - // server.inject('/', (res) => { - // - // expect(res.headers['access-control-allow-headers']).to.equal('Invalid, List, Of, Values'); - // done(); - // }); - // }); - // }); - it('merges upstream headers', async () => { const handler = function (request, h) { @@ -287,7 +290,9 @@ describe('H2o2', () => { upstream.route({ method: 'GET', path: '/headers', handler }); await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/headers', handler: { proxy: { host: 'localhost', port: upstream.info.port, passThrough: true, onResponse } } }); const res = await server.inject({ url: '/headers', headers: { 'accept-encoding': 'gzip' } }); @@ -308,7 +313,9 @@ describe('H2o2', () => { upstream.route({ method: 'GET', path: '/gzip', handler: gzipHandler }); await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/gzip', handler: { proxy: { host: 'localhost', port: upstream.info.port, passThrough: true } } }); const zipped = await Zlib.gzipSync(Buffer.from('123456789012345678901234567890123456789012345678901234567890')); @@ -328,11 +335,13 @@ describe('H2o2', () => { }; const upstream = Hapi.server({ compression: { minBytes: 1 } }); - await upstream.register(require('inert')); + await upstream.register(Inert); upstream.route({ method: 'GET', path: '/gzipstream', handler: gzipStreamHandler }); await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/gzipstream', handler: { proxy: { host: 'localhost', port: upstream.info.port, passThrough: true } } }); const res = await server.inject({ url: '/gzipstream', headers: { 'accept-encoding': 'gzip' } }); @@ -359,7 +368,9 @@ describe('H2o2', () => { upstream.route({ method: 'GET', path: '/noHeaders', handler: headers }); await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/noHeaders', handler: { proxy: { host: 'localhost', port: upstream.info.port } } }); const res = await server.inject('/noHeaders'); @@ -387,7 +398,9 @@ describe('H2o2', () => { upstream.route({ method: 'GET', path: '/item', handler }); await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/item', handler: { proxy: { host: 'localhost', port: upstream.info.port, protocol: 'http:' } }, config: { cache: { expiresIn: 500 } } }); const response = await server.inject('/item'); @@ -412,7 +425,10 @@ describe('H2o2', () => { const upstream = Hapi.server(); upstream.route({ method: 'POST', path: '/item', handler: item }); await upstream.start(); - const server = await provisionServer(); + + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'POST', path: '/item', handler: { proxy: { host: 'localhost', port: upstream.info.port } } }); const res = await server.inject({ url: '/item', method: 'POST' }); @@ -433,7 +449,9 @@ describe('H2o2', () => { upstream.route({ method: 'GET', path: '/unauthorized', handler: unauthorized }); await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/unauthorized', handler: { proxy: { host: 'localhost', port: upstream.info.port } }, config: { cache: { expiresIn: 500 } } }); const res = await server.inject('/unauthorized'); @@ -447,7 +465,9 @@ describe('H2o2', () => { const upstream = Hapi.server(); await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'POST', path: '/notfound', handler: { proxy: { host: 'localhost', port: upstream.info.port } } }); const res = await server.inject('/notfound'); @@ -467,7 +487,9 @@ describe('H2o2', () => { const upstream = Hapi.server(); await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/onResponseError', handler: { proxy: { host: 'localhost', port: upstream.info.port, onResponse: onResponseWithError } } }); const res = await server.inject('/onResponseError'); @@ -487,7 +509,9 @@ describe('H2o2', () => { const upstream = Hapi.server(); await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', port: upstream.info.port, onResponse: on } } }); const res = await server.inject('/'); @@ -533,7 +557,9 @@ describe('H2o2', () => { } }; - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/onRequestSocket', config: { handler, bind: { c: 6 } } }); const res = await server.inject('/onRequestSocket'); @@ -562,7 +588,9 @@ describe('H2o2', () => { } }; - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/onResponseError', config: { handler, bind: { c: 6 } } }); const res = await server.inject('/onResponseError'); @@ -598,7 +626,9 @@ describe('H2o2', () => { name: 'test' }; - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + await server.register(plugin); const res = await server.inject('/'); @@ -635,7 +665,9 @@ describe('H2o2', () => { name: 'test' }; - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + await server.register(plugin); const res = await server.inject('/'); @@ -672,7 +704,9 @@ describe('H2o2', () => { name: 'test' }; - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + await server.register(plugin); const res = await server.inject('/'); @@ -694,8 +728,9 @@ describe('H2o2', () => { const dummyPort = dummy.info.port; await dummy.stop(Hoek.ignore); + const server = Hapi.server(); + await server.register(H2o2); - const server = await provisionServer(); server.route({ method: 'GET', path: '/failureResponse', handler: { proxy: { host: 'localhost', port: dummyPort, onResponse: failureResponse } }, config: { cache: { expiresIn: 500 } } }); const res = await server.inject('/failureResponse'); @@ -715,10 +750,9 @@ describe('H2o2', () => { upstream.route({ method: 'GET', path: '/', handler }); await upstream.start(); - const server = await provisionServer({ - host, - tls: tlsOptions - }); + const server = Hapi.server({ host, tls: tlsOptions }); + await server.register(H2o2); + server.route({ method: 'GET', path: '/', @@ -740,8 +774,8 @@ describe('H2o2', () => { expect(response.res.statusCode).to.equal(200); const result = JSON.parse(response.payload); - const expectedClientAddress = '127.0.0.1'; - const expectedClientAddressAndPort = expectedClientAddress + ':' + server.info.port; + let expectedClientAddress = '127.0.0.1'; + let expectedClientAddressAndPort = expectedClientAddress + ':' + server.info.port; if (Net.isIPv6(server.listener.address().address)) { expectedClientAddress = '::ffff:127.0.0.1'; @@ -783,7 +817,9 @@ describe('H2o2', () => { }; }; - const server = await provisionServer({ host: '127.0.0.1' }); + const server = Hapi.server({ host: '127.0.0.1' }); + await server.register(H2o2); + server.route({ method: 'GET', path: '/', handler: { proxy: { mapUri, xforward: true } } }); await server.start(); @@ -792,8 +828,7 @@ describe('H2o2', () => { const result = JSON.parse(response.payload); - const expectedClientAddress = '127.0.0.1'; - + let expectedClientAddress = '127.0.0.1'; if (Net.isIPv6(server.listener.address().address)) { expectedClientAddress = '::ffff:127.0.0.1'; } @@ -833,7 +868,9 @@ describe('H2o2', () => { upstream.route({ method: 'GET', path: '/', handler }); await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/', handler: { proxy: { mapUri, xforward: true } } }); const res = await server.inject('/'); @@ -866,7 +903,9 @@ describe('H2o2', () => { upstream.route({ method: 'POST', path: '/echo', handler: echoPostBody }); await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'POST', path: '/echo', handler: { proxy: { mapUri } } }); const res = await server.inject({ url: '/echo', method: 'POST', payload: '{"echo":true}' }); @@ -883,7 +922,9 @@ describe('H2o2', () => { throw new Error('myerror'); }; - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/maperror', handler: { proxy: { mapUri: mapUriWithError } } }); const res = await server.inject('/maperror'); @@ -901,7 +942,9 @@ describe('H2o2', () => { upstream.route({ method: 'GET', path: '/redirect', handler: redirectHandler }); await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/redirect', handler: { proxy: { host: 'localhost', port: upstream.info.port, passThrough: true, redirects: 2 } } }); const res = await server.inject('/redirect?x=1'); @@ -920,7 +963,10 @@ describe('H2o2', () => { const upstream = Hapi.server(); upstream.route({ method: 'GET', path: '/redirect', handler: redirectHandler }); await upstream.start(); - const server = await provisionServer(); + + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/redirect', handler: { proxy: { host: 'localhost', port: upstream.info.port, passThrough: true, redirects: 2 } } }); const res = await server.inject('/redirect?x=3'); @@ -931,7 +977,9 @@ describe('H2o2', () => { it('errors on redirection to bad host', async () => { - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/nowhere', handler: { proxy: { host: 'no.such.domain.x8' } } }); const res = await server.inject('/nowhere'); @@ -940,7 +988,9 @@ describe('H2o2', () => { it('errors on redirection to bad host (https)', async () => { - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/nowhere', handler: { proxy: { host: 'no.such.domain.x8', protocol: 'https' } } }); const res = await server.inject('/nowhere'); @@ -964,7 +1014,9 @@ describe('H2o2', () => { upstream.route({ method: 'GET', path: '/profile', handler: profile, config: { cache: { expiresIn: 2000 } } }); await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/redirect', handler: { proxy: { host: 'localhost', port: upstream.info.port, passThrough: true, redirects: 2 } } }); server.state('auto', { autoValue: 'xyz' }); @@ -994,7 +1046,9 @@ describe('H2o2', () => { upstream.route({ method: 'GET', path: '/profile', handler: profile, config: { cache: { expiresIn: 2000 } } }); await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/redirect', handler: { proxy: { host: 'localhost', port: upstream.info.port, passThrough: true, redirects: 2 } } }); server.state('auto', { autoValue: 'xyz' }); @@ -1026,9 +1080,12 @@ describe('H2o2', () => { return h.response(request.payload); } }); + await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'POST', path: '/post1', handler: { proxy: { host: 'localhost', port: upstream.info.port, redirects: 3 } }, config: { payload: { output: 'stream' } } }); const res = await server.inject({ method: 'POST', url: '/post1', payload: 'test', headers: { 'content-type': 'text/plain' } }); @@ -1056,9 +1113,12 @@ describe('H2o2', () => { } }); + await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/timeout1', handler: { proxy: { host: 'localhost', port: upstream.info.port, timeout: 5 } } }); const res = await server.inject('/timeout1'); @@ -1085,9 +1145,12 @@ describe('H2o2', () => { }); } }); + await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/timeout2', handler: { proxy: { host: 'localhost', port: upstream.info.port } } }); const res = await server.inject('/timeout2'); @@ -1118,7 +1181,9 @@ describe('H2o2', () => { }; }; - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/allow', handler: { proxy: { mapUri: mapSslUri, rejectUnauthorized: false } } }); await server.start(); @@ -1141,6 +1206,7 @@ describe('H2o2', () => { return h.response('Ok'); } }); + await upstream.start(); const mapSslUri = function (request, h) { @@ -1150,7 +1216,9 @@ describe('H2o2', () => { }; }; - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/reject', handler: { proxy: { mapUri: mapSslUri, rejectUnauthorized: true } } }); await server.start(); @@ -1172,6 +1240,7 @@ describe('H2o2', () => { return h.response('Ok'); } }); + await upstream.start(); const mapSslUri = function (request) { @@ -1179,7 +1248,9 @@ describe('H2o2', () => { return { uri: `https://127.0.0.1:${upstream.info.port}` }; }; - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/sslDefault', handler: { proxy: { mapUri: mapSslUri } } }); await server.start(); @@ -1208,9 +1279,12 @@ describe('H2o2', () => { } }); + await upstream.start(); - const server = await provisionServer({ routes: { timeout: { server: 8 } } }); + const server = Hapi.server({ routes: { timeout: { server: 8 } } }); + await server.register(H2o2); + server.route({ method: 'GET', path: '/timeout2', handler: { proxy: { host: 'localhost', port: upstream.info.port, timeout: 2 } } }); await server.start(); @@ -1238,9 +1312,12 @@ describe('H2o2', () => { }); } }); + await upstream.start(); - const server = await provisionServer({ routes: { timeout: { server: 5 } } }); + const server = Hapi.server({ routes: { timeout: { server: 5 } } }); + await server.register(H2o2); + server.route({ method: 'GET', path: '/timeout1', handler: { proxy: { host: 'localhost', port: upstream.info.port, timeout: 15 } } }); const res = await server.inject('/timeout1'); @@ -1260,9 +1337,12 @@ describe('H2o2', () => { return h.response({ a: 1 }); } }); + await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/handlerTemplate', handler: { proxy: { uri: '{protocol}://localhost:' + upstream.info.port + '/item' } } }); await server.start(); @@ -1285,9 +1365,12 @@ describe('H2o2', () => { return h.response({ a: request.params.param_a, b: request.params.param_b }); } }); + await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/handlerTemplate/{a}/{b}', handler: { proxy: { uri: 'http://localhost:' + upstream.info.port + '/item/{a}/{b}' } } }); const prma = 'foo'; @@ -1316,9 +1399,12 @@ describe('H2o2', () => { } } }); + await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/cachedItem', handler: { proxy: { host: 'localhost', port: upstream.info.port, ttl: 'upstream' } } }); server.state('auto', { autoValue: 'xyz' }); await server.start(); @@ -1339,7 +1425,9 @@ describe('H2o2', () => { }); await upstream.listen(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', port: upstream.address().port, ttl: 'upstream' } } }); const res = await server.inject('/'); @@ -1356,9 +1444,12 @@ describe('H2o2', () => { res.writeHeader(200, { 'cache-control': 'some crap that does not work' }); res.end('not much'); }); + await upstream.listen(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', port: upstream.address().port, ttl: 'upstream' } } }); const res = await server.inject('/'); @@ -1379,6 +1470,7 @@ describe('H2o2', () => { return h.response({ a: 1 }); } }); + await upstream.start(); const onResponse304 = function (err, res, request, h, settings, ttl) { @@ -1387,7 +1479,9 @@ describe('H2o2', () => { return h.response(res).code(304); }; - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/304', handler: { proxy: { uri: 'http://localhost:' + upstream.info.port + '/item', onResponse: onResponse304 } } }); const res = await server.inject('/304'); @@ -1408,9 +1502,12 @@ describe('H2o2', () => { return h.response({ a: 1 }); } }); + await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.ext('onPreResponse', (request, h) => { return h.response({ something: 'else' }); @@ -1435,7 +1532,9 @@ describe('H2o2', () => { upstream.route({ method: 'GET', path: '/', handler: profile, config: { cache: { expiresIn: 2000 } } }); await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', port: upstream.info.port, acceptEncoding: true, passThrough: true } } }); const res = await server.inject({ url: '/', headers: { 'accept-encoding': '*/*' } }); @@ -1456,7 +1555,9 @@ describe('H2o2', () => { upstream.route({ method: 'GET', path: '/', handler: profile, config: { cache: { expiresIn: 2000 } } }); await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', port: upstream.info.port, acceptEncoding: false, passThrough: true } } }); const res = await server.inject({ url: '/', headers: { 'accept-encoding': '*/*' } }); @@ -1468,7 +1569,8 @@ describe('H2o2', () => { it('does not send multiple Content-Type headers on passthrough', { parallel: false }, async () => { - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); const httpClient = { request(method, uri, options, callback) { @@ -1484,7 +1586,9 @@ describe('H2o2', () => { it('allows passing in an agent through to Wreck', { parallel: false }, async () => { - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + const agent = { name: 'myagent' }; const httpClient = { @@ -1509,7 +1613,9 @@ describe('H2o2', () => { upstream.route({ method: 'GET', path: '/', handler }); await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.state('a'); server.route({ @@ -1544,7 +1650,9 @@ describe('H2o2', () => { upstream.route({ method: 'GET', path: '/', handler }); await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.state('a', { passThrough: true }); server.route({ method: 'GET', @@ -1578,7 +1686,9 @@ describe('H2o2', () => { upstream.route({ method: 'GET', path: '/', handler }); await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.state('a', { passThrough: true }); server.route({ method: 'GET', @@ -1603,7 +1713,9 @@ describe('H2o2', () => { it('errors on invalid cookie header', async () => { - const server = await provisionServer({ routes: { state: { failAction: 'ignore' } } }); + const server = Hapi.server({ routes: { state: { failAction: 'ignore' } } }); + await server.register(H2o2); + server.state('a', { passThrough: true }); server.route({ @@ -1633,7 +1745,9 @@ describe('H2o2', () => { upstream.route({ method: 'GET', path: '/', handler }); await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.state('a'); server.route({ method: 'GET', @@ -1667,7 +1781,9 @@ describe('H2o2', () => { upstream.route({ method: 'GET', path: '/', handler }); await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.state('a', { passThrough: false }); server.route({ method: 'GET', @@ -1701,9 +1817,12 @@ describe('H2o2', () => { return h.response('ok'); } }); + await upstream.start(); - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/', @@ -1731,19 +1850,11 @@ describe('H2o2', () => { return h.response('ok'); } }); - await upstream.start(); - const plugin = { - register: H2o2.register, - pkg: H2o2.pkg - }; - const options = { secureProtocol: 'TLSv1_2_method', ciphers: 'ECDHE-RSA-AES128-SHA256' }; + await upstream.start(); const server = Hapi.server(); - await server.register({ - plugin, - options - }); + await server.register({ plugin: H2o2, options: { secureProtocol: 'TLSv1_2_method', ciphers: 'ECDHE-RSA-AES128-SHA256' } }); server.route({ method: 'GET', path: '/', @@ -1771,19 +1882,11 @@ describe('H2o2', () => { return h.response('ok'); } }); - await upstream.start(); - const plugin = { - register: H2o2.register, - pkg: H2o2.pkg - }; - const options = { downstreamResponseTime: true }; + await upstream.start(); const server = Hapi.server(); - await server.register({ - plugin, - options - }); + await server.register({ plugin: H2o2, options: { downstreamResponseTime: true } }); server.route({ method: 'GET', path: '/', @@ -1818,17 +1921,10 @@ describe('H2o2', () => { const dummyPort = dummy.info.port; await dummy.stop(Hoek.ignore); - const plugin = { - register: H2o2.register, - pkg: H2o2.pkg - }; const options = { downstreamResponseTime: true }; const server = Hapi.server(); - await server.register({ - plugin, - options - }); + await server.register({ plugin: H2o2, options }); server.route({ method: 'GET', path: '/failureResponse', handler: { proxy: { host: 'localhost', port: dummyPort, onResponse: failureResponse } }, config: { cache: { expiresIn: 500 } } }); let firstEvent = true; @@ -1856,7 +1952,9 @@ describe('H2o2', () => { parseCacheControl: Wreck.parseCacheControl.bind(Wreck) }; - const server = await provisionServer(); + const server = Hapi.server(); + await server.register(H2o2); + server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', port: upstream.info.port, httpClient } } }); const res = await server.inject('/'); From cb4e6045ac299ebd794fed89cac4f48b5ba889c1 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Sat, 13 Apr 2019 15:24:04 -0700 Subject: [PATCH 46/73] 8.3.0 --- package.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 1f4d7d6..5f166a9 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,16 @@ { "name": "@hapi/h2o2", "description": "Proxy handler plugin for hapi.js", - "version": "8.2.0", + "version": "8.3.0", "repository": "git://github.com/hapijs/h2o2", "main": "lib/index.js", - "keywords": ["HTTP", "proxy", "handler", "hapi", "plugin"], + "keywords": [ + "HTTP", + "proxy", + "handler", + "hapi", + "plugin" + ], "dependencies": { "@hapi/boom": "7.x.x", "@hapi/hoek": "6.x.x", From e4af596b358d4858667e22fbe7c2db0cf2aad6fa Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Wed, 24 Apr 2019 20:39:45 -0700 Subject: [PATCH 47/73] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6aec8b2..540c3ad 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - + # h2o2 From f8438ab48d4be0dd2961360c8e4ddd55787cc1a3 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Wed, 24 Apr 2019 23:11:58 -0700 Subject: [PATCH 48/73] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index fe9f8dd..36e4f9a 100755 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ language: node_js node_js: - "8" - "10" - - "11" + - "12" - "node" sudo: false From 5282a7bb407fdb7f5db1eff611ecfa2de22b2be5 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Wed, 14 Aug 2019 22:25:28 +0000 Subject: [PATCH 49/73] Update deps. Closes #101 --- .travis.yml | 1 - lib/index.js | 2 +- package.json | 6 +++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 36e4f9a..dcc2455 100755 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,6 @@ install: - "npm install hapi@$HAPI_VERSION" env: - - HAPI_VERSION="17" - HAPI_VERSION="18" os: diff --git a/lib/index.js b/lib/index.js index 7c9474d..ab3cfbc 100755 --- a/lib/index.js +++ b/lib/index.js @@ -81,7 +81,7 @@ exports.plugin = { internals.handler = function (route, handlerOptions) { - const settings = Hoek.applyToDefaultsWithShallow(internals.defaults, handlerOptions, ['agent']); + const settings = Hoek.applyToDefaults(internals.defaults, handlerOptions, { shallow: ['agent'] }); Joi.assert(handlerOptions, internals.schema, 'Invalid proxy handler options (' + route.path + ')'); Hoek.assert(!route.settings.payload || ((route.settings.payload.output === 'data' || route.settings.payload.output === 'stream') && !route.settings.payload.parse), 'Cannot proxy if payload is parsed or if output is not stream or data'); settings.mapUri = handlerOptions.mapUri || internals.mapUri(handlerOptions.protocol, handlerOptions.host, handlerOptions.port, handlerOptions.uri); diff --git a/package.json b/package.json index 5f166a9..956a45e 100644 --- a/package.json +++ b/package.json @@ -13,15 +13,15 @@ ], "dependencies": { "@hapi/boom": "7.x.x", - "@hapi/hoek": "6.x.x", + "@hapi/hoek": "8.x.x", "@hapi/joi": "15.x.x", "@hapi/wreck": "15.x.x" }, "devDependencies": { - "@hapi/code": "5.x.x", + "@hapi/code": "6.x.x", "@hapi/hapi": "18.x.x", "@hapi/inert": "5.x.x", - "@hapi/lab": "18.x.x" + "@hapi/lab": "20.x.x" }, "scripts": { "test": "lab -a @hapi/code -t 100 -L", From 3a23710f9d503869c83f21a042bdc53f78500563 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Wed, 14 Aug 2019 22:25:34 +0000 Subject: [PATCH 50/73] 8.3.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 956a45e..99a3da3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@hapi/h2o2", "description": "Proxy handler plugin for hapi.js", - "version": "8.3.0", + "version": "8.3.1", "repository": "git://github.com/hapijs/h2o2", "main": "lib/index.js", "keywords": [ From a16cd60fb574044de1000f5539652d4b0d22a7b1 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Thu, 15 Aug 2019 22:36:29 +0000 Subject: [PATCH 51/73] Fix scope --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index dcc2455..6f59abb 100755 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ sudo: false install: - "npm install" - - "npm install hapi@$HAPI_VERSION" + - "npm install @hapi/hapi@$HAPI_VERSION" env: - HAPI_VERSION="18" From b092e13648f6611ac81fc6f162f44bbd99ce70e5 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Thu, 12 Sep 2019 23:05:49 -0700 Subject: [PATCH 52/73] Update joi. Closes #102 --- package.json | 2 +- test/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 99a3da3..08eb428 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "dependencies": { "@hapi/boom": "7.x.x", "@hapi/hoek": "8.x.x", - "@hapi/joi": "15.x.x", + "@hapi/joi": "16.x.x", "@hapi/wreck": "15.x.x" }, "devDependencies": { diff --git a/test/index.js b/test/index.js index e2ed038..285283a 100755 --- a/test/index.js +++ b/test/index.js @@ -159,7 +159,7 @@ describe('h2o2', () => { path: '/', config: { handler: { - proxy: { some: 'key' } + proxy: {} } } }); From 274fb87b14671dc802e18b7515286972f3196975 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Thu, 12 Sep 2019 23:05:52 -0700 Subject: [PATCH 53/73] 8.3.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 08eb428..b9603c6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@hapi/h2o2", "description": "Proxy handler plugin for hapi.js", - "version": "8.3.1", + "version": "8.3.2", "repository": "git://github.com/hapijs/h2o2", "main": "lib/index.js", "keywords": [ From ac87629968d90bb102eee05fb821e833de742688 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Tue, 17 Sep 2019 12:16:29 -0700 Subject: [PATCH 54/73] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 540c3ad..754469f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# h2o2 +# @hapi/h2o2 Proxy handler plugin for hapi.js. From 34cb9479e15b2d78785ab8f124fa9f25930ff253 Mon Sep 17 00:00:00 2001 From: jarrodyellets Date: Thu, 26 Sep 2019 13:55:13 +0200 Subject: [PATCH 55/73] Move Readme to API --- API.md | 220 +++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 222 +----------------------------------------------------- 2 files changed, 221 insertions(+), 221 deletions(-) create mode 100644 API.md diff --git a/API.md b/API.md new file mode 100644 index 0000000..69035a0 --- /dev/null +++ b/API.md @@ -0,0 +1,220 @@ + +## Introduction + +**h2o2** adds proxying functionality to a hapi server. + +## Manual loading + +```javascript +const Hapi = require('@hapi/hapi'); +const H2o2 = require('@hapi/h2o2'); + + +const start = async function() { + + const server = Hapi.server(); + try { + await server.register(H2o2); + await server.start(); + + console.log(`Server started at: ${server.info.uri}`); + } + catch(e) { + console.log('Failed to load h2o2'); + } +} + +start(); +``` + +## Options + +The plugin can be registered with an optional object specifying defaults to be applied to the proxy handler object. + +The proxy handler object has the following properties: + +* `host` - upstream service host to proxy requests to. It will have the same path as the client request. +* `port` - upstream service port. +* `protocol` - protocol to use when making the request to the proxied host: + * 'http' + * 'https' +* `uri` - absolute URI used instead of host, port, protocol, path, and query. Cannot be used with `host`, `port`, `protocol`, or `mapUri`. +* `httpClient` - an http client that abides by the Wreck interface. Defaults to [`wreck`](https://github.com/hapijs/wreck). +* `passThrough` - if set to `true`, it forwards the headers from the client to the upstream service, headers sent from the upstream service will also be forwarded to the client. Defaults to `false`. +* `localStatePassThrough` - if set to`false`, any locally defined state is removed from incoming requests before being sent to the upstream service. This value can be overridden on a per state basis via the `server.state()` `passThrough` option. Defaults to `false` +* `acceptEncoding` - if set to `false`, does not pass-through the 'Accept-Encoding' HTTP header which is useful for the `onResponse` post-processing to avoid receiving an encoded response. Can only be used together with `passThrough`. Defaults to `true` (passing header). +* `rejectUnauthorized` - sets the `rejectUnauthorized` property on the https [agent](http://nodejs.org/api/https.html#https_https_request_options_callback) making the request. This value is only used when the proxied server uses TLS/SSL. If set it will override the node.js `rejectUnauthorized` property. If `false` then ssl errors will be ignored. When `true` the server certificate is verified and an 500 response will be sent when verification fails. This shouldn't be used alongside the `agent` setting as the `agent` will be used instead. Defaults to the https agent default value of `true`. +* `xforward` - if set to `true`, sets the 'X-Forwarded-For', 'X-Forwarded-Port', 'X-Forwarded-Proto', 'X-Forwarded-Host' headers when making a request to the proxied upstream endpoint. Defaults to `false`. +* `redirects` - the maximum number of HTTP redirections allowed to be followed automatically by the handler. Set to `false` or `0` to disable all redirections (the response will contain the redirection received from the upstream service). If redirections are enabled, no redirections (301, 302, 307, 308) will be passed along to the client, and reaching the maximum allowed redirections will return an error response. Defaults to `false`. +* `timeout` - number of milliseconds before aborting the upstream request. Defaults to `180000` (3 minutes). +* `mapUri` - a function used to map the request URI to the proxied URI. Cannot be used together with `host`, `port`, `protocol`, or `uri`. The function signature is `function (request)` where: + * `request` - is the incoming [request object](http://hapijs.com/api#request-object). The response from this function should be an object with the following properties: + * `uri` - the absolute proxy URI. + * `headers` - optional object where each key is an HTTP request header and the value is the header content. +* `onRequest` - a custom function which is passed the upstream request. Function signature is `function (req)` where: + * `req` - the [wreck] (https://github.com/hapijs/wreck) request to the upstream server. +* `onResponse` - a custom function for processing the response from the upstream service before sending to the client. Useful for custom error handling of responses from the proxied endpoint or other payload manipulation. Function signature is `function (err, res, request, h, settings, ttl)` where: + * `err` - internal or upstream error returned from attempting to contact the upstream proxy. + * `res` - the node response object received from the upstream service. `res` is a readable stream (use the [wreck](https://github.com/hapijs/wreck) module `read` method to easily convert it to a Buffer or string). + * `request` - is the incoming [request object](http://hapijs.com/api#request-object). + * `h` - the [response toolkit](https://hapijs.com/api#response-toolkit). + * `settings` - the proxy handler configuration. + * `ttl` - the upstream TTL in milliseconds if `proxy.ttl` it set to `'upstream'` and the upstream response included a valid 'Cache-Control' header with 'max-age'. +* `ttl` - if set to `'upstream'`, applies the upstream response caching policy to the response using the `response.ttl()` method (or passed as an argument to the `onResponse` method if provided). +* `agent` - a node [http(s) agent](http://nodejs.org/api/http.html#http_class_http_agent) to be used for connections to upstream server. +* `maxSockets` - sets the maximum number of sockets available per outgoing proxy host connection. `false` means use the **wreck** module default value (`Infinity`). Does not affect non-proxy outgoing client connections. Defaults to `Infinity`. +* `secureProtocol` - [TLS](http://nodejs.org/api/tls.html) flag indicating the SSL method to use, e.g. `SSLv3_method` +to force SSL version 3. The possible values depend on your installation of OpenSSL. Read the official OpenSSL docs for possible [SSL_METHODS](https://www.openssl.org/docs/man1.0.2/ssl/ssl.html). +* `ciphers` - [TLS](https://nodejs.org/api/tls.html#tls_modifying_the_default_tls_cipher_suite) list of TLS ciphers to override node's default. +The possible values depend on your installation of OpenSSL. Read the official OpenSSL docs for possible [TLS_CIPHERS](https://www.openssl.org/docs/man1.0.2/apps/ciphers.html#CIPHER-LIST-FORMAT). +* `downstreamResponseTime` - logs the time spent processing the downstream request using [process.hrtime](https://nodejs.org/api/process.html#process_process_hrtime_time). Defaults to `false`. + +## Usage + +As one of the handlers for hapi, it is used through the route configuration object. + +### `h.proxy(options)` + +Proxies the request to an upstream endpoint where: +- `options` - an object including the same keys and restrictions defined by the + [route `proxy` handler options](#options). + +No return value. + +The [response flow control rules](http://hapijs.com/api#flow-control) **do not** apply. + +```js +const handler = function (request, h) { + + return h.proxy({ host: 'example.com', port: 80, protocol: 'http' }); +}; +``` + +### Using the `host`, `port`, `protocol` options + +Setting these options will send the request to certain route to a specific upstream service with the same path as the original request. Cannot be used with `uri`, `mapUri`. + +```javascript +server.route({ + method: 'GET', + path: '/', + handler: { + proxy: { + host: '10.33.33.1', + port: '443', + protocol: 'https' + } + } +}); +``` + +### Using the `uri` option + +Setting this option will send the request to an absolute URI instead of the incoming host, port, protocol, path and query. Cannot be used with `host`, `port`, `protocol`, `mapUri`. + +```javascript +server.route({ + method: 'GET', + path: '/', + handler: { + proxy: { + uri: 'https://some.upstream.service.com/that/has?what=you&want=todo' + } + } +}); +``` +### Custom `uri` template values + +When using the `uri` option, there are optional **default** template values that can be injected from the incoming `request`: + +* `{protocol}` +* `{host}` +* `{port}` +* `{path}` + +```javascript +server.route({ + method: 'GET', + path: '/foo', + handler: { + proxy: { + uri: '{protocol}://{host}:{port}/go/to/{path}' + } + } +}); +``` +Requests to `http://127.0.0.1:8080/foo/` would be proxied to an upstream destination of `http://127.0.0.1:8080/go/to/foo` + + +Additionally, you can capture request.params values and inject them into the upstream uri value using a similar replacment strategy: +```javascript +server.route({ + method: 'GET', + path: '/foo/{bar}', + handler: { + proxy: { + uri: 'https://some.upstream.service.com/some/path/to/{bar}' + } + } +}); +``` +**Note** The default variables of `{protocol}`, `{host}`, `{port}`, `{path}` take precedence - it's best to treat those as reserved when naming your own `request.params`. + + +### Using the `mapUri` and `onResponse` options + +Setting both options with custom functions will allow you to map the original request to an upstream service and to processing the response from the upstream service, before sending it to the client. Cannot be used together with `host`, `port`, `protocol`, or `uri`. + +```javascript +server.route({ + method: 'GET', + path: '/', + handler: { + proxy: { + mapUri: function (request) { + + console.log('doing some additional stuff before redirecting'); + return { + uri: 'https://some.upstream.service.com/' + }; + }, + onResponse: function (err, res, request, h, settings, ttl) { + + console.log('receiving the response from the upstream.'); + Wreck.read(res, { json: true }, function (err, payload) { + + console.log('some payload manipulation if you want to.') + const response = h.response(payload); + response.headers = res.headers; + return response; + }); + } + } + } +}); + +``` + + +### Using a custom http client + +By default, `h2o2` uses Wreck to perform requests. A custom http client can be provided by passing a client to `httpClient`, as long as it abides by the [`wreck`](https://github.com/hapijs/wreck) interface. The two functions that `h2o2` utilizes are `request()` and `parseCacheControl()`. + +```javascript +server.route({ + method: 'GET', + path: '/', + handler: { + proxy: { + httpClient: { + request(method, uri, options) { + return axios({ + method, + url: 'https://some.upstream.service.com/' + }) + } + } + } + } +}); +``` diff --git a/README.md b/README.md index 754469f..1b36517 100644 --- a/README.md +++ b/README.md @@ -4,224 +4,4 @@ Proxy handler plugin for hapi.js. -[![Build Status](https://secure.travis-ci.org/hapijs/h2o2.png)](http://travis-ci.org/hapijs/h2o2) - -## Introduction - -**h2o2** adds proxying functionality to a hapi server. - -## Manual loading - -```javascript -const Hapi = require('@hapi/hapi'); -const H2o2 = require('@hapi/h2o2'); - - -const start = async function() { - - const server = Hapi.server(); - try { - await server.register(H2o2); - await server.start(); - - console.log(`Server started at: ${server.info.uri}`); - } - catch(e) { - console.log('Failed to load h2o2'); - } -} - -start(); -``` - -## Options - -The plugin can be registered with an optional object specifying defaults to be applied to the proxy handler object. - -The proxy handler object has the following properties: - -* `host` - upstream service host to proxy requests to. It will have the same path as the client request. -* `port` - upstream service port. -* `protocol` - protocol to use when making the request to the proxied host: - * 'http' - * 'https' -* `uri` - absolute URI used instead of host, port, protocol, path, and query. Cannot be used with `host`, `port`, `protocol`, or `mapUri`. -* `httpClient` - an http client that abides by the Wreck interface. Defaults to [`wreck`](https://github.com/hapijs/wreck). -* `passThrough` - if set to `true`, it forwards the headers from the client to the upstream service, headers sent from the upstream service will also be forwarded to the client. Defaults to `false`. -* `localStatePassThrough` - if set to`false`, any locally defined state is removed from incoming requests before being sent to the upstream service. This value can be overridden on a per state basis via the `server.state()` `passThrough` option. Defaults to `false` -* `acceptEncoding` - if set to `false`, does not pass-through the 'Accept-Encoding' HTTP header which is useful for the `onResponse` post-processing to avoid receiving an encoded response. Can only be used together with `passThrough`. Defaults to `true` (passing header). -* `rejectUnauthorized` - sets the `rejectUnauthorized` property on the https [agent](http://nodejs.org/api/https.html#https_https_request_options_callback) making the request. This value is only used when the proxied server uses TLS/SSL. If set it will override the node.js `rejectUnauthorized` property. If `false` then ssl errors will be ignored. When `true` the server certificate is verified and an 500 response will be sent when verification fails. This shouldn't be used alongside the `agent` setting as the `agent` will be used instead. Defaults to the https agent default value of `true`. -* `xforward` - if set to `true`, sets the 'X-Forwarded-For', 'X-Forwarded-Port', 'X-Forwarded-Proto', 'X-Forwarded-Host' headers when making a request to the proxied upstream endpoint. Defaults to `false`. -* `redirects` - the maximum number of HTTP redirections allowed to be followed automatically by the handler. Set to `false` or `0` to disable all redirections (the response will contain the redirection received from the upstream service). If redirections are enabled, no redirections (301, 302, 307, 308) will be passed along to the client, and reaching the maximum allowed redirections will return an error response. Defaults to `false`. -* `timeout` - number of milliseconds before aborting the upstream request. Defaults to `180000` (3 minutes). -* `mapUri` - a function used to map the request URI to the proxied URI. Cannot be used together with `host`, `port`, `protocol`, or `uri`. The function signature is `function (request)` where: - * `request` - is the incoming [request object](http://hapijs.com/api#request-object). The response from this function should be an object with the following properties: - * `uri` - the absolute proxy URI. - * `headers` - optional object where each key is an HTTP request header and the value is the header content. -* `onRequest` - a custom function which is passed the upstream request. Function signature is `function (req)` where: - * `req` - the [wreck] (https://github.com/hapijs/wreck) request to the upstream server. -* `onResponse` - a custom function for processing the response from the upstream service before sending to the client. Useful for custom error handling of responses from the proxied endpoint or other payload manipulation. Function signature is `function (err, res, request, h, settings, ttl)` where: - * `err` - internal or upstream error returned from attempting to contact the upstream proxy. - * `res` - the node response object received from the upstream service. `res` is a readable stream (use the [wreck](https://github.com/hapijs/wreck) module `read` method to easily convert it to a Buffer or string). - * `request` - is the incoming [request object](http://hapijs.com/api#request-object). - * `h` - the [response toolkit](https://hapijs.com/api#response-toolkit). - * `settings` - the proxy handler configuration. - * `ttl` - the upstream TTL in milliseconds if `proxy.ttl` it set to `'upstream'` and the upstream response included a valid 'Cache-Control' header with 'max-age'. -* `ttl` - if set to `'upstream'`, applies the upstream response caching policy to the response using the `response.ttl()` method (or passed as an argument to the `onResponse` method if provided). -* `agent` - a node [http(s) agent](http://nodejs.org/api/http.html#http_class_http_agent) to be used for connections to upstream server. -* `maxSockets` - sets the maximum number of sockets available per outgoing proxy host connection. `false` means use the **wreck** module default value (`Infinity`). Does not affect non-proxy outgoing client connections. Defaults to `Infinity`. -* `secureProtocol` - [TLS](http://nodejs.org/api/tls.html) flag indicating the SSL method to use, e.g. `SSLv3_method` -to force SSL version 3. The possible values depend on your installation of OpenSSL. Read the official OpenSSL docs for possible [SSL_METHODS](https://www.openssl.org/docs/man1.0.2/ssl/ssl.html). -* `ciphers` - [TLS](https://nodejs.org/api/tls.html#tls_modifying_the_default_tls_cipher_suite) list of TLS ciphers to override node's default. -The possible values depend on your installation of OpenSSL. Read the official OpenSSL docs for possible [TLS_CIPHERS](https://www.openssl.org/docs/man1.0.2/apps/ciphers.html#CIPHER-LIST-FORMAT). -* `downstreamResponseTime` - logs the time spent processing the downstream request using [process.hrtime](https://nodejs.org/api/process.html#process_process_hrtime_time). Defaults to `false`. - -## Usage - -As one of the handlers for hapi, it is used through the route configuration object. - -### `h.proxy(options)` - -Proxies the request to an upstream endpoint where: -- `options` - an object including the same keys and restrictions defined by the - [route `proxy` handler options](#options). - -No return value. - -The [response flow control rules](http://hapijs.com/api#flow-control) **do not** apply. - -```js -const handler = function (request, h) { - - return h.proxy({ host: 'example.com', port: 80, protocol: 'http' }); -}; -``` - -### Using the `host`, `port`, `protocol` options - -Setting these options will send the request to certain route to a specific upstream service with the same path as the original request. Cannot be used with `uri`, `mapUri`. - -```javascript -server.route({ - method: 'GET', - path: '/', - handler: { - proxy: { - host: '10.33.33.1', - port: '443', - protocol: 'https' - } - } -}); -``` - -### Using the `uri` option - -Setting this option will send the request to an absolute URI instead of the incoming host, port, protocol, path and query. Cannot be used with `host`, `port`, `protocol`, `mapUri`. - -```javascript -server.route({ - method: 'GET', - path: '/', - handler: { - proxy: { - uri: 'https://some.upstream.service.com/that/has?what=you&want=todo' - } - } -}); -``` -### Custom `uri` template values - -When using the `uri` option, there are optional **default** template values that can be injected from the incoming `request`: - -* `{protocol}` -* `{host}` -* `{port}` -* `{path}` - -```javascript -server.route({ - method: 'GET', - path: '/foo', - handler: { - proxy: { - uri: '{protocol}://{host}:{port}/go/to/{path}' - } - } -}); -``` -Requests to `http://127.0.0.1:8080/foo/` would be proxied to an upstream destination of `http://127.0.0.1:8080/go/to/foo` - - -Additionally, you can capture request.params values and inject them into the upstream uri value using a similar replacment strategy: -```javascript -server.route({ - method: 'GET', - path: '/foo/{bar}', - handler: { - proxy: { - uri: 'https://some.upstream.service.com/some/path/to/{bar}' - } - } -}); -``` -**Note** The default variables of `{protocol}`, `{host}`, `{port}`, `{path}` take precedence - it's best to treat those as reserved when naming your own `request.params`. - - -### Using the `mapUri` and `onResponse` options - -Setting both options with custom functions will allow you to map the original request to an upstream service and to processing the response from the upstream service, before sending it to the client. Cannot be used together with `host`, `port`, `protocol`, or `uri`. - -```javascript -server.route({ - method: 'GET', - path: '/', - handler: { - proxy: { - mapUri: function (request) { - - console.log('doing some additional stuff before redirecting'); - return { - uri: 'https://some.upstream.service.com/' - }; - }, - onResponse: function (err, res, request, h, settings, ttl) { - - console.log('receiving the response from the upstream.'); - Wreck.read(res, { json: true }, function (err, payload) { - - console.log('some payload manipulation if you want to.') - const response = h.response(payload); - response.headers = res.headers; - return response; - }); - } - } - } -}); - -``` - - -### Using a custom http client - -By default, `h2o2` uses Wreck to perform requests. A custom http client can be provided by passing a client to `httpClient`, as long as it abides by the [`wreck`](https://github.com/hapijs/wreck) interface. The two functions that `h2o2` utilizes are `request()` and `parseCacheControl()`. - -```javascript -server.route({ - method: 'GET', - path: '/', - handler: { - proxy: { - httpClient: { - request(method, uri, options) { - return axios({ - method, - url: 'https://some.upstream.service.com/' - }) - } - } - } - } -}); -``` +[![Build Status](https://secure.travis-ci.org/hapijs/h2o2.png)](http://travis-ci.org/hapijs/h2o2) \ No newline at end of file From dd6b01545198b897221442f1d1b8d2d54c501d67 Mon Sep 17 00:00:00 2001 From: Jarrod Yellets Date: Mon, 7 Oct 2019 09:52:57 +0200 Subject: [PATCH 56/73] Update README to new template --- README.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1b36517..46f20b5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,16 @@ - + # @hapi/h2o2 -Proxy handler plugin for hapi.js. +#### Proxy handler for hapi.js. -[![Build Status](https://secure.travis-ci.org/hapijs/h2o2.png)](http://travis-ci.org/hapijs/h2o2) \ No newline at end of file +**h2o2** is part of the **hapi** ecosystem and was designed to work seamlessly with the [hapi web framework](https://hapi.dev) and its other components (but works great on its own or with other frameworks). If you are using a different web framework and find this module useful, check out [hapi](https://hapi.dev) – they work even better together. + +### Visit the [hapi.dev](https://hapi.dev) Developer Portal for tutorials, documentation, and support + +## Useful resources + +- [Documentation and API](https://hapi.dev/family/h2o2/) +- [Version status](https://hapi.dev/resources/status/#h2o2) (builds, dependencies, node versions, licenses, eol) +- [Project policies](https://hapi.dev/policies/) +- [Free and commercial support options](https://hapi.dev/support/) From 4c17ca007f9d11f07d583079ecab99e0957619a9 Mon Sep 17 00:00:00 2001 From: nwhitmont Date: Fri, 3 Jan 2020 16:28:51 -0800 Subject: [PATCH 57/73] delete changelog --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 661dade..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,3 +0,0 @@ -Breaking changes are documented using GitHub issues, see [issues labeled "release notes"](https://github.com/hapijs/h2o2/issues?q=is%3Aissue+label%3A%22release+notes%22). - -If you want changes of a specific minor or patch release, you can browse the [GitHub milestones](https://github.com/hapijs/h2o2/milestones?state=closed&direction=asc&sort=due_date). From 7e26c2fa2a02f0ef0a64f7e86d06c04b04783043 Mon Sep 17 00:00:00 2001 From: nwhitmont Date: Fri, 3 Jan 2020 16:29:24 -0800 Subject: [PATCH 58/73] add link to changelog --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 46f20b5..70f377d 100644 --- a/README.md +++ b/README.md @@ -12,5 +12,6 @@ - [Documentation and API](https://hapi.dev/family/h2o2/) - [Version status](https://hapi.dev/resources/status/#h2o2) (builds, dependencies, node versions, licenses, eol) +- [Changelog](https://hapi.dev/family/h2o2/changelog/) - [Project policies](https://hapi.dev/policies/) - [Free and commercial support options](https://hapi.dev/support/) From fd1eb2c2f0eae78b16b64f5b2e1eae0812579598 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Sat, 11 Jan 2020 22:17:11 -0800 Subject: [PATCH 59/73] Update deps. Closes #110. Closes #111. Closes #112 --- .travis.yml | 3 +-- LICENSE.md | 2 +- lib/index.js | 3 +-- package.json | 19 +++++++++++-------- test/index.js | 2 +- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6f59abb..c4d91d0 100755 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,6 @@ language: node_js node_js: - - "8" - - "10" - "12" - "node" @@ -14,6 +12,7 @@ install: env: - HAPI_VERSION="18" + - HAPI_VERSION="19" os: - "linux" diff --git a/LICENSE.md b/LICENSE.md index aa2404c..0d96bf8 100755 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright (c) 2012-2019, Sideway Inc, and project contributors +Copyright (c) 2012-2020, Sideway Inc, and project contributors Copyright (c) 2012-2014, Walmart. All rights reserved. diff --git a/lib/index.js b/lib/index.js index ab3cfbc..2989b72 100755 --- a/lib/index.js +++ b/lib/index.js @@ -62,10 +62,9 @@ internals.schema = Joi.object({ exports.plugin = { - name: 'h2o2', // Override package name pkg: require('../package.json'), requirements: { - hapi: '>=17.9.0' + hapi: '>=18.4.0' }, register: function (server, options) { diff --git a/package.json b/package.json index b9603c6..c5cc812 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,9 @@ "version": "8.3.2", "repository": "git://github.com/hapijs/h2o2", "main": "lib/index.js", + "files": [ + "lib" + ], "keywords": [ "HTTP", "proxy", @@ -12,16 +15,16 @@ "plugin" ], "dependencies": { - "@hapi/boom": "7.x.x", - "@hapi/hoek": "8.x.x", - "@hapi/joi": "16.x.x", - "@hapi/wreck": "15.x.x" + "@hapi/boom": "9.x.x", + "@hapi/hoek": "9.x.x", + "@hapi/joi": "17.x.x", + "@hapi/wreck": "17.x.x" }, "devDependencies": { - "@hapi/code": "6.x.x", - "@hapi/hapi": "18.x.x", - "@hapi/inert": "5.x.x", - "@hapi/lab": "20.x.x" + "@hapi/code": "8.x.x", + "@hapi/hapi": "19.x.x", + "@hapi/inert": "6.x.x", + "@hapi/lab": "22.x.x" }, "scripts": { "test": "lab -a @hapi/code -t 100 -L", diff --git a/test/index.js b/test/index.js index 285283a..caddb30 100755 --- a/test/index.js +++ b/test/index.js @@ -1561,7 +1561,7 @@ describe('h2o2', () => { server.route({ method: 'GET', path: '/', handler: { proxy: { host: 'localhost', port: upstream.info.port, acceptEncoding: false, passThrough: true } } }); const res = await server.inject({ url: '/', headers: { 'accept-encoding': '*/*' } }); - expect(res.statusCode).to.equal(200); + expect(res.statusCode).to.be.within(200, 204); expect(res.payload).to.equal(''); await upstream.stop(); From f64b492b8ca84a9db680f49617011a5ca64a8f71 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Sat, 11 Jan 2020 22:17:15 -0800 Subject: [PATCH 60/73] 9.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c5cc812..dd46bd1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@hapi/h2o2", "description": "Proxy handler plugin for hapi.js", - "version": "8.3.2", + "version": "9.0.0", "repository": "git://github.com/hapijs/h2o2", "main": "lib/index.js", "files": [ From 079a06b5e7fd333e5b51c963a62579eff99f12d8 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Sun, 12 Jan 2020 00:17:34 -0800 Subject: [PATCH 61/73] hapi 19. Closes #113 --- .travis.yml | 1 - lib/index.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c4d91d0..33b4f7d 100755 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,6 @@ install: - "npm install @hapi/hapi@$HAPI_VERSION" env: - - HAPI_VERSION="18" - HAPI_VERSION="19" os: diff --git a/lib/index.js b/lib/index.js index 2989b72..7e2506c 100755 --- a/lib/index.js +++ b/lib/index.js @@ -64,7 +64,7 @@ internals.schema = Joi.object({ exports.plugin = { pkg: require('../package.json'), requirements: { - hapi: '>=18.4.0' + hapi: '>=19.0.0' }, register: function (server, options) { From 430c7e6eaa6418c8cbf1f737d514a2ce91efd3c8 Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Sun, 12 Jan 2020 00:17:44 -0800 Subject: [PATCH 62/73] 9.0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dd46bd1..bd6ee4b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@hapi/h2o2", "description": "Proxy handler plugin for hapi.js", - "version": "9.0.0", + "version": "9.0.1", "repository": "git://github.com/hapijs/h2o2", "main": "lib/index.js", "files": [ From 83dbe85a70791204612b0cd1daa10f09be6129c9 Mon Sep 17 00:00:00 2001 From: Antony Jones Date: Tue, 31 Mar 2020 10:28:19 +0100 Subject: [PATCH 63/73] Update onResponse for Wreck's async/await syntax --- API.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/API.md b/API.md index 69035a0..d4e23a1 100644 --- a/API.md +++ b/API.md @@ -178,16 +178,15 @@ server.route({ uri: 'https://some.upstream.service.com/' }; }, - onResponse: function (err, res, request, h, settings, ttl) { + onResponse: async function (err, res, request, h, settings, ttl) { console.log('receiving the response from the upstream.'); - Wreck.read(res, { json: true }, function (err, payload) { + const payload = await Wreck.read(res, { json: true }) - console.log('some payload manipulation if you want to.') - const response = h.response(payload); - response.headers = res.headers; - return response; - }); + console.log('some payload manipulation if you want to.') + const response = h.response(payload); + response.headers = res.headers; + return response; } } } From 323e8406e7d7f822eba7472ed8a3f3595ddedb99 Mon Sep 17 00:00:00 2001 From: Antony Jones Date: Tue, 31 Mar 2020 10:29:39 +0100 Subject: [PATCH 64/73] Unify use of semicolons --- API.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/API.md b/API.md index d4e23a1..0687539 100644 --- a/API.md +++ b/API.md @@ -181,9 +181,9 @@ server.route({ onResponse: async function (err, res, request, h, settings, ttl) { console.log('receiving the response from the upstream.'); - const payload = await Wreck.read(res, { json: true }) + const payload = await Wreck.read(res, { json: true }); - console.log('some payload manipulation if you want to.') + console.log('some payload manipulation if you want to.'); const response = h.response(payload); response.headers = res.headers; return response; From 8f61fec26e7b3b4911c05c5e261bd06ff4ab00fd Mon Sep 17 00:00:00 2001 From: Eran Hammer Date: Fri, 15 May 2020 12:19:59 -0700 Subject: [PATCH 65/73] Clarify res cleanup. Closes #105 --- .travis.yml | 1 + API.md | 2 +- package.json | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) mode change 100644 => 100755 API.md diff --git a/.travis.yml b/.travis.yml index 33b4f7d..21d27ed 100755 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ language: node_js node_js: - "12" + - "14" - "node" sudo: false diff --git a/API.md b/API.md old mode 100644 new mode 100755 index 0687539..a22e30d --- a/API.md +++ b/API.md @@ -55,7 +55,7 @@ The proxy handler object has the following properties: * `req` - the [wreck] (https://github.com/hapijs/wreck) request to the upstream server. * `onResponse` - a custom function for processing the response from the upstream service before sending to the client. Useful for custom error handling of responses from the proxied endpoint or other payload manipulation. Function signature is `function (err, res, request, h, settings, ttl)` where: * `err` - internal or upstream error returned from attempting to contact the upstream proxy. - * `res` - the node response object received from the upstream service. `res` is a readable stream (use the [wreck](https://github.com/hapijs/wreck) module `read` method to easily convert it to a Buffer or string). + * `res` - the node response object received from the upstream service. `res` is a readable stream (use the [wreck](https://github.com/hapijs/wreck) module `read` method to easily convert it to a Buffer or string). Note that it is your responsibility to close the `res` stream. * `request` - is the incoming [request object](http://hapijs.com/api#request-object). * `h` - the [response toolkit](https://hapijs.com/api#response-toolkit). * `settings` - the proxy handler configuration. diff --git a/package.json b/package.json index bd6ee4b..2ee08f5 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,9 @@ "version": "9.0.1", "repository": "git://github.com/hapijs/h2o2", "main": "lib/index.js", + "engines": { + "node": ">=12.0.0" + }, "files": [ "lib" ], From 2ac14fc746d21f97f4bb8004bc80b620d076c770 Mon Sep 17 00:00:00 2001 From: Lloyd Benson Date: Mon, 3 Aug 2020 16:11:35 -0500 Subject: [PATCH 66/73] update lab and switch joi to validate (#119) --- lib/index.js | 52 ++++++++++++++++++++++++++-------------------------- package.json | 4 ++-- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/lib/index.js b/lib/index.js index 7e2506c..ba10ef0 100755 --- a/lib/index.js +++ b/lib/index.js @@ -4,7 +4,7 @@ const Http = require('http'); const Https = require('https'); const Hoek = require('@hapi/hoek'); -const Joi = require('@hapi/joi'); +const Validate = require('@hapi/validate'); const Wreck = require('@hapi/wreck'); @@ -28,31 +28,31 @@ internals.defaults = { }; -internals.schema = Joi.object({ - httpClient: Joi.object({ - request: Joi.func(), - parseCacheControl: Joi.func() +internals.schema = Validate.object({ + httpClient: Validate.object({ + request: Validate.func(), + parseCacheControl: Validate.func() }), - host: Joi.string(), - port: Joi.number().integer(), - protocol: Joi.string().valid('http', 'https', 'http:', 'https:'), - uri: Joi.string(), - passThrough: Joi.boolean(), - localStatePassThrough: Joi.boolean(), - acceptEncoding: Joi.boolean().when('passThrough', { is: true, otherwise: Joi.forbidden() }), - rejectUnauthorized: Joi.boolean(), - xforward: Joi.boolean(), - redirects: Joi.number().min(0).integer().allow(false), - timeout: Joi.number().integer(), - mapUri: Joi.func(), - onResponse: Joi.func(), - onRequest: Joi.func(), - agent: Joi.object(), - ttl: Joi.string().valid('upstream').allow(null), - maxSockets: Joi.number().positive().allow(false), - secureProtocol: Joi.string(), - ciphers: Joi.string(), - downstreamResponseTime: Joi.boolean() + host: Validate.string(), + port: Validate.number().integer(), + protocol: Validate.string().valid('http', 'https', 'http:', 'https:'), + uri: Validate.string(), + passThrough: Validate.boolean(), + localStatePassThrough: Validate.boolean(), + acceptEncoding: Validate.boolean().when('passThrough', { is: true, otherwise: Validate.forbidden() }), + rejectUnauthorized: Validate.boolean(), + xforward: Validate.boolean(), + redirects: Validate.number().min(0).integer().allow(false), + timeout: Validate.number().integer(), + mapUri: Validate.func(), + onResponse: Validate.func(), + onRequest: Validate.func(), + agent: Validate.object(), + ttl: Validate.string().valid('upstream').allow(null), + maxSockets: Validate.number().positive().allow(false), + secureProtocol: Validate.string(), + ciphers: Validate.string(), + downstreamResponseTime: Validate.boolean() }) .xor('host', 'mapUri', 'uri') .without('mapUri', 'port') @@ -81,7 +81,7 @@ exports.plugin = { internals.handler = function (route, handlerOptions) { const settings = Hoek.applyToDefaults(internals.defaults, handlerOptions, { shallow: ['agent'] }); - Joi.assert(handlerOptions, internals.schema, 'Invalid proxy handler options (' + route.path + ')'); + Validate.assert(handlerOptions, internals.schema, 'Invalid proxy handler options (' + route.path + ')'); Hoek.assert(!route.settings.payload || ((route.settings.payload.output === 'data' || route.settings.payload.output === 'stream') && !route.settings.payload.parse), 'Cannot proxy if payload is parsed or if output is not stream or data'); settings.mapUri = handlerOptions.mapUri || internals.mapUri(handlerOptions.protocol, handlerOptions.host, handlerOptions.port, handlerOptions.uri); diff --git a/package.json b/package.json index 2ee08f5..d32ad0e 100644 --- a/package.json +++ b/package.json @@ -20,14 +20,14 @@ "dependencies": { "@hapi/boom": "9.x.x", "@hapi/hoek": "9.x.x", - "@hapi/joi": "17.x.x", + "@hapi/validate": "1.x.x", "@hapi/wreck": "17.x.x" }, "devDependencies": { "@hapi/code": "8.x.x", "@hapi/hapi": "19.x.x", "@hapi/inert": "6.x.x", - "@hapi/lab": "22.x.x" + "@hapi/lab": "23.x.x" }, "scripts": { "test": "lab -a @hapi/code -t 100 -L", From 888460ecba362b74f098e0161e071cf785ff0c0f Mon Sep 17 00:00:00 2001 From: Lloyd Benson Date: Tue, 11 Aug 2020 19:16:53 -0500 Subject: [PATCH 67/73] upgrade to hapi 20 (#120) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d32ad0e..97b7b5a 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ }, "devDependencies": { "@hapi/code": "8.x.x", - "@hapi/hapi": "19.x.x", + "@hapi/hapi": "20.x.x", "@hapi/inert": "6.x.x", "@hapi/lab": "23.x.x" }, From 5165cd8d299630c61faad0f194757978dfe9833d Mon Sep 17 00:00:00 2001 From: Lloyd Benson Date: Sat, 22 Aug 2020 13:01:41 -0500 Subject: [PATCH 68/73] migrate to new travis format (#121) --- .travis.yml | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index 21d27ed..baf8fe9 100755 --- a/.travis.yml +++ b/.travis.yml @@ -1,20 +1,8 @@ -language: node_js +version: ~> 1.0 -node_js: - - "12" - - "14" - - "node" -sudo: false - -install: - - "npm install" - - "npm install @hapi/hapi@$HAPI_VERSION" - -env: - - HAPI_VERSION="19" - -os: - - "linux" - - "osx" - - "windows" +import: + - hapijs/ci-config-travis:node_js.yml@main + - hapijs/ci-config-travis:install_plugin.yml@main + - hapijs/ci-config-travis:os.yml@main + - hapijs/ci-config-travis:env.yml@main From 6c1f9743c917b8e1d39f828a66f03c026bbe3d9e Mon Sep 17 00:00:00 2001 From: cjihrig Date: Sat, 26 Sep 2020 12:39:42 -0400 Subject: [PATCH 69/73] update to lab@24.x.x --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 97b7b5a..5b2b140 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "@hapi/code": "8.x.x", "@hapi/hapi": "20.x.x", "@hapi/inert": "6.x.x", - "@hapi/lab": "23.x.x" + "@hapi/lab": "24.x.x" }, "scripts": { "test": "lab -a @hapi/code -t 100 -L", From a1db8d2affa425cfa085d0f2c2e42e7a8b458ee5 Mon Sep 17 00:00:00 2001 From: cjihrig Date: Sat, 26 Sep 2020 12:42:43 -0400 Subject: [PATCH 70/73] v9.0.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5b2b140..e983ecd 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@hapi/h2o2", "description": "Proxy handler plugin for hapi.js", - "version": "9.0.1", + "version": "9.0.2", "repository": "git://github.com/hapijs/h2o2", "main": "lib/index.js", "engines": { From bae468bc66ab18608d2c1f9c1a7291f4e830d146 Mon Sep 17 00:00:00 2001 From: Jonas Pauthier Date: Sun, 15 Nov 2020 23:19:07 +0100 Subject: [PATCH 71/73] Create ci-plugin.yml (#122) * Create ci-plugin.yml * Remove .travis.yml --- .github/workflows/ci-plugin.yml | 30 ++++++++++++++++++++++++++++++ .travis.yml | 8 -------- 2 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/ci-plugin.yml delete mode 100755 .travis.yml diff --git a/.github/workflows/ci-plugin.yml b/.github/workflows/ci-plugin.yml new file mode 100644 index 0000000..7d21056 --- /dev/null +++ b/.github/workflows/ci-plugin.yml @@ -0,0 +1,30 @@ +name: ci + +on: + push: + branches: + - master + pull_request: + +jobs: + test: + strategy: + fail-fast: false + matrix: + os: [ubuntu, windows, macos] + node: ['*', '14', '12'] + hapi: ['20', '19'] + + runs-on: ${{ matrix.os }}-latest + name: ${{ matrix.os }} node@${{ matrix.node }} hapi@${{ matrix.hapi }} + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node }} + - name: install + run: npm install + - name: install hapi + run: npm install @hapi/hapi@${{ matrix.hapi }} + - name: test + run: npm test diff --git a/.travis.yml b/.travis.yml deleted file mode 100755 index baf8fe9..0000000 --- a/.travis.yml +++ /dev/null @@ -1,8 +0,0 @@ -version: ~> 1.0 - - -import: - - hapijs/ci-config-travis:node_js.yml@main - - hapijs/ci-config-travis:install_plugin.yml@main - - hapijs/ci-config-travis:os.yml@main - - hapijs/ci-config-travis:env.yml@main From d7e58f66f3cfef0469b0eeda274e4a85abf5799d Mon Sep 17 00:00:00 2001 From: Dara Dermody Date: Wed, 25 Nov 2020 14:47:14 +0000 Subject: [PATCH 72/73] Pulled latest upstream and simplified fork changes --- README.md | 8 ++++++ lib/index.js | 22 +++++++++++++++-- package.json | 8 +++--- test/index.js | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 100 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 70f377d..1b8346b 100644 --- a/README.md +++ b/README.md @@ -15,3 +15,11 @@ - [Changelog](https://hapi.dev/family/h2o2/changelog/) - [Project policies](https://hapi.dev/policies/) - [Free and commercial support options](https://hapi.dev/support/) + + +## Tagging new release + +Before tagging make sure the version in package.json was bumped and committed + +``` +git tag -a 5.4.0-kibi-5 -m 'version 5.4.0-kibi-5' && git push origin 5.4.0-kibi-5 diff --git a/lib/index.js b/lib/index.js index ba10ef0..bc1ec15 100755 --- a/lib/index.js +++ b/lib/index.js @@ -6,6 +6,7 @@ const Https = require('https'); const Hoek = require('@hapi/hoek'); const Validate = require('@hapi/validate'); const Wreck = require('@hapi/wreck'); +const URL = require('url'); const internals = { @@ -52,7 +53,8 @@ internals.schema = Validate.object({ maxSockets: Validate.number().positive().allow(false), secureProtocol: Validate.string(), ciphers: Validate.string(), - downstreamResponseTime: Validate.boolean() + downstreamResponseTime: Validate.boolean(), + mapHttpClientOptions: Validate.func() }) .xor('host', 'mapUri', 'uri') .without('mapUri', 'port') @@ -161,7 +163,11 @@ internals.handler = function (route, handlerOptions) { downstreamStartTime = process.hrtime(); } - const promise = settings.httpClient.request(request.method, uri, options); + if (settings.mapHttpClientOptions) { + Hoek.merge(options, await settings.mapHttpClientOptions(request)); + } + + const promise = settings.httpClient.request(request.method, internals.encodeUri(uri), options); if (settings.onRequest) { settings.onRequest(promise.req); @@ -299,3 +305,15 @@ internals.agent = function (protocol, settings, request) { return agents[type]; }; + + +internals.encodeUri = function (uri) { + + const tempUri = URL.parse(uri); + if (decodeURI(tempUri.pathname) === tempUri.pathname) { + tempUri.pathname = encodeURI(tempUri.pathname); + return URL.format(tempUri); + } + + return uri; +}; diff --git a/package.json b/package.json index e983ecd..6a88fac 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "@hapi/h2o2", - "description": "Proxy handler plugin for hapi.js", - "version": "9.0.2", - "repository": "git://github.com/hapijs/h2o2", + "name": "@sirensolutions/h2o2", + "description": "Fork of proxy handler plugin for hapi.js that adds ability to modify request", + "version": "9.0.2-kibi-1", + "repository": "git://github.com/sirensolutions/h2o2", "main": "lib/index.js", "engines": { "node": ">=12.0.0" diff --git a/test/index.js b/test/index.js index caddb30..6d0e914 100755 --- a/test/index.js +++ b/test/index.js @@ -1961,4 +1961,72 @@ describe('h2o2', () => { expect(res.payload).to.equal('ok'); }); + + it('updates payload based on mapHttpClientOptions', async () => { + + const upstream = Hapi.server(); + upstream.route({ method: 'POST', path: '/', handler: (request) => request.payload }); + await upstream.start(); + + const server = Hapi.server(); + await server.register(H2o2); + + server.route({ + method: 'POST', + path: '/', + handler: { + proxy: { + mapHttpClientOptions: async (request) => ({ payload: await internals.parseReadStream(request.payload) + 'bar' }), + host: 'localhost', + port: upstream.info.port + } + } + }); + + const response = await server.inject({ method: 'POST', url: '/', payload: 'foo', headers: { 'Content-Type': 'text/plain' } }); + expect(response.payload).to.equal('foobar'); + expect(response.statusCode).to.equal(200); + + await upstream.stop(); + }); + + it('does not encode URI if already encoded', async () => { + + const upstream = Hapi.server(); + upstream.route({ method: 'GET', path: '/{param}', handler: (request) => ({ path: request.path, param: request.params.param }) }); + await upstream.start(); + + const server = Hapi.server(); + await server.register(H2o2); + + server.route({ + method: 'GET', + path: '/{param}', + handler: { + proxy: { + host: 'localhost', + port: upstream.info.port + } + } + }); + + const response = await server.inject(encodeURI('/フーバー')); + const { path, param } = JSON.parse(response.payload); + expect(path).to.equal(encodeURI('/フーバー')); + expect(param).to.equal('フーバー'); + expect(response.statusCode).to.equal(200); + + await upstream.stop(); + }); }); + +internals.parseReadStream = function (stream) { + + return new Promise((resolve, reject) => { + + const chunks = []; + stream.on('error', reject); + stream.on('data', (chunk) => chunks.push(chunk)); + stream.on('end', () => resolve(chunks.join().toString())); + }); +}; From a04bf7bbf921969ea5685200515973c2ca8cc5da Mon Sep 17 00:00:00 2001 From: Dara Dermody Date: Wed, 25 Nov 2020 15:14:58 +0000 Subject: [PATCH 73/73] Updated GitHub workflow to install peer depenency --- .github/workflows/ci-plugin.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci-plugin.yml b/.github/workflows/ci-plugin.yml index 7d21056..70c7fa1 100644 --- a/.github/workflows/ci-plugin.yml +++ b/.github/workflows/ci-plugin.yml @@ -26,5 +26,7 @@ jobs: run: npm install - name: install hapi run: npm install @hapi/hapi@${{ matrix.hapi }} + - name: install eslint + run: npm install --no-save eslint - name: test run: npm test