diff --git a/README.md b/README.md index 73acf89e..edc4aa75 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,9 @@ before trusting. For example, `req.body.foo.toString()` may fail in multiple ways, for example the `foo` property may not be there or may not be a string, and `toString` may not be a function and instead a string or other user input. +**Note** Brotli provides better and faster compression then gzip or deflate, +but is supported only since Node.js versions v11.7.0 and v10.16.0. + [Learn about the anatomy of an HTTP transaction in Node.js](https://nodejs.org/en/docs/guides/anatomy-of-an-http-transaction/). _This does not handle multipart bodies_, due to their complex and typically @@ -68,8 +71,8 @@ The various errors returned by this module are described in the Returns middleware that only parses `json` and only looks at requests where the `Content-Type` header matches the `type` option. This parser accepts any -Unicode encoding of the body and supports automatic inflation of `gzip` and -`deflate` encodings. +Unicode encoding of the body and supports automatic inflation of `gzip`, +`br` (brotli) and `deflate` encodings. A new `body` object containing the parsed data is populated on the `request` object after the middleware (i.e. `req.body`). @@ -123,7 +126,8 @@ encoding of the request. The parsing can be aborted by throwing an error. Returns middleware that parses all bodies as a `Buffer` and only looks at requests where the `Content-Type` header matches the `type` option. This -parser supports automatic inflation of `gzip` and `deflate` encodings. +parser supports automatic inflation of `gzip`, `br` (brotli) and `deflate` +encodings. A new `body` object containing the parsed data is populated on the `request` object after the middleware (i.e. `req.body`). This will be a `Buffer` object @@ -168,7 +172,8 @@ encoding of the request. The parsing can be aborted by throwing an error. Returns middleware that parses all bodies as a string and only looks at requests where the `Content-Type` header matches the `type` option. This -parser supports automatic inflation of `gzip` and `deflate` encodings. +parser supports automatic inflation of `gzip`, `br` (brotli) and `deflate` +encodings. A new `body` string containing the parsed data is populated on the `request` object after the middleware (i.e. `req.body`). This will be a string of the @@ -218,7 +223,7 @@ encoding of the request. The parsing can be aborted by throwing an error. Returns middleware that only parses `urlencoded` bodies and only looks at requests where the `Content-Type` header matches the `type` option. This parser accepts only UTF-8 encoding of the body and supports automatic -inflation of `gzip` and `deflate` encodings. +inflation of `gzip`, `br` (brotli) and `deflate` encodings. A new `body` object containing the parsed data is populated on the `request` object after the middleware (i.e. `req.body`). This object will contain diff --git a/lib/read.js b/lib/read.js index c1026095..3478578e 100644 --- a/lib/read.js +++ b/lib/read.js @@ -23,6 +23,12 @@ var zlib = require('zlib') module.exports = read +/** + * @const + * whether current node version has brotli support + */ +var hasBrotliSupport = 'brotli' in process.versions + /** * Read a request into a buffer and parse. * @@ -170,6 +176,18 @@ function contentstream (req, debug, inflate) { stream = req stream.length = length break + case 'br': + if (hasBrotliSupport) { + stream = zlib.createBrotliDecompress() + debug('brotli decompress body') + req.pipe(stream) + } else { + throw createError(415, 'unsupported content encoding "' + encoding + '"', { + encoding: encoding, + type: 'encoding.unsupported' + }) + } + break default: throw createError(415, 'unsupported content encoding "' + encoding + '"', { encoding: encoding, diff --git a/package.json b/package.json index 1ea75517..03a2fb0a 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ }, "scripts": { "lint": "eslint --plugin markdown --ext js,md .", - "test": "mocha --require test/support/env --reporter spec --check-leaks --bail test/", + "test": "mocha --require test/support/env --reporter spec --check-leaks --bail test/ --globals __core-js_shared__", "test-cov": "nyc --reporter=html --reporter=text npm test", "test-travis": "nyc --reporter=text npm test" } diff --git a/test/json.js b/test/json.js index ae745712..87185a8d 100644 --- a/test/json.js +++ b/test/json.js @@ -6,6 +6,12 @@ var request = require('supertest') var bodyParser = require('..') +/** + * @const + * whether current node version has brotli support + */ +var hasBrotliSupport = 'brotli' in process.versions + describe('bodyParser.json()', function () { it('should parse JSON', function (done) { request(createServer()) @@ -595,6 +601,24 @@ describe('bodyParser.json()', function () { test.expect(200, '{"name":"论"}', done) }) + var brotlit = hasBrotliSupport ? it : it.skip + brotlit('should support brotli encoding', function (done) { + var test = request(this.server).post('/') + test.set('Content-Encoding', 'br') + test.set('Content-Type', 'application/json') + test.write(Buffer.from('8b06807b226e616d65223a22e8aeba227d03', 'hex')) + test.expect(200, '{"name":"论"}', done) + }) + + var nobrotlit = hasBrotliSupport ? it.skip : it + nobrotlit('should throw 415 if there\'s no brotli support', function (done) { + var test = request(this.server).post('/') + test.set('Content-Encoding', 'br') + test.set('Content-Type', 'application/json') + test.write(Buffer.from('8b06807b226e616d65223a22e8aeba227d03', 'hex')) + test.expect(415, 'unsupported content encoding "br"', done) + }) + it('should be case-insensitive', function (done) { var test = request(this.server).post('/') test.set('Content-Encoding', 'GZIP') diff --git a/test/raw.js b/test/raw.js index 36dc1ef6..71c6d5ff 100644 --- a/test/raw.js +++ b/test/raw.js @@ -6,6 +6,12 @@ var request = require('supertest') var bodyParser = require('..') +/** + * @const + * whether current node version has brotli support + */ +var hasBrotliSupport = 'brotli' in process.versions + describe('bodyParser.raw()', function () { before(function () { this.server = createServer() @@ -339,6 +345,24 @@ describe('bodyParser.raw()', function () { test.expect(200, 'buf:6e616d653de8aeba', done) }) + var brotlit = hasBrotliSupport ? it : it.skip + brotlit('should support brotli encoding', function (done) { + var test = request(this.server).post('/') + test.set('Content-Encoding', 'br') + test.set('Content-Type', 'application/octet-stream') + test.write(Buffer.from('8b03806e616d653de8aeba03', 'hex')) + test.expect(200, 'buf:6e616d653de8aeba', done) + }) + + var nobrotlit = hasBrotliSupport ? it.skip : it + nobrotlit('should throw 415 if there\'s no brotli support', function (done) { + var test = request(this.server).post('/') + test.set('Content-Encoding', 'br') + test.set('Content-Type', 'application/octet-stream') + test.write(Buffer.from('8b03806e616d653de8aeba03', 'hex')) + test.expect(415, 'unsupported content encoding "br"', done) + }) + it('should be case-insensitive', function (done) { var test = request(this.server).post('/') test.set('Content-Encoding', 'GZIP') diff --git a/test/text.js b/test/text.js index 01781d7f..7c50a8c0 100644 --- a/test/text.js +++ b/test/text.js @@ -6,6 +6,12 @@ var request = require('supertest') var bodyParser = require('..') +/** + * @const + * whether current node version has brotli support + */ +var hasBrotliSupport = 'brotli' in process.versions + describe('bodyParser.text()', function () { before(function () { this.server = createServer() @@ -407,6 +413,24 @@ describe('bodyParser.text()', function () { test.expect(200, '"name is 论"', done) }) + var brotlit = hasBrotliSupport ? it : it.skip + brotlit('should support brotli encoding', function (done) { + var test = request(this.server).post('/') + test.set('Content-Encoding', 'br') + test.set('Content-Type', 'text/plain') + test.write(Buffer.from('0b05806e616d6520697320e8aeba03', 'hex')) + test.expect(200, '"name is 论"', done) + }) + + var nobrotlit = hasBrotliSupport ? it.skip : it + nobrotlit('should throw 415 if there\'s no brotli support', function (done) { + var test = request(this.server).post('/') + test.set('Content-Encoding', 'br') + test.set('Content-Type', 'text/plain') + test.write(Buffer.from('0b05806e616d6520697320e8aeba03', 'hex')) + test.expect(415, 'unsupported content encoding "br"', done) + }) + it('should be case-insensitive', function (done) { var test = request(this.server).post('/') test.set('Content-Encoding', 'GZIP') diff --git a/test/urlencoded.js b/test/urlencoded.js index e867c0f4..f8042796 100644 --- a/test/urlencoded.js +++ b/test/urlencoded.js @@ -6,6 +6,12 @@ var request = require('supertest') var bodyParser = require('..') +/** + * @const + * whether current node version has brotli support + */ +var hasBrotliSupport = 'brotli' in process.versions + describe('bodyParser.urlencoded()', function () { before(function () { this.server = createServer() @@ -681,6 +687,24 @@ describe('bodyParser.urlencoded()', function () { test.expect(200, '{"name":"论"}', done) }) + var brotlit = hasBrotliSupport ? it : it.skip + brotlit('should support brotli encoding', function (done) { + var test = request(this.server).post('/') + test.set('Content-Encoding', 'br') + test.set('Content-Type', 'application/x-www-form-urlencoded') + test.write(Buffer.from('8b03806e616d653de8aeba03', 'hex')) + test.expect(200, '{"name":"论"}', done) + }) + + var nobrotlit = hasBrotliSupport ? it.skip : it + nobrotlit('should throw 415 if there\'s no brotli support', function (done) { + var test = request(this.server).post('/') + test.set('Content-Encoding', 'br') + test.set('Content-Type', 'application/x-www-form-urlencoded') + test.write(Buffer.from('789ccb4bcc4db57db16e17001068042f', 'hex')) + test.expect(415, 'unsupported content encoding "br"', done) + }) + it('should be case-insensitive', function (done) { var test = request(this.server).post('/') test.set('Content-Encoding', 'GZIP')