httpRequest.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. "use strict";
  2. var __importDefault = (this && this.__importDefault) || function (mod) {
  3. return (mod && mod.__esModule) ? mod : { "default": mod };
  4. };
  5. var __importStar = (this && this.__importStar) || function (mod) {
  6. if (mod && mod.__esModule) return mod;
  7. var result = {};
  8. if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
  9. result["default"] = mod;
  10. return result;
  11. };
  12. Object.defineProperty(exports, "__esModule", { value: true });
  13. const http_1 = __importDefault(require("http"));
  14. const tracing_1 = require("./tracing");
  15. const utils = __importStar(require("./utils"));
  16. const code_1 = require("../const/code");
  17. const symbol_1 = require("../const/symbol");
  18. const cloudbase_1 = require("../cloudbase");
  19. const request_1 = require("./request");
  20. const requestHook_1 = require("./requestHook");
  21. const wxCloudToken_1 = require("./wxCloudToken");
  22. const signature_nodejs_1 = require("@cloudbase/signature-nodejs");
  23. const url_1 = __importDefault(require("url"));
  24. // import { version } from '../../package.json'
  25. const secretManager_1 = __importDefault(require("./secretManager"));
  26. const { version } = require('../../package.json');
  27. const { E, second, processReturn, getServerInjectUrl } = utils;
  28. class Request {
  29. constructor(args) {
  30. this.urlPath = '/admin';
  31. this.defaultTimeout = 15000;
  32. this.timestamp = new Date().valueOf();
  33. this.tracingInfo = tracing_1.generateTracingInfo();
  34. this.slowWarnTimer = null;
  35. // 请求参数
  36. this.hooks = {};
  37. this.args = args;
  38. this.config = args.config;
  39. this.opts = args.opts || {};
  40. this.secretManager = new secretManager_1.default();
  41. }
  42. /**
  43. * 最终发送请求
  44. */
  45. async request() {
  46. // 校验密钥是否存在
  47. await this.validateSecretIdAndKey();
  48. // 构造请求参数
  49. const params = await this.makeParams();
  50. const opts = await this.makeReqOpts(params);
  51. const action = this.getAction();
  52. const key = {
  53. functions: 'function_name',
  54. database: 'collectionName',
  55. wx: 'apiName'
  56. }[action.split('.')[0]];
  57. const argopts = this.opts;
  58. const config = this.config;
  59. // 发请求时未找到有效环境字段
  60. if (!params.envName) {
  61. // 检查config中是否有设置
  62. if (config.envName) {
  63. return processReturn(config.throwOnCode, Object.assign({}, code_1.ERROR.INVALID_PARAM, { message: '未取到init 指定 env!' }));
  64. }
  65. else {
  66. console.warn(`当前未指定env,将默认使用第一个创建的环境!`);
  67. }
  68. }
  69. // 注意:必须初始化为 null
  70. let retryOptions = null;
  71. if (argopts.retryOptions) {
  72. retryOptions = argopts.retryOptions;
  73. }
  74. else if (config.retries && typeof config.retries === 'number') {
  75. retryOptions = { retries: config.retries };
  76. }
  77. return request_1.extraRequest(opts, {
  78. debug: config.debug,
  79. op: `${action}:${this.args.params[key]}@${params.envName}`,
  80. seqId: this.getSeqId(),
  81. retryOptions: retryOptions,
  82. timingsMeasurerOptions: config.timingsMeasurerOptions || {}
  83. }).then((response) => {
  84. this.slowWarnTimer && clearTimeout(this.slowWarnTimer);
  85. const { body } = response;
  86. if (response.statusCode === 200) {
  87. let res;
  88. try {
  89. res = typeof body === 'string' ? JSON.parse(body) : body;
  90. if (this.hooks && this.hooks.handleData) {
  91. res = this.hooks.handleData(res, null, response, body);
  92. }
  93. }
  94. catch (e) {
  95. res = body;
  96. }
  97. return res;
  98. }
  99. else {
  100. const e = E({
  101. code: response.statusCode,
  102. message: ` ${response.statusCode} ${http_1.default.STATUS_CODES[response.statusCode]} | [${opts.url}]`
  103. });
  104. throw e;
  105. }
  106. });
  107. }
  108. setHooks(hooks) {
  109. Object.assign(this.hooks, hooks);
  110. }
  111. getSeqId() {
  112. return this.tracingInfo.seqId;
  113. }
  114. /**
  115. * 接口action
  116. */
  117. getAction() {
  118. const { params } = this.args;
  119. const { action } = params;
  120. return action;
  121. }
  122. /**
  123. * 设置超时warning
  124. */
  125. setSlowWarning(timeout) {
  126. const action = this.getAction();
  127. const { seqId } = this.tracingInfo;
  128. this.slowWarnTimer = setTimeout(() => {
  129. /* istanbul ignore next */
  130. const msg = `Your current request ${action ||
  131. ''} is longer than 3s, it may be due to the network or your query performance | [${seqId}]`;
  132. /* istanbul ignore next */
  133. console.warn(msg);
  134. }, timeout);
  135. }
  136. /**
  137. * 构造params
  138. */
  139. async makeParams() {
  140. const { TCB_SESSIONTOKEN, TCB_ENV, SCF_NAMESPACE } = cloudbase_1.CloudBase.getCloudbaseContext();
  141. const args = this.args;
  142. const opts = this.opts;
  143. const config = this.config;
  144. const { eventId } = this.tracingInfo;
  145. const crossAuthorizationData = opts.getCrossAccountInfo && (await opts.getCrossAccountInfo()).authorization;
  146. const { wxCloudApiToken, wxCloudbaseAccesstoken } = wxCloudToken_1.getWxCloudToken();
  147. const params = Object.assign({}, args.params, { envName: config.envName, eventId,
  148. wxCloudApiToken,
  149. wxCloudbaseAccesstoken, tcb_sessionToken: TCB_SESSIONTOKEN || '', sessionToken: config.sessionToken, crossAuthorizationToken: crossAuthorizationData
  150. ? Buffer.from(JSON.stringify(crossAuthorizationData)).toString('base64')
  151. : '' });
  152. // 取当前云函数环境时,替换为云函数下环境变量
  153. if (params.envName === symbol_1.SYMBOL_CURRENT_ENV) {
  154. params.envName = TCB_ENV || SCF_NAMESPACE;
  155. }
  156. // 过滤value undefined
  157. utils.filterUndefined(params);
  158. return params;
  159. }
  160. /**
  161. * 构造请求项
  162. */
  163. async makeReqOpts(params) {
  164. const config = this.config;
  165. const args = this.args;
  166. const isInternal = await utils.checkIsInternalAsync();
  167. const url = this.getUrl({ isInternal });
  168. const method = this.getMethod();
  169. const opts = {
  170. url: url,
  171. method,
  172. // 先取模块的timeout,没有则取sdk的timeout,还没有就使用默认值
  173. // timeout: args.timeout || config.timeout || 15000,
  174. timeout: this.getTimeout(),
  175. // 优先取config,其次取模块,最后取默认
  176. headers: await this.getHeaders(url),
  177. proxy: config.proxy
  178. };
  179. opts.keepalive = config.keepalive === true;
  180. if (args.method === 'post') {
  181. if (args.isFormData) {
  182. opts.formData = params;
  183. opts.encoding = null;
  184. }
  185. else {
  186. opts.body = params;
  187. opts.json = true;
  188. }
  189. }
  190. else {
  191. /* istanbul ignore next */
  192. opts.qs = params;
  193. }
  194. return opts;
  195. }
  196. /**
  197. * 协议
  198. */
  199. getProtocol() {
  200. return this.config.isHttp === true ? 'http' : 'https';
  201. }
  202. /**
  203. * 请求方法
  204. */
  205. getMethod() {
  206. return this.args.method || 'get';
  207. }
  208. /**
  209. * 超时时间
  210. */
  211. getTimeout() {
  212. const { opts = {} } = this.args;
  213. // timeout优先级 自定义接口timeout > config配置timeout > 默认timeout
  214. return opts.timeout || this.config.timeout || this.defaultTimeout;
  215. }
  216. /**
  217. * 校验密钥和token是否存在
  218. */
  219. async validateSecretIdAndKey() {
  220. const { TENCENTCLOUD_SECRETID, TENCENTCLOUD_SECRETKEY, TENCENTCLOUD_SESSIONTOKEN } = cloudbase_1.CloudBase.getCloudbaseContext(); // 放在此处是为了兼容本地环境下读环境变量
  221. const isInSCF = utils.checkIsInScf();
  222. const isInContainer = utils.checkIsInEks();
  223. let opts = this.opts;
  224. let getCrossAccountInfo = opts.getCrossAccountInfo || this.config.getCrossAccountInfo;
  225. /* istanbul ignore if */
  226. if (getCrossAccountInfo) {
  227. let crossAccountInfo = await getCrossAccountInfo();
  228. let { credential } = await getCrossAccountInfo();
  229. let { secretId, secretKey, token } = credential || {};
  230. this.config = Object.assign({}, this.config, { secretId,
  231. secretKey, sessionToken: token });
  232. this.opts.getCrossAccountInfo = () => Promise.resolve(crossAccountInfo);
  233. if (!this.config.secretId || !this.config.secretKey) {
  234. throw E(Object.assign({}, code_1.ERROR.INVALID_PARAM, { message: 'missing secretId or secretKey of tencent cloud' }));
  235. }
  236. }
  237. else {
  238. const { secretId, secretKey } = this.config;
  239. if (!secretId || !secretKey) {
  240. /* istanbul ignore if */
  241. if (isInContainer) {
  242. // 这种情况有可能是在容器内,此时尝试拉取临时
  243. const tmpSecret = await this.secretManager.getTmpSecret();
  244. this.config = Object.assign({}, this.config, { secretId: tmpSecret.id, secretKey: tmpSecret.key, sessionToken: tmpSecret.token });
  245. return;
  246. }
  247. if (!TENCENTCLOUD_SECRETID || !TENCENTCLOUD_SECRETKEY) {
  248. if (isInSCF) {
  249. throw E(Object.assign({}, code_1.ERROR.INVALID_PARAM, { message: 'missing authoration key, redeploy the function' }));
  250. }
  251. else {
  252. throw E(Object.assign({}, code_1.ERROR.INVALID_PARAM, { message: 'missing secretId or secretKey of tencent cloud' }));
  253. }
  254. }
  255. else {
  256. this.config = Object.assign({}, this.config, { secretId: TENCENTCLOUD_SECRETID, secretKey: TENCENTCLOUD_SECRETKEY, sessionToken: TENCENTCLOUD_SESSIONTOKEN });
  257. }
  258. }
  259. }
  260. }
  261. /**
  262. *
  263. * 获取headers 此函数中设置authorization
  264. */
  265. async getHeaders(url) {
  266. const config = this.config;
  267. const { secretId, secretKey } = config;
  268. const args = this.args;
  269. const method = this.getMethod();
  270. const { TCB_SOURCE } = cloudbase_1.CloudBase.getCloudbaseContext();
  271. // Note: 云函数被调用时可能调用端未传递 SOURCE,TCB_SOURCE 可能为空
  272. const SOURCE = utils.checkIsInScf() ? `${TCB_SOURCE || ''},scf` : ',not_scf';
  273. let requiredHeaders = {
  274. 'User-Agent': `tcb-node-sdk/${version}`,
  275. 'x-tcb-source': SOURCE,
  276. 'x-client-timestamp': this.timestamp,
  277. 'X-SDK-Version': `tcb-node-sdk/${version}`,
  278. Host: url_1.default.parse(url).host
  279. };
  280. if (config.version) {
  281. requiredHeaders['X-SDK-Version'] = config.version;
  282. }
  283. if (this.tracingInfo.trace) {
  284. requiredHeaders['x-tcb-tracelog'] = this.tracingInfo.trace;
  285. }
  286. const region = this.config.region || process.env.TENCENTCLOUD_REGION || '';
  287. if (region) {
  288. requiredHeaders['X-TCB-Region'] = region;
  289. }
  290. requiredHeaders = Object.assign({}, config.headers, args.headers, requiredHeaders);
  291. const { authorization, timestamp } = signature_nodejs_1.sign({
  292. secretId: secretId,
  293. secretKey: secretKey,
  294. method: method,
  295. url: url,
  296. params: await this.makeParams(),
  297. headers: requiredHeaders,
  298. withSignedParams: true,
  299. timestamp: second() - 1
  300. });
  301. requiredHeaders['Authorization'] = authorization;
  302. requiredHeaders['X-Signature-Expires'] = 600;
  303. requiredHeaders['X-Timestamp'] = timestamp;
  304. return Object.assign({}, requiredHeaders);
  305. }
  306. /**
  307. * 获取url
  308. * @param action
  309. */
  310. /* eslint-disable-next-line complexity */
  311. getUrl(options = {
  312. isInternal: false
  313. }) {
  314. if (utils.checkIsInScf()) {
  315. // 云函数环境下,应该包含以下环境变量,如果没有,后续逻辑可能会有问题
  316. if (!process.env.TENCENTCLOUD_REGION) {
  317. console.error('[ERROR] missing `TENCENTCLOUD_REGION` environment');
  318. }
  319. if (!process.env.SCF_NAMESPACE) {
  320. console.error('[ERROR] missing `SCF_NAMESPACE` environment');
  321. }
  322. }
  323. const { TCB_ENV, SCF_NAMESPACE } = cloudbase_1.CloudBase.getCloudbaseContext();
  324. // 优先级:用户配置 > 环境变量
  325. const region = this.config.region || process.env.TENCENTCLOUD_REGION || '';
  326. // 有地域信息则访问地域级别域名,无地域信息则访问默认域名,默认域名固定解析到上海地域保持兼容
  327. const internetRegionEndpoint = region
  328. ? `${region}.tcb-api.tencentcloudapi.com`
  329. : `tcb-api.tencentcloudapi.com`;
  330. const internalRegionEndpoint = region
  331. ? `internal.${region}.tcb-api.tencentcloudapi.com`
  332. : `internal.tcb-api.tencentcloudapi.com`;
  333. // 同地域走内网,跨地域走公网
  334. const isSameRegionVisit = this.config.region
  335. ? this.config.region === process.env.TENCENTCLOUD_REGION
  336. : true;
  337. const endpoint = isSameRegionVisit && (options.isInternal)
  338. ? internalRegionEndpoint
  339. : internetRegionEndpoint;
  340. const envName = this.config.envName || '';
  341. const currEnv = TCB_ENV || SCF_NAMESPACE || '';
  342. // 注意:特殊环境ID不能拼在请求地址的域名中,所以这里需要特殊处理
  343. const envId = envName === symbol_1.SYMBOL_CURRENT_ENV || utils.isPageModuleName(envName)
  344. ? currEnv
  345. : envName;
  346. const envEndpoint = utils.isValidEnvFormat(envId) ? `${envId}.${endpoint}` : endpoint;
  347. const protocol = options.isInternal ? 'http' : this.getProtocol();
  348. // 注意:云函数环境下有地域信息,云应用环境下不确定是否有,如果没有,用户必须显式的传入
  349. const defaultUrl = `${protocol}://${envEndpoint}${this.urlPath}`;
  350. const { eventId, seqId } = this.tracingInfo;
  351. const { serviceUrl } = this.config;
  352. const serverInjectUrl = getServerInjectUrl();
  353. const url = serviceUrl || serverInjectUrl || defaultUrl;
  354. const qs = cloudbase_1.CloudBase.scfContext
  355. ? `&eventId=${eventId}&seqId=${seqId}&scfRequestId=${cloudbase_1.CloudBase.scfContext.requestId}`
  356. : `&eventId=${eventId}&seqId=${seqId}`;
  357. return url.includes('?') ? `${url}${qs}` : `${url}?${qs}`;
  358. }
  359. }
  360. exports.Request = Request;
  361. // 业务逻辑都放在这里处理
  362. exports.default = async (args) => {
  363. const req = new Request(args);
  364. const config = args.config;
  365. const { action } = args.params;
  366. if (action === 'wx.openApi' || action === 'wx.wxPayApi') {
  367. req.setHooks({ handleData: requestHook_1.handleWxOpenApiData });
  368. }
  369. if (action.startsWith('database')) {
  370. req.setSlowWarning(3000);
  371. }
  372. try {
  373. const res = await req.request();
  374. // 检查res是否为return {code, message}回包
  375. if (res && res.code) {
  376. // 判断是否设置config._returnCodeByThrow = false
  377. return processReturn(config.throwOnCode, res);
  378. }
  379. return res;
  380. }
  381. finally {
  382. //
  383. }
  384. };