2
0
mirror of https://github.com/tenrok/axios.git synced 2026-06-17 19:21:29 +03:00

feat(http): implement zstd decompression for http adapter (#6792)

* feat: implement zstd compression for http adapter
* feat(http): gate zstd accept encoding

---------

Co-authored-by: Jay <jasonsaayman@gmail.com>
This commit is contained in:
Sung Jeon
2026-05-20 02:52:07 +09:00
committed by GitHub
parent e63e48c692
commit 2af5c5fbca
7 changed files with 97 additions and 1 deletions
+5
View File
@@ -29,6 +29,11 @@ This file is the canonical contributor guide for both human and AI agents workin
- Keep public runtime exports, `index.d.ts` (ESM types), and `index.d.cts` (CJS `export = axios` types) in sync for API changes.
- `lib/env/data.js` is version-generated by `gulp version`; do not edit it for normal feature work.
## Pre-Release Notes
- Add user-visible unreleased changes to `PRE_RELEASE_CHANGELOG.md`, not `CHANGELOG.md`. `CHANGELOG.md` is release-owned and should only be updated as part of preparing an actual release.
- Do not update `README.md` or the docs site for unreleased runtime/API changes unless the task is explicitly release preparation. Instead, record the exact README/docs updates needed under `PRE_RELEASE_CHANGELOG.md` so they can be applied during release work.
## Architecture Boundaries
- `lib/core/` is axios domain logic: request dispatch, config merge, interceptors, headers, errors. Key classes: `Axios` (request dispatch + interceptor chains), `AxiosError` (standardized error codes), `AxiosHeaders` (case-insensitive header normalization), `InterceptorManager` (sync/async interceptor registration).
+13
View File
@@ -0,0 +1,13 @@
# Pre-Release Changelog
## Unreleased
## New Features
- **HTTP Adapter - Zstandard:** Added automatic zstd decompression on Node.js versions that support it. `zstd` is only advertised in the default `Accept-Encoding` header when `advertiseZstd: true` is set. (**#6792**)
## Release Documentation TODO
- Update `README.md` request config docs for `advertiseZstd` and zstd decompression support.
- Update `docs/pages/advanced/request-config.md` for `advertiseZstd` and zstd decompression support.
- Update decompression-bomb security guidance in `README.md` and `docs/pages/misc/security.md` to mention zstd.
+1
View File
@@ -512,6 +512,7 @@ declare namespace axios {
proxy?: AxiosProxyConfig | false;
cancelToken?: CancelToken | undefined;
decompress?: boolean;
advertiseZstd?: boolean;
transitional?: TransitionalOptions;
signal?: GenericAbortSignal;
insecureHTTPParser?: boolean;
Vendored
+1
View File
@@ -410,6 +410,7 @@ export interface AxiosRequestConfig<D = any> {
proxy?: AxiosProxyConfig | false;
cancelToken?: CancelToken | undefined;
decompress?: boolean;
advertiseZstd?: boolean;
transitional?: TransitionalOptions;
signal?: GenericAbortSignal;
insecureHTTPParser?: boolean;
+16 -1
View File
@@ -44,7 +44,15 @@ const brotliOptions = {
finishFlush: zlib.constants.BROTLI_OPERATION_FLUSH,
};
const zstdOptions = {
flush: zlib.constants.ZSTD_e_flush,
finishFlush: zlib.constants.ZSTD_e_flush,
};
const isBrotliSupported = utils.isFunction(zlib.createBrotliDecompress);
const isZstdSupported = utils.isFunction(zlib.createZstdDecompress);
const ACCEPT_ENCODING = 'gzip, compress, deflate' + (isBrotliSupported ? ', br' : '');
const ACCEPT_ENCODING_WITH_ZSTD = ACCEPT_ENCODING + (isZstdSupported ? ', zstd' : '');
const { http: httpFollow, https: httpsFollow } = followRedirects;
@@ -861,7 +869,7 @@ export default isHttpAdapterSupported &&
headers.set(
'Accept-Encoding',
'gzip, compress, deflate' + (isBrotliSupported ? ', br' : ''),
own('advertiseZstd') === true ? ACCEPT_ENCODING_WITH_ZSTD : ACCEPT_ENCODING,
false
);
@@ -1037,6 +1045,13 @@ export default isHttpAdapterSupported &&
streams.push(zlib.createBrotliDecompress(brotliOptions));
delete res.headers['content-encoding'];
}
break;
case 'zstd':
if (isZstdSupported) {
streams.push(zlib.createZstdDecompress(zstdOptions));
delete res.headers['content-encoding'];
}
break;
}
}
+1
View File
@@ -96,6 +96,7 @@ export default function mergeConfig(config1, config2) {
onUploadProgress: defaultToConfig2,
onDownloadProgress: defaultToConfig2,
decompress: defaultToConfig2,
advertiseZstd: defaultToConfig2,
maxContentLength: defaultToConfig2,
maxBodyLength: defaultToConfig2,
beforeRedirect: defaultToConfig2,
+60
View File
@@ -727,6 +727,9 @@ describe('supports http with nodejs', () => {
});
describe('compression', async () => {
const isZstdSupported = typeof zlib.createZstdDecompress === 'function' &&
typeof zlib.zstdCompress === 'function';
it('should support transparent gunzip', async () => {
const data = {
firstName: 'Fred',
@@ -828,6 +831,50 @@ describe('supports http with nodejs', () => {
}
});
it('should not advertise zstd by default', async () => {
let acceptEncoding;
const server = await startHTTPServer(
(req, res) => {
acceptEncoding = req.headers['accept-encoding'];
res.end('ok');
},
{ port: SERVER_PORT }
);
try {
await axios.get(`http://localhost:${server.address().port}/`);
assert.strictEqual(acceptEncoding.includes('zstd'), false);
} finally {
await stopHTTPServer(server);
}
});
it('should advertise zstd when enabled and supported', async () => {
if (!isZstdSupported) {
return;
}
let acceptEncoding;
const server = await startHTTPServer(
(req, res) => {
acceptEncoding = req.headers['accept-encoding'];
res.end('ok');
},
{ port: SERVER_PORT }
);
try {
await axios.get(`http://localhost:${server.address().port}/`, {
advertiseZstd: true,
});
assert.strictEqual(acceptEncoding.includes('zstd'), true);
} finally {
await stopHTTPServer(server);
}
});
describe('algorithms', () => {
const responseBody = 'str';
@@ -879,6 +926,18 @@ describe('supports http with nodejs', () => {
});
});
const zstdCompress = (value) =>
new Promise((resolve, reject) => {
zlib.zstdCompress(value, (error, compressed) => {
if (error) {
reject(error);
return;
}
resolve(compressed);
});
});
for (const [typeName, zipped] of Object.entries({
gzip: gzip(responseBody),
GZIP: gzip(responseBody),
@@ -886,6 +945,7 @@ describe('supports http with nodejs', () => {
deflate: deflate(responseBody),
'deflate-raw': deflateRaw(responseBody),
br: brotliCompress(responseBody),
...(isZstdSupported ? { zstd: zstdCompress(responseBody) } : {}),
})) {
const type = typeName.split('-')[0];