@microsoft/kiota-http-fetchlibrary
Version:
Kiota request adapter implementation with fetch
180 lines • 8.3 kB
JavaScript
/**
* -------------------------------------------------------------------------------------------
* 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