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:
@@ -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).
|
||||
|
||||
@@ -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.
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -96,6 +96,7 @@ export default function mergeConfig(config1, config2) {
|
||||
onUploadProgress: defaultToConfig2,
|
||||
onDownloadProgress: defaultToConfig2,
|
||||
decompress: defaultToConfig2,
|
||||
advertiseZstd: defaultToConfig2,
|
||||
maxContentLength: defaultToConfig2,
|
||||
maxBodyLength: defaultToConfig2,
|
||||
beforeRedirect: defaultToConfig2,
|
||||
|
||||
@@ -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];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user