Skip to content

Commit b1f3d58

Browse files
authored
feat: zstandard support (#586)
* feat: zstandard support * docs: clarify zstd support and remove redundant not
1 parent d57c5ba commit b1f3d58

6 files changed

Lines changed: 115 additions & 15 deletions

File tree

README.md

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ This module provides the following parsers:
3737
* [Text body parser](#bodyparsertextoptions)
3838
* [URL-encoded form body parser](#bodyparserurlencodedoptions)
3939

40+
All parsers support automatic inflation of `gzip`, `br` (brotli), `deflate`
41+
and `zstd` encodings. `zstd` is available in Node.js `^22.15.0` and
42+
`>=23.8.0`.
43+
4044
Other body parsers you might be interested in:
4145

4246
- [body](https://www.npmjs.com/package/body#readme)
@@ -72,8 +76,7 @@ The various errors returned by this module are described in the
7276

7377
Returns middleware that only parses `json` and only looks at requests where
7478
the `Content-Type` header matches the `type` option. This parser accepts any
75-
Unicode encoding of the body and supports automatic inflation of `gzip`,
76-
`br` (brotli) and `deflate` encodings.
79+
Unicode encoding of the body.
7780

7881
A new `body` object containing the parsed data is populated on the `request`
7982
object after the middleware (i.e. `req.body`).
@@ -133,9 +136,7 @@ encoding of the request. The parsing can be aborted by throwing an error.
133136
### bodyParser.raw([options])
134137

135138
Returns middleware that parses all bodies as a `Buffer` and only looks at
136-
requests where the `Content-Type` header matches the `type` option. This
137-
parser supports automatic inflation of `gzip`, `br` (brotli) and `deflate`
138-
encodings.
139+
requests where the `Content-Type` header matches the `type` option.
139140

140141
A new `body` object containing the parsed data is populated on the `request`
141142
object after the middleware (i.e. `req.body`). This will be a `Buffer` object
@@ -181,9 +182,7 @@ encoding of the request. The parsing can be aborted by throwing an error.
181182
### bodyParser.text([options])
182183

183184
Returns middleware that parses all bodies as a string and only looks at
184-
requests where the `Content-Type` header matches the `type` option. This
185-
parser supports automatic inflation of `gzip`, `br` (brotli) and `deflate`
186-
encodings.
185+
requests where the `Content-Type` header matches the `type` option.
187186

188187
A new `body` string containing the parsed data is populated on the `request`
189188
object after the middleware (i.e. `req.body`). This will be a string of the
@@ -234,8 +233,7 @@ encoding of the request. The parsing can be aborted by throwing an error.
234233

235234
Returns middleware that only parses `urlencoded` bodies and only looks at
236235
requests where the `Content-Type` header matches the `type` option. This
237-
parser accepts only UTF-8 and ISO-8859-1 encodings of the body and supports
238-
automatic inflation of `gzip`, `br` (brotli) and `deflate` encodings.
236+
parser accepts only UTF-8 and ISO-8859-1 encodings of the body.
239237

240238
A new `body` object containing the parsed data is populated on the `request`
241239
object after the middleware (i.e. `req.body`). This object will contain

lib/read.js

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ const contentType = require('content-type')
2525

2626
module.exports = read
2727

28+
/**
29+
* @const
30+
* whether current node version has zstandard support
31+
*/
32+
const hasZstandardSupport = 'createZstdDecompress' in zlib
33+
2834
/**
2935
* Read a request into a buffer and parse.
3036
*
@@ -221,12 +227,16 @@ function createDecompressionStream (encoding, debug) {
221227
case 'br':
222228
debug('brotli decompress body')
223229
return zlib.createBrotliDecompress()
224-
default:
225-
throw createError(415, 'unsupported content encoding "' + encoding + '"', {
226-
encoding,
227-
type: 'encoding.unsupported'
228-
})
230+
case 'zstd':
231+
if (hasZstandardSupport) {
232+
debug('zstd decompress body')
233+
return zlib.createZstdDecompress()
234+
}
229235
}
236+
throw createError(415, 'unsupported content encoding "' + encoding + '"', {
237+
encoding,
238+
type: 'encoding.unsupported'
239+
})
230240
}
231241

232242
/**

test/json.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@ const { describe, it, before } = require('mocha')
44
const assert = require('node:assert')
55
const AsyncLocalStorage = require('node:async_hooks').AsyncLocalStorage
66
const http = require('node:http')
7+
const zlib = require('node:zlib')
78
const request = require('supertest')
89

910
const bodyParser = require('..')
1011

12+
const hasZstandardSupport = 'createZstdDecompress' in zlib
13+
const zstandardit = hasZstandardSupport ? it : it.skip
14+
const nozstandardit = !hasZstandardSupport ? it : it.skip
15+
1116
describe('bodyParser.json()', function () {
1217
it('should parse JSON', function (done) {
1318
request(createServer())
@@ -703,6 +708,24 @@ describe('bodyParser.json()', function () {
703708
test.expect(200, '{"name":"论"}', done)
704709
})
705710

711+
zstandardit('should support zstandard encoding', function (done) {
712+
const server = createServer({ experimentalZstd: true, limit: '1kb' })
713+
const test = request(server).post('/')
714+
test.set('Content-Encoding', 'zstd')
715+
test.set('Content-Type', 'application/json')
716+
test.write(Buffer.from('28b52ffd200e7100007b226e616d65223a22e8aeba227d', 'hex'))
717+
test.expect(200, '{"name":"论"}', done)
718+
})
719+
720+
nozstandardit('should throw 415 if there\'s no zstandard support', function (done) {
721+
const server = createServer({ experimentalZstd: true, limit: '1kb' })
722+
const test = request(server).post('/')
723+
test.set('Content-Encoding', 'zstd')
724+
test.set('Content-Type', 'application/json')
725+
test.write(Buffer.from('28b52ffd200e7100007b226e616d65223a22e8aeba227d', 'hex'))
726+
test.expect(415, '[encoding.unsupported] unsupported content encoding "zstd"', done)
727+
})
728+
706729
it('should be case-insensitive', function (done) {
707730
const test = request(this.server).post('/')
708731
test.set('Content-Encoding', 'GZIP')

test/raw.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@ const { describe, it, before } = require('mocha')
44
const assert = require('node:assert')
55
const AsyncLocalStorage = require('node:async_hooks').AsyncLocalStorage
66
const http = require('node:http')
7+
const zlib = require('node:zlib')
78
const request = require('supertest')
89

910
const bodyParser = require('..')
1011

12+
const hasZstandardSupport = 'createZstdDecompress' in zlib
13+
const zstandardit = hasZstandardSupport ? it : it.skip
14+
const nozstandardit = !hasZstandardSupport ? it : it.skip
15+
1116
describe('bodyParser.raw()', function () {
1217
before(function () {
1318
this.server = createServer()
@@ -459,6 +464,24 @@ describe('bodyParser.raw()', function () {
459464
test.expect(200, 'buf:6e616d653de8aeba', done)
460465
})
461466

467+
zstandardit('should support zstandard encoding', function (done) {
468+
const server = createServer({ experimentalZstd: true, limit: '10kb' })
469+
const test = request(server).post('/')
470+
test.set('Content-Encoding', 'zstd')
471+
test.set('Content-Type', 'application/octet-stream')
472+
test.write(Buffer.from('28b52ffd20084100006e616d653de8aeba', 'hex'))
473+
test.expect(200, 'buf:6e616d653de8aeba', done)
474+
})
475+
476+
nozstandardit('should throw 415 if there\'s no zstandard support', function (done) {
477+
const server = createServer({ experimentalZstd: true, limit: '10kb' })
478+
const test = request(server).post('/')
479+
test.set('Content-Encoding', 'zstd')
480+
test.set('Content-Type', 'application/octet-stream')
481+
test.write(Buffer.from('28b52ffd20084100006e616d653de8aeba', 'hex'))
482+
test.expect(415, '[encoding.unsupported] unsupported content encoding "zstd"', done)
483+
})
484+
462485
it('should be case-insensitive', function (done) {
463486
const test = request(this.server).post('/')
464487
test.set('Content-Encoding', 'GZIP')

test/text.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@ const { describe, it, before } = require('mocha')
44
const assert = require('node:assert')
55
const AsyncLocalStorage = require('node:async_hooks').AsyncLocalStorage
66
const http = require('node:http')
7+
const zlib = require('node:zlib')
78
const request = require('supertest')
89

910
const bodyParser = require('..')
1011

12+
const hasZstandardSupport = 'createZstdDecompress' in zlib
13+
const zstandardit = hasZstandardSupport ? it : it.skip
14+
const nozstandardit = !hasZstandardSupport ? it : it.skip
15+
1116
describe('bodyParser.text()', function () {
1217
before(function () {
1318
this.server = createServer()
@@ -529,6 +534,24 @@ describe('bodyParser.text()', function () {
529534
test.expect(200, '"name is 论"', done)
530535
})
531536

537+
zstandardit('should support zstandard encoding', function (done) {
538+
const server = createServer({ experimentalZstd: true, limit: '10kb' })
539+
const test = request(server).post('/')
540+
test.set('Content-Encoding', 'zstd')
541+
test.set('Content-Type', 'text/plain')
542+
test.write(Buffer.from('28b52ffd200b5900006e616d6520697320e8aeba', 'hex'))
543+
test.expect(200, '"name is 论"', done)
544+
})
545+
546+
nozstandardit('should throw 415 if there\'s no zstandard support', function (done) {
547+
const server = createServer({ experimentalZstd: true, limit: '10kb' })
548+
const test = request(server).post('/')
549+
test.set('Content-Encoding', 'zstd')
550+
test.set('Content-Type', 'text/plain')
551+
test.write(Buffer.from('28b52ffd200b5900006e616d6520697320e8aeba', 'hex'))
552+
test.expect(415, '[encoding.unsupported] unsupported content encoding "zstd"', done)
553+
})
554+
532555
it('should be case-insensitive', function (done) {
533556
const test = request(this.server).post('/')
534557
test.set('Content-Encoding', 'GZIP')

test/urlencoded.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@ const { describe, it, before } = require('mocha')
44
const assert = require('node:assert')
55
const AsyncLocalStorage = require('node:async_hooks').AsyncLocalStorage
66
const http = require('node:http')
7+
const zlib = require('node:zlib')
78
const request = require('supertest')
89

910
const bodyParser = require('..')
1011

12+
const hasZstandardSupport = 'createZstdDecompress' in zlib
13+
const zstandardit = hasZstandardSupport ? it : it.skip
14+
const nozstandardit = !hasZstandardSupport ? it : it.skip
15+
1116
describe('bodyParser.urlencoded()', function () {
1217
before(function () {
1318
this.server = createServer()
@@ -911,6 +916,24 @@ describe('bodyParser.urlencoded()', function () {
911916
test.expect(200, '{"name":"论"}', done)
912917
})
913918

919+
zstandardit('should support zstandard encoding', function (done) {
920+
const server = createServer({ experimentalZstd: true })
921+
const test = request(server).post('/')
922+
test.set('Content-Encoding', 'zstd')
923+
test.set('Content-Type', 'application/x-www-form-urlencoded')
924+
test.write(Buffer.from('28b52ffd20084100006e616d653de8aeba', 'hex'))
925+
test.expect(200, '{"name":"论"}', done)
926+
})
927+
928+
nozstandardit('should throw 415 if there\'s no zstandard support', function (done) {
929+
const server = createServer({ experimentalZstd: true })
930+
const test = request(server).post('/')
931+
test.set('Content-Encoding', 'zstd')
932+
test.set('Content-Type', 'application/x-www-form-urlencoded')
933+
test.write(Buffer.from('28b52ffd20084100006e616d653de8aeba', 'hex'))
934+
test.expect(415, '[encoding.unsupported] unsupported content encoding "zstd"', done)
935+
})
936+
914937
it('should be case-insensitive', function (done) {
915938
const test = request(this.server).post('/')
916939
test.set('Content-Encoding', 'GZIP')

0 commit comments

Comments
 (0)