diff --git a/API.md b/API.md
index 7b9316f06..fdcc44881 100755
--- a/API.md
+++ b/API.md
@@ -98,10 +98,35 @@ assigned one or more (array):
#### `server.options.compression`
-Default value: `{ minBytes: 1024 }`.
+Default value: `{ decompress: true, encodings: { gzip: true, deflate: true, br: false, zstd: false }, minBytes: 1024 }`.
Defines server handling of content encoding requests. If `false`, response content encoding is
-disabled and no compression is performed by the server.
+disabled, and no compression is performed by the server.
+
+#### `server.options.compression.decompress`
+
+Default value: `true`.
+
+Controls whether the server automatically decompresses incoming content encoding requests.
+If `false`, no decompression automatically performed by the server.
+
+#### `server.options.compression.encodings`
+
+Default value: `{ gzip: true, deflate: true, br: false, zstd: false }`.
+
+Configures the built-in support of compression algorithms aka encodings, represented by the object of kv pairs.
+
+Available values for each kv pair:
+
+- `true` - enables the encoding with default options.
+- `false` - disables the encoding.
+
+Note that default encoder and decoder options can be configured at the server default [route configuration](#server.options.routes)
+
+Zstd compression is experimental (see [node Zstd documentation](https://nodejs.org/api/zlib.html#zlibcreatezstdcompressoptions)).
+
+Disabling an encoding allows custom compression algorithm to be applied by
+[`server.encoder()`](#server.encoder()) and [`server.decoder()`](#server.decoder()).
##### `server.options.compression.minBytes`
@@ -110,6 +135,13 @@ Default value: '1024'.
Sets the minimum response payload size in bytes that is required for content encoding compression.
If the payload size is under the limit, no compression is performed.
+##### `server.options.compression.priority`
+
+Default value: `null`.
+
+Sets the priority for content encoding compression algorithms in descending order,
+e.g.: `['zstd', 'br', 'gzip', 'deflate']`.
+
#### `server.options.debug`
Default value: `{ request: ['implementation'] }`.
@@ -1290,8 +1322,7 @@ are called, where:
### `server.decoder(encoding, decoder)`
-Registers a custom content decoding compressor to extend the built-in support for `'gzip'` and
-'`deflate`' where:
+Registers a custom content decoding compressor to extend the built-in support where:
- `encoding` - the decoder name string.
@@ -1487,8 +1518,7 @@ The `dependencies` configuration accepts one of:
### `server.encoder(encoding, encoder)`
-Registers a custom content encoding compressor to extend the built-in support for `'gzip'` and
-'`deflate`' where:
+Registers a custom content encoding compressor to extend the built-in support where:
- `encoding` - the encoder name string.
diff --git a/lib/compression.js b/lib/compression.js
index 3e4c692e4..4a8889816 100755
--- a/lib/compression.js
+++ b/lib/compression.js
@@ -5,33 +5,87 @@ const Zlib = require('zlib');
const Accept = require('@hapi/accept');
const Bounce = require('@hapi/bounce');
const Hoek = require('@hapi/hoek');
+const Boom = require('@hapi/boom');
+const defaultBrotliOptions = {
+ params: {
+ [Zlib.constants.BROTLI_PARAM_QUALITY]: 4
+ }
+};
+
+const defaultZstdOptions = {
+ params: {
+ [Zlib.constants.ZSTD_c_compressionLevel]: 6
+ }
+};
const internals = {
- common: ['gzip, deflate', 'deflate, gzip', 'gzip', 'deflate', 'gzip, deflate, br']
+ common: [
+ 'gzip, deflate, br, zstd',
+ 'gzip, deflate, br',
+ 'zstd',
+ 'br',
+ 'gzip, deflate',
+ 'deflate, gzip',
+ 'gzip',
+ 'deflate'
+ ],
+ provision: new Map([
+ ['zstd', [
+ (options = {}) => Zlib.createZstdCompress(Hoek.applyToDefaults(defaultZstdOptions, options)),
+ (options) => Zlib.createZstdDecompress(options)
+ ]],
+ ['br', [
+ (options = {}) => Zlib.createBrotliCompress(Hoek.applyToDefaults(defaultBrotliOptions, options)),
+ (options) => Zlib.createBrotliDecompress(options)
+ ]],
+ ['deflate', [
+ (options) => Zlib.createDeflate(options),
+ (options) => Zlib.createInflate(options)
+ ]],
+ ['gzip', [
+ (options) => Zlib.createGzip(options),
+ (options) => Zlib.createGunzip(options)
+ ]]
+ ])
};
exports = module.exports = internals.Compression = class {
- decoders = {
- gzip: (options) => Zlib.createGunzip(options),
- deflate: (options) => Zlib.createInflate(options)
- };
+ decoders = {};
- encodings = ['identity', 'gzip', 'deflate'];
+ encodings = ['identity'];
encoders = {
- identity: null,
- gzip: (options) => Zlib.createGzip(options),
- deflate: (options) => Zlib.createDeflate(options)
+ identity: null
};
#common = null;
+ #options = null;
- constructor() {
+ constructor(options) {
- this._updateCommons();
+ this.#options = options;
+ if (!this.#options) {
+ return this._updateCommons();
+ }
+
+ for (const [encoding, [encoder, decoder]] of internals.provision.entries()) {
+ const conditions = this.#options?.encodings?.[encoding];
+ if (conditions) {
+ this.addEncoder(encoding, encoder);
+ if (this.#options.decompress !== false) {
+ this.addDecoder(encoding, decoder);
+ }
+ else {
+ this.addDecoder(encoding, () => {
+
+ throw Boom.unsupportedMediaType();
+ });
+ }
+ }
+ }
}
_updateCommons() {
@@ -89,13 +143,13 @@ exports = module.exports = internals.Compression = class {
return null;
}
- const request = response.request;
- if (!request._core.settings.compression ||
- length !== null && length < request._core.settings.compression.minBytes) {
+ if (!this.#options ||
+ length !== null && length < this.#options.minBytes) {
return null;
}
+ const request = response.request;
const mime = request._core.mime.type(response.headers['content-type'] || 'application/octet-stream');
if (!mime.compressible) {
return null;
@@ -116,4 +170,10 @@ exports = module.exports = internals.Compression = class {
Hoek.assert(encoder !== undefined, `Unknown encoding ${encoding}`);
return encoder(request.route.settings.compression[encoding]);
}
+
+ setPriority(priority) {
+
+ this.encodings = [...new Set([...priority, ...this.encodings])];
+ this._updateCommons();
+ }
};
diff --git a/lib/config.js b/lib/config.js
index 2b668f97d..1153c135a 100755
--- a/lib/config.js
+++ b/lib/config.js
@@ -241,7 +241,15 @@ internals.server = Validate.object({
autoListen: Validate.boolean(),
cache: Validate.allow(null), // Validated elsewhere
compression: Validate.object({
- minBytes: Validate.number().min(1).integer().default(1024)
+ decompress: Validate.boolean().default(true),
+ encodings: Validate.object({
+ gzip: Validate.boolean().default(true),
+ deflate: Validate.boolean().default(true),
+ br: Validate.boolean().default(false),
+ zstd: Validate.boolean().default(false)
+ }).default(),
+ minBytes: Validate.number().min(1).integer().default(1024),
+ priority: Validate.array().items(Validate.string().valid('gzip', 'deflate', 'br', 'zstd')).default(null)
})
.allow(false)
.default(),
diff --git a/lib/core.js b/lib/core.js
index 202f6dbe0..42691c00f 100755
--- a/lib/core.js
+++ b/lib/core.js
@@ -54,7 +54,7 @@ exports = module.exports = internals.Core = class {
app = {};
auth = new Auth(this);
caches = new Map(); // Cache clients
- compression = new Compression();
+ compression = null;
controlled = null; // Other servers linked to the phases of this server
dependencies = []; // Plugin dependencies
events = new Podium.Podium(internals.events);
@@ -119,6 +119,7 @@ exports = module.exports = internals.Core = class {
this.settings = settings;
this.type = type;
+ this.compression = new Compression(this.settings.compression);
this.heavy = new Heavy(this.settings.load);
this.mime = new Mimos(this.settings.mime);
this.router = new Call.Router(this.settings.router);
@@ -127,6 +128,10 @@ exports = module.exports = internals.Core = class {
this._debug();
this._initializeCache();
+ if (this.settings.compression.priority) {
+ this.compression.setPriority(this.settings.compression.priority);
+ }
+
if (this.settings.routes.validate.validator) {
this.validator = Validation.validator(this.settings.routes.validate.validator);
}
diff --git a/lib/types/server/encoders.d.ts b/lib/types/server/encoders.d.ts
index c91fd7df3..96d89a259 100644
--- a/lib/types/server/encoders.d.ts
+++ b/lib/types/server/encoders.d.ts
@@ -1,4 +1,4 @@
-import { createDeflate, createGunzip, createGzip, createInflate } from 'zlib';
+import { createBrotliCompress, createBrotliDecompress, createDeflate, createGunzip, createGzip, createInflate, createZstdCompress, createZstdDecompress } from 'zlib';
/**
* Available [content encoders](https://github.com/hapijs/hapi/blob/master/API.md#-serverencoderencoding-encoder).
@@ -7,6 +7,8 @@ export interface ContentEncoders {
deflate: typeof createDeflate;
gzip: typeof createGzip;
+ br: typeof createBrotliCompress;
+ zstd: typeof createZstdCompress;
}
/**
@@ -16,4 +18,6 @@ export interface ContentDecoders {
deflate: typeof createInflate;
gzip: typeof createGunzip;
+ br: typeof createBrotliDecompress;
+ zstd: typeof createZstdDecompress;
}
diff --git a/lib/types/server/options.d.ts b/lib/types/server/options.d.ts
index 5b7c9a7e3..8e294341b 100644
--- a/lib/types/server/options.d.ts
+++ b/lib/types/server/options.d.ts
@@ -9,7 +9,15 @@ import { CacheProvider, ServerOptionsCache } from './cache';
import { SameSitePolicy, ServerStateCookieOptions } from './state';
export interface ServerOptionsCompression {
+ decompress?: boolean;
+ encodings?: {
+ gzip?: boolean;
+ deflate?: boolean;
+ br?: boolean;
+ zstd?: boolean;
+ };
minBytes: number;
+ priority?: string[];
}
/**
diff --git a/lib/types/server/server.d.ts b/lib/types/server/server.d.ts
index 4d64710e1..e32b8faf0 100644
--- a/lib/types/server/server.d.ts
+++ b/lib/types/server/server.d.ts
@@ -332,7 +332,7 @@ export class Server {
cache: ServerCache;
/**
- * Registers a custom content decoding compressor to extend the built-in support for 'gzip' and 'deflate' where:
+ * Registers a custom content decoding compressor to extend the built-in support where:
* @param encoding - the decoder name string.
* @param decoder - a function using the signature function(options) where options are the encoding specific options configured in the route payload.compression configuration option, and the
* return value is an object compatible with the output of node's zlib.createGunzip().
@@ -392,7 +392,7 @@ export class Server {
dependency(dependencies: Dependencies, after?: ((server: Server) => Promise) | undefined): void;
/**
- * Registers a custom content encoding compressor to extend the built-in support for 'gzip' and 'deflate' where:
+ * Registers a custom content encoding compressor to extend the built-in support where:
* @param encoding - the encoder name string.
* @param encoder - a function using the signature function(options) where options are the encoding specific options configured in the route compression option, and the return value is an object
* compatible with the output of node's zlib.createGzip().
diff --git a/package.json b/package.json
index ec7ba6c99..43e2f991d 100755
--- a/package.json
+++ b/package.json
@@ -39,7 +39,7 @@
"@hapi/somever": "^4.1.1",
"@hapi/statehood": "^8.2.0",
"@hapi/subtext": "^8.1.1",
- "@hapi/teamwork": "^6.0.0",
+ "@hapi/teamwork": "^6.0.1",
"@hapi/topo": "^6.0.2",
"@hapi/validate": "^2.0.1"
},
@@ -47,14 +47,14 @@
"@hapi/code": "^9.0.3",
"@hapi/eslint-plugin": "^6.0.0",
"@hapi/inert": "^7.1.0",
- "@hapi/joi-legacy-test": "npm:@hapi/joi@^15.0.0",
+ "@hapi/joi-legacy-test": "npm:@hapi/joi@^15.1.1",
"@hapi/lab": "^25.3.2",
"@hapi/vision": "^7.0.3",
"@hapi/wreck": "^18.1.0",
- "@types/node": "^18.19.122",
+ "@types/node": "^22.18.3",
"handlebars": "^4.7.8",
"joi": "^17.13.3",
- "legacy-readable-stream": "npm:readable-stream@^1.0.34",
+ "legacy-readable-stream": "npm:readable-stream@^1.1.14",
"typescript": "^4.9.5"
},
"scripts": {
diff --git a/test/common.js b/test/common.js
index eae48a36b..265de6992 100644
--- a/test/common.js
+++ b/test/common.js
@@ -3,6 +3,7 @@
const ChildProcess = require('child_process');
const Http = require('http');
const Net = require('net');
+const Zlib = require('zlib');
const internals = {};
@@ -30,3 +31,5 @@ internals.hasIPv6 = () => {
exports.hasLsof = internals.hasLsof();
exports.hasIPv6 = internals.hasIPv6();
+
+exports.hasZstd = !!Zlib.constants.ZSTD_CLEVEL_DEFAULT;
diff --git a/test/payload.js b/test/payload.js
index 93b045cdf..b1e53864f 100755
--- a/test/payload.js
+++ b/test/payload.js
@@ -14,6 +14,8 @@ const Hoek = require('@hapi/hoek');
const Lab = require('@hapi/lab');
const Wreck = require('@hapi/wreck');
+const Common = require('./common');
+
const internals = {};
@@ -477,6 +479,30 @@ describe('Payload', () => {
expect(peeked).to.be.true();
});
+ it('rejects compressed payload when decompression is disabled', async () => {
+
+ const message = { 'msg': 'This message is going to be gzipped.' };
+ const server = Hapi.server({ compression: { decompress: false } });
+ server.route({ method: 'POST', path: '/', handler: (request) => request.payload });
+
+ const compressed = await new Promise((resolve) => Zlib.gzip(JSON.stringify(message), (ignore, result) => resolve(result)));
+
+ const request = {
+ method: 'POST',
+ url: '/',
+ headers: {
+ 'content-type': 'application/json',
+ 'content-encoding': 'gzip',
+ 'content-length': compressed.length
+ },
+ payload: compressed
+ };
+
+ const res = await server.inject(request);
+ expect(res.result).to.exist();
+ expect(res.statusCode).to.equal(415);
+ });
+
it('handles gzipped payload', async () => {
const message = { 'msg': 'This message is going to be gzipped.' };
@@ -525,6 +551,54 @@ describe('Payload', () => {
expect(res.result).to.equal(message);
});
+ it('handles br payload', async () => {
+
+ const message = { 'msg': 'This message is going to be brotlied.' };
+ const server = Hapi.server({ compression: { encodings: { br: true } } });
+ server.route({ method: 'POST', path: '/', handler: (request) => request.payload });
+
+ const compressed = await new Promise((resolve) => Zlib.brotliCompress(JSON.stringify(message), (ignore, result) => resolve(result)));
+
+ const request = {
+ method: 'POST',
+ url: '/',
+ headers: {
+ 'content-type': 'application/json',
+ 'content-encoding': 'br',
+ 'content-length': compressed.length
+ },
+ payload: compressed
+ };
+
+ const res = await server.inject(request);
+ expect(res.result).to.exist();
+ expect(res.result).to.equal(message);
+ });
+
+ it('handles zstd payload', { skip: !Common.hasZstd }, async () => {
+
+ const message = { 'msg': 'This message is going to be zstded.' };
+ const server = Hapi.server({ compression: { encodings: { zstd: true } } });
+ server.route({ method: 'POST', path: '/', handler: (request) => request.payload });
+
+ const compressed = await new Promise((resolve) => Zlib.zstdCompress(JSON.stringify(message), (ignore, result) => resolve(result)));
+
+ const request = {
+ method: 'POST',
+ url: '/',
+ headers: {
+ 'content-type': 'application/json',
+ 'content-encoding': 'zstd',
+ 'content-length': compressed.length
+ },
+ payload: compressed
+ };
+
+ const res = await server.inject(request);
+ expect(res.result).to.exist();
+ expect(res.result).to.equal(message);
+ });
+
it('handles custom compression', async () => {
const message = { 'msg': 'This message is going to be gzipped.' };
diff --git a/test/transmit.js b/test/transmit.js
index b6f1fd7cd..d22242dda 100755
--- a/test/transmit.js
+++ b/test/transmit.js
@@ -677,6 +677,32 @@ describe('transmission', () => {
expect(res3.headers['last-modified']).to.equal(res2.headers['last-modified']);
});
+ it('returns a zstded file in the response when the request accepts zstd', { skip: !Common.hasZstd }, async () => {
+
+ const server = Hapi.server({ compression: { encodings: { zstd: true }, minBytes: 1 }, routes: { files: { relativeTo: __dirname } } });
+ await server.register(Inert);
+ server.route({ method: 'GET', path: '/file', handler: (request, h) => h.file(__dirname + '/../package.json') });
+
+ const res = await server.inject({ url: '/file', headers: { 'accept-encoding': 'zstd' } });
+ expect(res.headers['content-type']).to.equal('application/json; charset=utf-8');
+ expect(res.headers['content-encoding']).to.equal('zstd');
+ expect(res.headers['content-length']).to.not.exist();
+ expect(res.payload).to.exist();
+ });
+
+ it('returns a brotlied file in the response when the request accepts br', async () => {
+
+ const server = Hapi.server({ compression: { encodings: { br: true }, minBytes: 1 }, routes: { files: { relativeTo: __dirname } } });
+ await server.register(Inert);
+ server.route({ method: 'GET', path: '/file', handler: (request, h) => h.file(__dirname + '/../package.json') });
+
+ const res = await server.inject({ url: '/file', headers: { 'accept-encoding': 'br' } });
+ expect(res.headers['content-type']).to.equal('application/json; charset=utf-8');
+ expect(res.headers['content-encoding']).to.equal('br');
+ expect(res.headers['content-length']).to.not.exist();
+ expect(res.payload).to.exist();
+ });
+
it('returns a gzipped file in the response when the request accepts gzip', async () => {
const server = Hapi.server({ compression: { minBytes: 1 }, routes: { files: { relativeTo: __dirname } } });
@@ -729,6 +755,39 @@ describe('transmission', () => {
expect(res.payload).to.exist();
});
+ it('returns a deflated file in the response when the request accepts gzip, deflate but priority set to deflate', async () => {
+
+ const server = Hapi.server({ compression: { minBytes: 1, priority: ['deflate'] }, routes: { files: { relativeTo: __dirname } } });
+ await server.register(Inert);
+ server.route({ method: 'GET', path: '/file', handler: (request, h) => h.file(__dirname + '/../package.json') });
+
+ const res = await server.inject({ url: '/file', headers: { 'accept-encoding': 'gzip, deflate' } });
+ expect(res.headers['content-type']).to.equal('application/json; charset=utf-8');
+ expect(res.headers['content-encoding']).to.equal('deflate');
+ expect(res.headers['content-length']).to.not.exist();
+ expect(res.payload).to.exist();
+ });
+
+ it('returns a zstded stream response without a content-length header when accept-encoding is zstd', async () => {
+
+ const server = Hapi.server({ compression: { encodings: { zstd: true }, minBytes: 1 } });
+ server.route({ method: 'GET', path: '/stream', handler: () => new internals.TimerStream() });
+
+ const res = await server.inject({ url: '/stream', headers: { 'Content-Type': 'application/json', 'accept-encoding': 'zstd' } });
+ expect(res.statusCode).to.equal(200);
+ expect(res.headers['content-length']).to.not.exist();
+ });
+
+ it('returns a brotlied stream response without a content-length header when accept-encoding is br', async () => {
+
+ const server = Hapi.server({ compression: { encodings: { br: true }, minBytes: 1 } });
+ server.route({ method: 'GET', path: '/stream', handler: () => new internals.TimerStream() });
+
+ const res = await server.inject({ url: '/stream', headers: { 'Content-Type': 'application/json', 'accept-encoding': 'br' } });
+ expect(res.statusCode).to.equal(200);
+ expect(res.headers['content-length']).to.not.exist();
+ });
+
it('returns a gzipped stream response without a content-length header when accept-encoding is gzip', async () => {
const server = Hapi.server({ compression: { minBytes: 1 } });
@@ -749,6 +808,66 @@ describe('transmission', () => {
expect(res.headers['content-length']).to.not.exist();
});
+ it('returns a zstd response on a post request when accept-encoding: zstd is requested', { skip: !Common.hasZstd }, async () => {
+
+ const data = '{"test":"true"}';
+
+ const server = Hapi.server({ compression: { encodings: { zstd: true }, minBytes: 1 } });
+ server.route({ method: 'POST', path: '/', handler: (request) => request.payload });
+ await server.start();
+
+ const uri = 'http://localhost:' + server.info.port;
+ const zstded = await internals.compress('zstdCompress', Buffer.from(data));
+ const { payload } = await Wreck.post(uri, { headers: { 'accept-encoding': 'zstd' }, payload: data });
+ expect(payload.toString()).to.equal(zstded.toString());
+ await server.stop();
+ });
+
+ it('returns a zstd response on a get request when accept-encoding: zstd is requested', { skip: !Common.hasZstd }, async () => {
+
+ const data = '{"test":"true"}';
+
+ const server = Hapi.server({ compression: { encodings: { zstd: true }, minBytes: 1 } });
+ server.route({ method: 'GET', path: '/', handler: () => data });
+ await server.start();
+
+ const uri = 'http://localhost:' + server.info.port;
+ const zstded = await internals.compress('zstdCompress', Buffer.from(data));
+ const { payload } = await Wreck.get(uri, { headers: { 'accept-encoding': 'zstd' } });
+ expect(payload.toString()).to.equal(zstded.toString());
+ await server.stop();
+ });
+
+ it('returns a br response on a post request when accept-encoding: br is requested', async () => {
+
+ const data = '{"test":"true"}';
+
+ const server = Hapi.server({ compression: { encodings: { br: true }, minBytes: 1 } });
+ server.route({ method: 'POST', path: '/', handler: (request) => request.payload });
+ await server.start();
+
+ const uri = 'http://localhost:' + server.info.port;
+ const brotlied = await internals.compress('brotliCompress', Buffer.from(data));
+ const { payload } = await Wreck.post(uri, { headers: { 'accept-encoding': 'br' }, payload: data });
+ expect(payload.toString()).to.equal(brotlied.toString());
+ await server.stop();
+ });
+
+ it('returns a br response on a get request when accept-encoding: br is requested', async () => {
+
+ const data = '{"test":"true"}';
+
+ const server = Hapi.server({ compression: { encodings: { br: true }, minBytes: 1 } });
+ server.route({ method: 'GET', path: '/', handler: () => data });
+ await server.start();
+
+ const uri = 'http://localhost:' + server.info.port;
+ const brotlied = await internals.compress('brotliCompress', Buffer.from(data));
+ const { payload } = await Wreck.get(uri, { headers: { 'accept-encoding': 'br' } });
+ expect(payload.toString()).to.equal(brotlied.toString());
+ await server.stop();
+ });
+
it('returns a gzip response on a post request when accept-encoding: gzip is requested', async () => {
const data = '{"test":"true"}';
@@ -789,8 +908,9 @@ describe('transmission', () => {
await server.start();
const uri = 'http://localhost:' + server.info.port;
+ const zipped = await internals.compress('gzip', Buffer.from(data));
const { payload } = await Wreck.post(uri, { headers: { 'accept-encoding': '*' }, payload: data });
- expect(payload.toString()).to.equal(data);
+ expect(payload.toString()).to.equal(zipped.toString());
await server.stop();
});
@@ -802,8 +922,9 @@ describe('transmission', () => {
await server.start();
const uri = 'http://localhost:' + server.info.port;
+ const zipped = await internals.compress('gzip', Buffer.from(data));
const { payload } = await Wreck.get(uri, { headers: { 'accept-encoding': '*' } });
- expect(payload.toString()).to.equal(data);
+ expect(payload.toString()).to.equal(zipped.toString());
await server.stop();
});
@@ -835,7 +956,7 @@ describe('transmission', () => {
await server.stop();
});
- it('returns a gzip response on a post request when accept-encoding: gzip;q=1, deflate;q=0.5 is requested', async () => {
+ it('returns a gzip response on a post request when accept-encoding: gzip;q=1, deflate;q=0.5, br;q=0.7; zstd;q=0.8 is requested', async () => {
const data = '{"test":"true"}';
const server = Hapi.server({ compression: { minBytes: 1 } });
@@ -844,12 +965,12 @@ describe('transmission', () => {
const uri = 'http://localhost:' + server.info.port;
const zipped = await internals.compress('gzip', Buffer.from(data));
- const { payload } = await Wreck.post(uri, { headers: { 'accept-encoding': 'gzip;q=1, deflate;q=0.5' }, payload: data });
+ const { payload } = await Wreck.post(uri, { headers: { 'accept-encoding': 'gzip;q=1, deflate;q=0.5, br;q=0.7, zstd;q=0.8' }, payload: data });
expect(payload.toString()).to.equal(zipped.toString());
await server.stop();
});
- it('returns a gzip response on a get request when accept-encoding: gzip;q=1, deflate;q=0.5 is requested', async () => {
+ it('returns a gzip response on a get request when accept-encoding: gzip;q=1, deflate;q=0.5, br;q=0.7, zstd;q=0.8 is requested', async () => {
const data = '{"test":"true"}';
const server = Hapi.server({ compression: { minBytes: 1 } });
@@ -858,12 +979,12 @@ describe('transmission', () => {
const uri = 'http://localhost:' + server.info.port;
const zipped = await internals.compress('gzip', Buffer.from(data));
- const { payload } = await Wreck.get(uri, { headers: { 'accept-encoding': 'gzip;q=1, deflate;q=0.5' } });
+ const { payload } = await Wreck.get(uri, { headers: { 'accept-encoding': 'gzip;q=1, deflate;q=0.5, br;q=0.7, zstd;q=0.8' } });
expect(payload.toString()).to.equal(zipped.toString());
await server.stop();
});
- it('returns a deflate response on a post request when accept-encoding: deflate;q=1, gzip;q=0.5 is requested', async () => {
+ it('returns a deflate response on a post request when accept-encoding: deflate;q=1, gzip;q=0.5, br;q=0.7, zstd;q=0.8 is requested', async () => {
const data = '{"test":"true"}';
const server = Hapi.server({ compression: { minBytes: 1 } });
@@ -872,12 +993,12 @@ describe('transmission', () => {
const uri = 'http://localhost:' + server.info.port;
const deflated = await internals.compress('deflate', Buffer.from(data));
- const { payload } = await Wreck.post(uri, { headers: { 'accept-encoding': 'deflate;q=1, gzip;q=0.5' }, payload: data });
+ const { payload } = await Wreck.post(uri, { headers: { 'accept-encoding': 'deflate;q=1, gzip;q=0.5, br;q=0.7, zstd;q=0.8' }, payload: data });
expect(payload.toString()).to.equal(deflated.toString());
await server.stop();
});
- it('returns a deflate response on a get request when accept-encoding: deflate;q=1, gzip;q=0.5 is requested', async () => {
+ it('returns a deflate response on a get request when accept-encoding: deflate;q=1, gzip;q=0.5, br;q=0.7, zstd;q=0.8 is requested', async () => {
const data = '{"test":"true"}';
const server = Hapi.server({ compression: { minBytes: 1 } });
@@ -886,11 +1007,67 @@ describe('transmission', () => {
const uri = 'http://localhost:' + server.info.port;
const deflated = await internals.compress('deflate', Buffer.from(data));
- const { payload } = await Wreck.get(uri, { headers: { 'accept-encoding': 'deflate;q=1, gzip;q=0.5' } });
+ const { payload } = await Wreck.get(uri, { headers: { 'accept-encoding': 'deflate;q=1, gzip;q=0.5, br;q=0.7, zstd;q=0.8' } });
expect(payload.toString()).to.equal(deflated.toString());
await server.stop();
});
+ it('returns a br response on a post request when accept-encoding: gzip;q=1, deflate;q=0.5, br;q=1, zstd;q=0.8 is requested', async () => {
+
+ const data = '{"test":"true"}';
+ const server = Hapi.server({ compression: { encodings: { br: true }, minBytes: 1, priority: ['br'] } });
+ server.route({ method: 'POST', path: '/', handler: (request) => request.payload });
+ await server.start();
+
+ const uri = 'http://localhost:' + server.info.port;
+ const brotlied = await internals.compress('brotliCompress', Buffer.from(data));
+ const { payload } = await Wreck.post(uri, { headers: { 'accept-encoding': 'gzip;q=1, deflate;q=0.5, br;q=1, zstd;q=0.8' }, payload: data });
+ expect(payload.toString()).to.equal(brotlied.toString());
+ await server.stop();
+ });
+
+ it('returns a br response on a get request when accept-encoding: gzip;q=1, deflate;q=0.5, br;q=1, zstd;q=0.8 is requested', async () => {
+
+ const data = '{"test":"true"}';
+ const server = Hapi.server({ compression: { encodings: { br: true }, minBytes: 1, priority: ['br'] } });
+ server.route({ method: 'GET', path: '/', handler: () => data });
+ await server.start();
+
+ const uri = 'http://localhost:' + server.info.port;
+ const brotlied = await internals.compress('brotliCompress', Buffer.from(data));
+ const { payload } = await Wreck.get(uri, { headers: { 'accept-encoding': 'gzip;q=1, deflate;q=0.5, br;q=1, zstd;q=0.8' } });
+ expect(payload.toString()).to.equal(brotlied.toString());
+ await server.stop();
+ });
+
+ it('returns a zstd response on a post request when accept-encoding: gzip;q=1, deflate;q=0.5, br;q=0.7; zstd;q=1 is requested', { skip: !Common.hasZstd }, async () => {
+
+ const data = '{"test":"true"}';
+ const server = Hapi.server({ compression: { encodings: { zstd: true }, minBytes: 1, priority: ['zstd'] } });
+ server.route({ method: 'POST', path: '/', handler: (request) => request.payload });
+ await server.start();
+
+ const uri = 'http://localhost:' + server.info.port;
+ const zstded = await internals.compress('zstdCompress', Buffer.from(data));
+ const { payload } = await Wreck.post(uri, { headers: { 'accept-encoding': 'gzip;q=1, deflate;q=0.5, br;q=0.7, zstd;q=1' }, payload: data });
+ expect(payload.toString()).to.equal(zstded.toString());
+ await server.stop();
+ });
+
+ it('returns a zstd response on a get request when accept-encoding: gzip;q=1, deflate;q=0.5, br;q=0.7, zstd;q=1 is requested', { skip: !Common.hasZstd }, async () => {
+
+ const data = '{"test":"true"}';
+ const server = Hapi.server({ compression: { encodings: { zstd: true }, minBytes: 1, priority: ['zstd'] } });
+ server.route({ method: 'GET', path: '/', handler: () => data });
+ await server.start();
+
+ const uri = 'http://localhost:' + server.info.port;
+ const zstded = await internals.compress('zstdCompress', Buffer.from(data));
+ const { payload } = await Wreck.get(uri, { headers: { 'accept-encoding': 'gzip;q=1, deflate;q=0.5, br;q=0.7, zstd;q=1' } });
+ expect(payload.toString()).to.equal(zstded.toString());
+ await server.stop();
+ });
+
it('returns a gzip response on a post request when accept-encoding: deflate, gzip is requested', async () => {
const data = '{"test":"true"}';
@@ -919,6 +1096,62 @@ describe('transmission', () => {
await server.stop();
});
+ it('returns a br response on a post request when accept-encoding: gzip, deflate, br is requested', async () => {
+
+ const data = '{"test":"true"}';
+ const server = Hapi.server({ compression: { encodings: { br: true }, minBytes: 1, priority: ['br'] } });
+ server.route({ method: 'POST', path: '/', handler: (request) => request.payload });
+ await server.start();
+
+ const uri = 'http://localhost:' + server.info.port;
+ const brotlied = await internals.compress('brotliCompress', Buffer.from(data));
+ const { payload } = await Wreck.post(uri, { headers: { 'accept-encoding': 'gzip, deflate, br' }, payload: data });
+ expect(payload.toString()).to.equal(brotlied.toString());
+ await server.stop();
+ });
+
+ it('returns a br response on a get request when accept-encoding: gzip, deflate, br is requested', async () => {
+
+ const data = '{"test":"true"}';
+ const server = Hapi.server({ compression: { encodings: { br: true }, minBytes: 1, priority: ['br'] } });
+ server.route({ method: 'GET', path: '/', handler: () => data });
+ await server.start();
+
+ const uri = 'http://localhost:' + server.info.port;
+ const brotlied = await internals.compress('brotliCompress', Buffer.from(data));
+ const { payload } = await Wreck.get(uri, { headers: { 'accept-encoding': 'gzip, deflate, br' } });
+ expect(payload.toString()).to.equal(brotlied.toString());
+ await server.stop();
+ });
+
+ it('returns a zstd response on a post request when accept-encoding: gzip, deflate, br, zstd is requested', { skip: !Common.hasZstd }, async () => {
+
+ const data = '{"test":"true"}';
+ const server = Hapi.server({ compression: { encodings: { zstd: true }, minBytes: 1, priority: ['zstd'] } });
+ server.route({ method: 'POST', path: '/', handler: (request) => request.payload });
+ await server.start();
+
+ const uri = 'http://localhost:' + server.info.port;
+ const zstded = await internals.compress('zstdCompress', Buffer.from(data));
+ const { payload } = await Wreck.post(uri, { headers: { 'accept-encoding': 'gzip, deflate, br, zstd' }, payload: data });
+ expect(payload.toString()).to.equal(zstded.toString());
+ await server.stop();
+ });
+
+ it('returns a zstd response on a get request when accept-encoding: gzip, deflate, br, zstd is requested', { skip: !Common.hasZstd }, async () => {
+
+ const data = '{"test":"true"}';
+ const server = Hapi.server({ compression: { encodings: { zstd: true }, minBytes: 1, priority: ['zstd'] } });
+ server.route({ method: 'GET', path: '/', handler: () => data });
+ await server.start();
+
+ const uri = 'http://localhost:' + server.info.port;
+ const zstded = await internals.compress('zstdCompress', Buffer.from(data));
+ const { payload } = await Wreck.get(uri, { headers: { 'accept-encoding': 'gzip, deflate, br, zstd' } });
+ expect(payload.toString()).to.equal(zstded.toString());
+ await server.stop();
+ });
+
it('boom object reused does not affect encoding header.', async () => {
const error = Boom.badRequest();