@cloudbase/node-sdk
Version:
tencent cloud base server sdk for node.js
238 lines (237 loc) • 8.72 kB
JavaScript
;
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;