UNPKG

@freemework/common

Version:

Common library of the Freemework Project.

312 lines 15.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.FHttpClient = exports.FHttpClientLoggerLabel = void 0; const http = __importStar(require("http")); const https = __importStar(require("https")); const content_type_1 = require("content-type"); const index_js_1 = require("../cancellation/index.js"); const index_js_2 = require("../exception/index.js"); const index_js_3 = require("../logging/index.js"); class FHttpClientLoggerLabel extends index_js_3.FLoggerLabel { static URL = new FHttpClientLoggerLabel("out.http.url", "Describes HTTP URL of output request"); static METHOD = new FHttpClientLoggerLabel("out.http.method", "Describes HTTP method of output request (like a GET, POST, etc.)"); } exports.FHttpClientLoggerLabel = FHttpClientLoggerLabel; class FHttpClient { _log; _logLevelHeaders; _logLevelBody; _proxyOpts; _sslOpts; _requestTimeout; constructor(opts) { this._log = opts !== undefined && opts.log !== undefined ? opts.log : index_js_3.FLogger.create(this.constructor.name); this._logLevelBody = opts !== undefined && opts.logLevelBody !== undefined ? opts.logLevelBody : index_js_3.FLoggerLevel.DEBUG; this._logLevelHeaders = opts !== undefined && opts.logLevelHeaders !== undefined ? opts.logLevelHeaders : index_js_3.FLoggerLevel.DEBUG; this._proxyOpts = opts !== undefined && opts.proxyOpts !== undefined ? opts.proxyOpts : null; this._sslOpts = opts !== undefined && opts.sslOpts !== undefined ? opts.sslOpts : null; this._requestTimeout = opts !== undefined && opts.timeout !== undefined ? opts.timeout : FHttpClient.DEFAULT_TIMEOUT; } async invoke(executionContext, { url, method, headers, body }) { executionContext = new index_js_3.FLoggerLabelsExecutionContext(executionContext, FHttpClientLoggerLabel.METHOD.value(method), FHttpClientLoggerLabel.URL.value(url.toString())); this._log.trace(executionContext, "Begin invoke"); try { return await new Promise((resolve, reject) => { const cancellationToken = index_js_1.FCancellationExecutionContext.of(executionContext).cancellationToken; let isConnectTimeout = false; let resolved = false; const errorHandler = (e) => { const ex = index_js_2.FException.wrapIfNeeded(e); if (!resolved) { resolved = true; const msg = isConnectTimeout ? "Connect Timeout" : `${method} ${url} failed with error: ${ex.message}. See innerException for details`; this._log.debug(executionContext, msg, ex); return reject(new FHttpClient.CommunicationError(url, method, headers !== undefined ? headers : {}, body !== undefined ? body : Buffer.alloc(0), msg, ex)); } }; const responseHandler = (response) => { const responseDataChunks = []; response.on("data", (chunk) => responseDataChunks.push(chunk)); response.on("error", errorHandler); response.on("end", () => { if (!resolved) { resolved = true; if (isConnectTimeout) { return reject(new FHttpClient.CommunicationError(url, method, headers !== undefined ? headers : {}, body !== undefined ? body : Buffer.alloc(0), "Connect Timeout")); } const respStatus = response.statusCode || 500; const respDescription = response.statusMessage || ""; const respHeaders = response.headers; const respBody = Buffer.concat(responseDataChunks); this._log.log(executionContext, this._logLevelHeaders, () => `Recv head: ${JSON.stringify({ respStatus, respDescription, respHeaders })}`); this._log.log(executionContext, this._logLevelBody, () => `Recv body: ${respBody.toString()}`); if (respStatus < 400) { return resolve({ statusCode: respStatus, statusDescription: respDescription, headers: respHeaders, body: respBody, }); } else { return reject(new FHttpClient.WebError(respStatus, respDescription, url, method, headers !== undefined ? headers : {}, body !== undefined ? body : Buffer.alloc(0), respHeaders, respBody)); } } }); }; try { cancellationToken.throwIfCancellationRequested(); // Will raise error, we want to reject this Promise exactly with cancellation exception. } catch (e) { return reject(e); } this._log.log(executionContext, this._logLevelHeaders, () => `Write head: ${JSON.stringify({ reqHeaders: headers })}`); const request = this.createClientRequest(executionContext, { url, method, headers: headers }, responseHandler); if (body !== undefined) { this._log.log(executionContext, this._logLevelBody, () => `Write body: ${body.toString()}`); request.write(body); } request.end(); request.on("error", errorHandler); request.on("close", () => request.removeListener("close", errorHandler)); // Prevent memory-leaks request.setTimeout(this._requestTimeout, () => { isConnectTimeout = true; request.destroy(); }); request.on("socket", socket => { // this will setup connect timeout socket.setTimeout(this._requestTimeout); // socket.on("timeout", () => { // isConnectTimeout = true; // request.abort(); // }); }); if (cancellationToken !== undefined) { const cb = () => { request.destroy(); if (!resolved) { resolved = true; try { cancellationToken.throwIfCancellationRequested(); // Should raise error // Guard for broken implementation of cancellationToken reject(new index_js_1.FCancellationException("Cancelled by user")); } catch (e) { reject(e); } } }; cancellationToken.addCancelListener(cb); request.on("close", () => cancellationToken.removeCancelListener(cb)); // Prevent memory-leaks } }); } finally { this._log.trace(executionContext, "End invoke"); } } createClientRequest(executionContext, { url, method, headers }, callback) { const proxyOpts = this._proxyOpts; if (proxyOpts && proxyOpts.type === "http") { const reqOpts = { protocol: "http:", host: proxyOpts.host, port: proxyOpts.port, path: url.href, method, headers: { Host: url.host, ...headers } }; this._log.trace(executionContext, () => `http.request(${JSON.stringify(reqOpts)})`); return http.request(reqOpts, callback); } else { const reqOpts = { protocol: url.protocol, host: url.hostname, port: url.port, path: url.pathname + url.search, method: method, headers: headers }; if (reqOpts.protocol === "https:") { const sslOpts = this._sslOpts; if (sslOpts) { if (sslOpts.ca) { reqOpts.ca = sslOpts.ca; } if (sslOpts.rejectUnauthorized !== undefined) { reqOpts.rejectUnauthorized = sslOpts.rejectUnauthorized; } if ("pfx" in sslOpts) { reqOpts.pfx = sslOpts.pfx; reqOpts.passphrase = sslOpts.passphrase; } else if ("cert" in sslOpts) { reqOpts.key = sslOpts.key; reqOpts.cert = sslOpts.cert; } } this._log.trace(executionContext, () => `https.request(${JSON.stringify(reqOpts)})`); return https.request(reqOpts, callback); } else { this._log.trace(executionContext, () => `http.request(${JSON.stringify(reqOpts)})`); return http.request(reqOpts, callback); } } } } exports.FHttpClient = FHttpClient; (function (FHttpClient) { FHttpClient.DEFAULT_TIMEOUT = 60000; let HttpMethod; (function (HttpMethod) { HttpMethod["CONNECT"] = "CONNECT"; HttpMethod["DELETE"] = "DELETE"; HttpMethod["HEAD"] = "HEAD"; HttpMethod["GET"] = "GET"; HttpMethod["OPTIONS"] = "OPTIONS"; HttpMethod["PATCH"] = "PATCH"; HttpMethod["POST"] = "POST"; HttpMethod["PUT"] = "PUT"; HttpMethod["TRACE"] = "TRACE"; })(HttpMethod = FHttpClient.HttpMethod || (FHttpClient.HttpMethod = {})); /** Base error type for WebClient */ class HttpClientError extends index_js_2.FException { _url; _method; _requestHeaders; _requestBody; constructor(url, method, requestHeaders, requestBody, errorMessage, innerException) { super(errorMessage, innerException); this._url = url; this._method = method; this._requestHeaders = requestHeaders; this._requestBody = requestBody; } get url() { return this._url; } get method() { return this._method; } get requestHeaders() { return this._requestHeaders; } get requestBody() { return this._requestBody; } get requestObject() { return parseJsonBody(this.requestBody, this.requestHeaders); } } FHttpClient.HttpClientError = HttpClientError; /** * WebError is a wrapper of HTTP responses with code 4xx/5xx */ class WebError extends HttpClientError { _statusCode; _statusDescription; _responseHeaders; _responseBody; constructor(statusCode, statusDescription, url, method, requestHeaders, requestBody, responseHeaders, responseBody, innerException) { super(url, method, requestHeaders, requestBody, `${statusCode} ${statusDescription}`, innerException); this._statusCode = statusCode; this._statusDescription = statusDescription; this._responseHeaders = responseHeaders; this._responseBody = responseBody; } get statusCode() { return this._statusCode; } get statusDescription() { return this._statusDescription; } get headers() { return this._responseHeaders; } get body() { return this._responseBody; } get object() { return parseJsonBody(this.body, this.headers); } } FHttpClient.WebError = WebError; /** * CommunicationError is a wrapper over underlying network errors. * Such a DNS lookup issues, TCP connection issues, etc... */ class CommunicationError extends HttpClientError { constructor(url, method, requestHeaders, requestBody, errorMessage, innerException) { super(url, method, requestHeaders, requestBody, errorMessage, innerException); } get code() { const innerException = this.innerException; if (innerException !== null && innerException instanceof index_js_2.FExceptionNativeErrorWrapper) { const error = innerException.nativeError; if ("code" in error) { const code = error.code; if (typeof (code) === "string") { return code; } } } return null; } } FHttpClient.CommunicationError = CommunicationError; })(FHttpClient || (exports.FHttpClient = FHttpClient = {})); function parseJsonBody(body, headers) { let contentType = null; if (headers !== null) { const contentTypeHeaderName = Object.keys(headers).find(header => header.toLowerCase() === "content-type"); if (contentTypeHeaderName !== undefined) { const headerValue = headers[contentTypeHeaderName]; if (typeof headerValue === "string") { contentType = (0, content_type_1.parse)(headerValue); } } } if (contentType === null || contentType.type !== "application/json" || ("charset" in contentType.parameters && contentType.parameters["charset"].toLowerCase() !== "utf-8")) { throw new index_js_2.FExceptionInvalidOperation("Wrong operation. The property available only for UTF-8 'application/json' content type."); } const friendlyBody = body instanceof Buffer ? body : Buffer.from(body); return JSON.parse(friendlyBody.toString("utf-8")); } //# sourceMappingURL=f_http_client.js.map