UNPKG

@cloudbase/node-sdk

Version:

tencent cloud base server sdk for node.js

238 lines (237 loc) 8.72 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 (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.request = void 0; const http_1 = __importDefault(require("http")); const https_1 = __importDefault(require("https")); const agentkeepalive_1 = __importStar(require("agentkeepalive")); const https_proxy_agent_1 = require("https-proxy-agent"); const http_proxy_agent_1 = require("http-proxy-agent"); const form_data_1 = __importDefault(require("form-data")); const kAgentCache = new Map(); /** * selectAgent * * 注意:当前不支持 keepalive & proxy 同时配置,如果同时配置,proxy 优先级更高 * * @param url * @param options * @returns */ function selectAgent(url, options) { // 开 keepalive 或 proxy 才需要 agent if (!options.keepalive && !options.proxy) { return null; } const isHttps = url.startsWith('https'); const cacheKey = `protocol=${isHttps ? 'https' : 'http'}timeout=${options.timeout}|keepalive${options.keepalive}|proxy=${options.proxy}`; if (kAgentCache && kAgentCache.has(cacheKey)) { return kAgentCache.get(cacheKey); } let agent = isHttps ? https_1.default.globalAgent : http_1.default.globalAgent; if (options.keepalive) { const keepAliveOpts = { keepAliveMsecs: 3000, maxSockets: 100, maxFreeSockets: 10, freeSocketTimeout: 4800, // timeout: options.timeout, socketActiveTTL: null }; agent = isHttps ? new agentkeepalive_1.HttpsAgent(Object.assign({}, keepAliveOpts)) : new agentkeepalive_1.default(Object.assign({}, keepAliveOpts)); } // 当前需兼容 node.js 12,http(s) proxy agent 最高版本为5,不支持传入 agent // 副作用:有 proxy 时,指定 keepalive 无效。由于 proxy 一般调试使用,可以接受 if (options.proxy) { const { protocol, hostname, port } = new URL(options.proxy); agent = isHttps ? new https_proxy_agent_1.HttpsProxyAgent({ protocol, host: hostname, port: Number(port), timeout: options.timeout }) : new http_proxy_agent_1.HttpProxyAgent({ protocol, host: hostname, port: Number(port), timeout: options.timeout }); } if (kAgentCache && agent) { kAgentCache.set(cacheKey, agent); } return agent; } function buildHttpRequestInfo(opts) { // NOTE: 仅某些 method 携带 body 这里仅简单处理 if (opts.formData) { const formdata = new form_data_1.default(); for (const key in opts.formData) { if (Object.prototype.hasOwnProperty.call(opts.formData, key)) { formdata.append(key, opts.formData[key]); } } return { headers: formdata.getHeaders(), body: formdata.getBuffer() }; } else { if (opts.body === undefined || opts.body === null) { return { headers: {} }; } const body = JSON.stringify(opts.body); return { headers: { 'content-length': Buffer.byteLength(body, 'utf8') }, body }; } } async function onResponse(res, { encoding, type = 'json' }) { if (type === 'stream') { return await Promise.resolve(undefined); } if (encoding) { res.setEncoding(encoding); } return await new Promise((resolve, reject) => { const bufs = []; res.on('data', (chunk) => { bufs.push(chunk); }); res.on('end', () => { const buf = Buffer.concat(bufs); if (type === 'json') { try { if (buf.byteLength === 0) { resolve(undefined); return; } resolve(JSON.parse(buf.toString())); } catch (e) { reject(e); } } resolve(buf); }); res.on('error', (err) => { reject(err); }); }); } function onTimeout(req, cb) { let hasConnected = false; req.once('socket', (socket) => { // NOTE: reusedSocket 为 true 时,不会触发 connect 事件 if (req.reusedSocket) { hasConnected = true; } else { socket.once('connect', () => { hasConnected = true; }); } }); req.on('timeout', () => { // request.reusedSocket // https://nodejs.org/api/net.html#socketconnecting // code 遵循 request 库定义: // ·ETIMEDOUT:connection timeouts,建立连接时发生超时 // ·ESOCKETTIMEDOUT:read timeouts,已经成功连接到服务器,等待响应超时 // https://github.com/request/request#timeouts const err = new Error(hasConnected ? 'request timeout' : 'connect timeout'); err.code = hasConnected ? 'ESOCKETTIMEDOUT' : 'ETIMEDOUT'; err.reusedSocket = req.reusedSocket; err.hasConnected = hasConnected; err.connecting = req.socket.connecting; err.url = `${req.protocol}://${req.host}${req.path}`; cb(err); }); } function request(opts, cb) { var _a; const times = opts.times || 1; const options = { method: opts.method, headers: opts.headers, timeout: opts.timeout || 1 }; const { headers, body } = buildHttpRequestInfo(opts); options.headers = Object.assign(Object.assign({}, options.headers), headers); options.agent = options.agent ? options.agent : selectAgent(opts.url, { timeout: opts.timeout, keepalive: opts.keepalive, proxy: opts.proxy }); const isHttps = (_a = opts.url) === null || _a === void 0 ? void 0 : _a.startsWith('https'); const req = (isHttps ? https_1.default : http_1.default).request(opts.url, options, (res) => { onResponse(res, { encoding: opts.encoding, type: opts.json ? 'json' : opts.type }) .then((body) => { cb(null, res, body); }) .catch((err) => { cb(err); }); }); req.on('abort', () => { cb(new Error('request aborted by client')); }); req.on('error', (err) => { if (err && opts.debug) { console.warn(`[TCB][RequestTimgings][keepalive:${opts.keepalive}][reusedSocket:${req === null || req === void 0 ? void 0 : req.reusedSocket}][code:${err.code}][message:${err.message}]${opts.url}`); } if ((err === null || err === void 0 ? void 0 : err.code) === 'ECONNRESET' && (req === null || req === void 0 ? void 0 : req.reusedSocket) && opts.keepalive && opts.times >= 0) { return request(Object.assign(Object.assign({}, opts), { times: times - 1 }), cb); } cb(err); }); if (typeof opts.timeout === 'number' && opts.timeout >= 0) { onTimeout(req, cb); req.setTimeout(opts.timeout); } // NOTE: 未传 body 时,不调用 write&end 方法,由外部调用,通常是 pipe 调用 if (body) { req.write(body); req.end(); } else { // 如果显式指明 noBody 则直接调用 end if (opts.noBody) { req.end(); } } // NOTE: http(s).request 需手动调用 end 方法 if (options.method.toLowerCase() === 'get') { req.end(); } return req; } exports.request = request;