UNPKG

@sap-cloud-sdk/http-client

Version:

SAP Cloud SDK for JavaScript http-client

125 lines 5.78 kB
"use strict"; 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