/** * ------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. * See License in the project root for license information. * ------------------------------------------------------------------------------------------- */ import { inNodeEnv } from "@microsoft/kiota-abstractions"; import { trace } from "@opentelemetry/api"; import { getObservabilityOptionsFromRequest } from "../observabilityOptions.js"; import { CompressionHandlerOptions, CompressionHandlerOptionsKey } from "./options/compressionHandlerOptions.js"; import { deleteRequestHeader, getRequestHeader, setRequestHeader } from "../utils/headersUtil.js"; /** * Compress the url content. */ export class CompressionHandler { /** * Creates a new instance of the CompressionHandler class * @param handlerOptions The options for the compression handler. * @returns An instance of the CompressionHandler class */ constructor(handlerOptions = new CompressionHandlerOptions()) { this.handlerOptions = handlerOptions; if (!handlerOptions) { throw new Error("handlerOptions cannot be undefined"); } } /** * @inheritdoc */ execute(url, requestInit, requestOptions) { let currentOptions = this.handlerOptions; if (requestOptions === null || requestOptions === void 0 ? void 0 : requestOptions[CompressionHandlerOptionsKey]) { currentOptions = requestOptions[CompressionHandlerOptionsKey]; } const obsOptions = getObservabilityOptionsFromRequest(requestOptions); if (obsOptions) { return trace.getTracer(obsOptions.getTracerInstrumentationName()).startActiveSpan("compressionHandler - execute", (span) => { try { span.setAttribute("com.microsoft.kiota.handler.compression.enable", currentOptions.ShouldCompress); return this.executeInternal(currentOptions, url, requestInit, requestOptions, span); } finally { span.end(); } }); } return this.executeInternal(currentOptions, url, requestInit, requestOptions); } async executeInternal(options, url, requestInit, requestOptions, span) { var _a, _b, _c, _d; if (!options.ShouldCompress || this.contentRangeBytesIsPresent(requestInit.headers) || this.contentEncodingIsPresent(requestInit.headers) || requestInit.body === null || requestInit.body === undefined) { return (_b = (_a = this.next) === null || _a === void 0 ? void 0 : _a.execute(url, requestInit, requestOptions)) !== null && _b !== void 0 ? _b : Promise.reject(new Error("Response is undefined")); } span === null || span === void 0 ? void 0 : span.setAttribute("http.request.body.compressed", true); const unCompressedBody = requestInit.body; const unCompressedBodySize = this.getRequestBodySize(unCompressedBody); // compress the request body const compressedBody = await this.compressRequestBody(unCompressedBody); // add Content-Encoding to request header setRequestHeader(requestInit, CompressionHandler.CONTENT_ENCODING_HEADER, "gzip"); requestInit.body = compressedBody.compressedBody; span === null || span === void 0 ? void 0 : span.setAttribute("http.request.body.size", compressedBody.size); // execute the next middleware and check if the response code is 415 let response = await ((_c = this.next) === null || _c === void 0 ? void 0 : _c.execute(url, requestInit, requestOptions)); if (!response) { throw new Error("Response is undefined"); } if (response.status === 415) { // remove the Content-Encoding header deleteRequestHeader(requestInit, CompressionHandler.CONTENT_ENCODING_HEADER); requestInit.body = unCompressedBody; span === null || span === void 0 ? void 0 : span.setAttribute("http.request.body.compressed", false); span === null || span === void 0 ? void 0 : span.setAttribute("http.request.body.size", unCompressedBodySize); response = await ((_d = this.next) === null || _d === void 0 ? void 0 : _d.execute(url, requestInit, requestOptions)); } return response !== undefined && response !== null ? Promise.resolve(response) : Promise.reject(new Error("Response is undefined")); } contentRangeBytesIsPresent(header) { var _a; if (!header) { return false; } const contentRange = getRequestHeader(header, CompressionHandler.CONTENT_RANGE_HEADER); return (_a = contentRange === null || contentRange === void 0 ? void 0 : contentRange.toLowerCase().includes("bytes")) !== null && _a !== void 0 ? _a : false; } contentEncodingIsPresent(header) { if (!header) { return false; } return getRequestHeader(header, CompressionHandler.CONTENT_ENCODING_HEADER) !== undefined; } getRequestBodySize(body) { if (!body) { return 0; } if (typeof body === "string") { return body.length; } if (body instanceof Blob) { return body.size; } if (body instanceof ArrayBuffer) { return body.byteLength; } if (ArrayBuffer.isView(body)) { return body.byteLength; } if (inNodeEnv() && Buffer.isBuffer(body)) { return body.byteLength; } throw new Error("Unsupported body type"); } readBodyAsBytes(body) { if (!body) { return { stream: new ReadableStream(), size: 0 }; } const uint8ArrayToStream = (uint8Array) => { return new ReadableStream({ start(controller) { controller.enqueue(uint8Array); controller.close(); }, }); }; if (typeof body === "string") { return { stream: uint8ArrayToStream(new TextEncoder().encode(body)), size: body.length }; } if (body instanceof Blob) { return { stream: body.stream(), size: body.size }; } if (body instanceof ArrayBuffer) { return { stream: uint8ArrayToStream(new Uint8Array(body)), size: body.byteLength }; } if (ArrayBuffer.isView(body)) { return { stream: uint8ArrayToStream(new Uint8Array(body.buffer, body.byteOffset, body.byteLength)), size: body.byteLength }; } throw new Error("Unsupported body type"); } async compressRequestBody(body) { const compressionData = this.readBodyAsBytes(body); const compressedBody = await this.compressUsingCompressionStream(compressionData.stream); return { compressedBody: compressedBody.body, size: compressedBody.size, }; } async compressUsingCompressionStream(uint8ArrayStream) { const compressionStream = new CompressionStream("gzip"); const compressedStream = uint8ArrayStream.pipeThrough(compressionStream); const reader = compressedStream.getReader(); const compressedChunks = []; let totalLength = 0; let result = await reader.read(); while (!result.done) { const chunk = result.value; compressedChunks.push(chunk); totalLength += chunk.length; result = await reader.read(); } const compressedArray = new Uint8Array(totalLength); let offset = 0; for (const chunk of compressedChunks) { compressedArray.set(chunk, offset); offset += chunk.length; } return { body: compressedArray.buffer, size: compressedArray.length, }; } } /** * A member holding the name of content range header */ CompressionHandler.CONTENT_RANGE_HEADER = "Content-Range"; /** * A member holding the name of content encoding header */ CompressionHandler.CONTENT_ENCODING_HEADER = "Content-Encoding"; //# sourceMappingURL=compressionHandler.js.map