import { context, propagation } from "@opentelemetry/api"; import { XMLParser } from "fast-xml-parser"; import * as http from "http"; import { ClientRequest } from "http"; import * as https from "https"; import { RequestOptions } from "https"; import * as zlib from "node:zlib"; import { URL } from "url"; function request(options: RequestOptions | string | URL, callback?: (res: http.IncomingMessage) => void): http.ClientRequest; function request(url: string | URL, options: RequestOptions, callback?: (res: http.IncomingMessage) => void): http.ClientRequest; function request(...args: any[]): http.ClientRequest { if (args[2]) { propagation.inject(context.active(), args[1].headers); return https.request(args[0], args[1], args[2]); } else { propagation.inject(context.active(), args[0].headers); return https.request(args[0], args[1]); } } async function doGetRequest(hostname: string, path: string, headers: any, port: number | string = 443) { return new Promise(function (resolve, reject) { const options = { "method": "GET", "hostname": hostname, "path": path, "headers": headers, "port": port } propagation.inject(context.active(), options.headers); const req = doHttpsRequest(options, resolve, reject); req.end(); }); } async function doPostRequest(hostname: string, path: string, headers: any, data: any, port: number | string = 443) { return new Promise(function (resolve, reject) { const options = { "method": "POST", "hostname": hostname, "path": path, "headers": headers, "port": port } propagation.inject(context.active(), options.headers); const req = doHttpsRequest(options, resolve, reject); setRequestBody(req, data); req.end(); }); } async function doPutRequest(hostname: string, path: string, headers: any, data: any, port: number | string = 443) { return new Promise(function (resolve, reject) { const options = { "method": "PUT", "hostname": hostname, "path": path, "headers": headers, "port": port } propagation.inject(context.active(), options.headers); const req = doHttpsRequest(options, resolve, reject); setRequestBody(req, data); req.end(); }); } async function doDeleteRequest(hostname: string, path: string, headers: any, port: number | string = 443) { return new Promise(function (resolve, reject) { const options = { "method": "DELETE", "hostname": hostname, "path": path, "headers": headers, "port": port } propagation.inject(context.active(), options.headers); const req = doHttpsRequest(options, resolve, reject); req.end(); }); } function doHttpsRequest(options: RequestOptions, resolve: any, reject: any): ClientRequest { return https.request(options, function (response) { const chunks: any[] = []; response.on("data", function (chunk) { chunks.push(chunk); }); response.on("end", function () { if (response.statusCode === 204) { //204 is no content, and most JSON parses blow up on an empty string resolve(null); } else { try { let body: Buffer = Buffer.concat(chunks); let parsedBody: any = body.toString(); let decodedBody: Buffer = body; //Get the response content-encoding header value so we can decode before parsing const responseContentEncoding: string = response.headers["content-encoding"]?.toLowerCase() ?? ''; if (responseContentEncoding !== '') { decodedBody = decodeResponse(body, responseContentEncoding); } //Get the response content-type header value so we can apply different parsing methods const responseContentType: string = response.headers["content-type"]?.toLowerCase() ?? ''; if (responseContentType === '' || responseContentType.includes('json')) { parsedBody = JSON.parse(decodedBody.toString()); } else if (responseContentType.includes('xml')) { const parser = new XMLParser({ ignoreDeclaration: true, ignorePiTags: true, ignoreAttributes: false }); parsedBody = parser.parse(decodedBody.toString()); } else if (responseContentType.includes('image')) { parsedBody = decodedBody.toString('base64'); } if (response.statusCode !== undefined && response.statusCode >= 200 && response.statusCode < 300) { resolve(parsedBody); } else { reject(parsedBody); } } catch (e: any) { reject(e); } } }); response.on("error", function (e) { reject(e); }) }); } function setRequestBody(request: ClientRequest, data: any) { if (data) { const requestContentType: string = request.getHeader('content-type')?.toString() ?? ''; if (requestContentType === '' || requestContentType.includes('json')) { //We serialize using JSON.stringify as a default, or if JSON is specified request.write(JSON.stringify(data)); } else { // If another content-type is specified however, we respect the serialization provided request.write(data); } } } function decodeResponse(encodedBody: Buffer, contentEncodingHeader: string): Buffer { const contentEncodings: string[] = contentEncodingHeader.replace(/\s/g, '').split(','); let decodedBody: Buffer = encodedBody; contentEncodings.forEach(contentEncoding => { switch (contentEncoding) { case 'gzip': decodedBody = zlib.gunzipSync(decodedBody); break; case 'deflate': decodedBody = zlib.inflateSync(decodedBody); break; case 'br': decodedBody = zlib.brotliDecompressSync(decodedBody); break; } }); return decodedBody; } module.exports = { request, doGetRequest, doPostRequest, doPutRequest, doDeleteRequest }