@sap-cloud-sdk/http-client
Version:
SAP Cloud SDK for JavaScript http-client
125 lines • 5.78 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.compressorsCache = void 0;
exports.compress = compress;
const node_util_1 = require("node:util");
const util_1 = require("@sap-cloud-sdk/util");
const logger = (0, util_1.createLogger)({
package: 'http-client',
messageContext: 'compress-request-middleware'
});
/**
* Dynamically imports zlib and checks for available compression algorithms.
* Caches the result to optimize subsequent calls.
* @internal
* @returns A promise that resolves to a map of available compressors.
*/
function getCompressors() {
return (exports.compressorsCache ??= (async () => {
// Dynamically import zlib to avoid issues in environments where it's not available (e.g., browsers).
const zlib = await import('node:zlib').catch(() => ({}));
return {
...(zlib.brotliCompress && { brotli: (0, node_util_1.promisify)(zlib.brotliCompress) }),
...(zlib.gzip && { gzip: (0, node_util_1.promisify)(zlib.gzip) }),
...(zlib.deflate && { deflate: (0, node_util_1.promisify)(zlib.deflate) }),
...(zlib.zstdCompress && { zstd: (0, node_util_1.promisify)(zlib.zstdCompress) })
};
})());
}
function getSupportedAlgorithms(available) {
return Object.keys(available).length
? Object.keys(available).join(', ')
: 'N/A';
}
/**
* Determines whether the payload needs compression based on the provided options.
* @param payload - The HTTP request payload.
* @param options - Configuration options for request compression.
* @returns Returns if the payload should be compressed.
*/
function checkIfNeedsCompression(payload, options) {
const mode = options?.mode ?? 'auto';
if (mode === 'header-only' || mode === 'never') {
return false;
}
if (mode === 'always') {
return true;
}
// Ensure we are in 'auto' mode if we reach this point
mode;
let payloadSize;
try {
payloadSize = Buffer.byteLength(payload);
}
catch (e) {
logger.error(new util_1.ErrorWithCause("Could not determine payload size for 'auto' compression decision. Payload will not be compressed.", e));
// Skip compression if payload size cannot be determined.
return false;
}
const minSize = options?.autoCompressMinSize ?? 1024;
const shouldCompress = payloadSize >= minSize;
const comparison = shouldCompress ? '>=' : '<';
const action = shouldCompress ? 'Compressing' : 'Skipping compression';
logger.debug(`Auto compression: payload size ${payloadSize} bytes ${comparison} threshold ${minSize} bytes. ${action}.`);
return shouldCompress;
}
function getContentEncodingValue(algorithm) {
switch (algorithm) {
case 'brotli':
return 'br';
case undefined:
case 'gzip':
return 'gzip';
default:
return algorithm;
}
}
/**
* Middleware to compress HTTP request payloads.
* @param options - Configuration options for request compression.
* @remarks
* **Middleware Ordering**: Place compression middleware early in your middleware array,
* typically after logging/csrf but before resilience middleware (retry, timeout, circuit breaker).
* This ensures the payload is compressed once and reused across retry attempts.
* @returns An HTTP middleware that compresses request payloads based on the provided options.
*/
function compress(options) {
return (middlewareOptions) => async (requestConfig) => {
const algorithm = options?.algorithm ?? 'gzip';
const needsCompression = checkIfNeedsCompression(requestConfig.data, options);
const mode = options?.mode ?? 'auto';
if (!needsCompression && mode !== 'header-only') {
return middlewareOptions.fn(requestConfig);
}
// If the payload already has a Content-Encoding header, we need to preserve it and append our encoding to it.
const currentContentEncoding = (0, util_1.pickValueIgnoreCase)(requestConfig.headers, 'content-encoding');
const algorithmContentEncoding = getContentEncodingValue(algorithm);
const targetContentEncoding = currentContentEncoding
? `${currentContentEncoding}, ${algorithmContentEncoding}`
: algorithmContentEncoding;
requestConfig.headers = (0, util_1.mergeIgnoreCase)(requestConfig.headers, {
'content-encoding': targetContentEncoding
});
if (mode === 'header-only') {
return middlewareOptions.fn(requestConfig);
}
const available = await getCompressors();
const compressor = available[algorithm];
if (!compressor) {
if (!Object.keys(available).length) {
throw new Error(`No compression algorithms are available in this environment. Cannot apply '${algorithm}' compression.`);
}
if (algorithm === 'zstd') {
throw new Error(`'zstd' compression is not supported in this environment. On Node.js, it requires v22.15.0 or higher. Supported algorithms are: ${getSupportedAlgorithms(available)}.`);
}
throw new Error(`Unsupported compression algorithm '${algorithm}'. Supported algorithms are: ${getSupportedAlgorithms(available)}.`);
}
// TODO: (future) Consider streaming compression for large payloads
const compressed = await compressor(requestConfig.data, options?.compressOptions).catch((err) => {
throw new util_1.ErrorWithCause(`Failed to compress request payload using '${algorithm}'.`, err);
});
requestConfig.data = compressed;
return middlewareOptions.fn(requestConfig);
};
}
//# sourceMappingURL=compress-request-middleware.js.map