httpRequest.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  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 getWxCloudApiToken_1 = require("./getWxCloudApiToken");
  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. let crossAuthorizationData = opts.getCrossAccountInfo && (await opts.getCrossAccountInfo()).authorization;
  146. const params = Object.assign({}, args.params, { envName: config.envName, eventId,
  147. // wxCloudApiToken: process.env.WX_API_TOKEN || '',
  148. wxCloudApiToken: getWxCloudApiToken_1.getWxCloudApiToken(),
  149. // 对应服务端 wxCloudSessionToken
  150. tcb_sessionToken: TCB_SESSIONTOKEN || '', sessionToken: config.sessionToken, sdk_version: version, crossAuthorizationToken: crossAuthorizationData
  151. ? Buffer.from(JSON.stringify(crossAuthorizationData)).toString('base64')
  152. : '' });
  153. // 取当前云函数环境时,替换为云函数下环境变量
  154. if (params.envName === symbol_1.SYMBOL_CURRENT_ENV) {
  155. params.envName = TCB_ENV || SCF_NAMESPACE;
  156. }
  157. // 过滤value undefined
  158. utils.filterUndefined(params);
  159. return params;
  160. }
  161. /**
  162. * 构造请求项
  163. */
  164. async makeReqOpts(params) {
  165. const config = this.config;
  166. const args = this.args;
  167. const url = this.getUrl();
  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(params),
  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.checkIsInContainer();
  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(params) {
  266. let { TCB_SOURCE } = cloudbase_1.CloudBase.getCloudbaseContext();
  267. const config = this.config;
  268. const { secretId, secretKey } = config;
  269. const args = this.args;
  270. const method = this.getMethod();
  271. const isInSCF = utils.checkIsInScf();
  272. // Note: 云函数被调用时可能调用端未传递 SOURCE,TCB_SOURCE 可能为空
  273. TCB_SOURCE = TCB_SOURCE || '';
  274. const SOURCE = isInSCF ? `${TCB_SOURCE},scf` : ',not_scf';
  275. const url = this.getUrl();
  276. // 默认
  277. let requiredHeaders = {
  278. 'User-Agent': `tcb-node-sdk/${version}`,
  279. 'x-tcb-source': SOURCE,
  280. 'x-client-timestamp': this.timestamp,
  281. 'X-SDK-Version': `tcb-node-sdk/${version}`,
  282. Host: url_1.default.parse(url).host
  283. };
  284. if (config.version) {
  285. requiredHeaders['X-SDK-Version'] = config.version;
  286. }
  287. if (this.tracingInfo.trace) {
  288. requiredHeaders['x-tcb-tracelog'] = this.tracingInfo.trace;
  289. }
  290. const region = this.config.region || process.env.TENCENTCLOUD_REGION || '';
  291. if (region) {
  292. requiredHeaders['X-TCB-Region'] = region;
  293. }
  294. requiredHeaders = Object.assign({}, config.headers, args.headers, requiredHeaders);
  295. const { authorization, timestamp } = signature_nodejs_1.sign({
  296. secretId: secretId,
  297. secretKey: secretKey,
  298. method: method,
  299. url: url,
  300. params: await this.makeParams(),
  301. headers: requiredHeaders,
  302. withSignedParams: true,
  303. timestamp: second() - 1
  304. });
  305. requiredHeaders['Authorization'] = authorization;
  306. requiredHeaders['X-Signature-Expires'] = 600;
  307. requiredHeaders['X-Timestamp'] = timestamp;
  308. return Object.assign({}, requiredHeaders);
  309. }
  310. /**
  311. * 获取url
  312. * @param action
  313. */
  314. /* eslint-disable-next-line complexity */
  315. getUrl() {
  316. const isInSCF = utils.checkIsInScf();
  317. const isInContainer = utils.checkIsInContainer();
  318. const { eventId, seqId } = this.tracingInfo;
  319. const { serviceUrl } = this.config;
  320. const serverInjectUrl = getServerInjectUrl();
  321. if (isInSCF) {
  322. // 云函数环境下,应该包含以下环境变量,如果没有,后续逻辑可能会有问题
  323. if (!process.env.TENCENTCLOUD_REGION) {
  324. console.error('[ERROR] missing `TENCENTCLOUD_REGION` environment');
  325. }
  326. if (!process.env.SCF_NAMESPACE) {
  327. console.error('[ERROR] missing `SCF_NAMESPACE` environment');
  328. }
  329. }
  330. const { TCB_ENV, SCF_NAMESPACE } = cloudbase_1.CloudBase.getCloudbaseContext();
  331. // 优先级:用户配置 > 环境变量
  332. const region = this.config.region || process.env.TENCENTCLOUD_REGION || '';
  333. const envId = this.config.envName === symbol_1.SYMBOL_CURRENT_ENV
  334. ? TCB_ENV || SCF_NAMESPACE
  335. : this.config.envName || '';
  336. // 有地域信息则访问地域级别域名,无地域信息则访问默认域名,默认域名固定解析到上海地域保持兼容
  337. const internetRegionEndpoint = region
  338. ? `${region}.tcb-api.tencentcloudapi.com`
  339. : `tcb-api.tencentcloudapi.com`;
  340. const internalRegionEndpoint = region
  341. ? `internal.${region}.tcb-api.tencentcloudapi.com`
  342. : `internal.tcb-api.tencentcloudapi.com`;
  343. // 同地域走内网,跨地域走公网
  344. const isSameRegionVisit = this.config.region
  345. ? this.config.region === process.env.TENCENTCLOUD_REGION
  346. : true;
  347. const endpoint = isSameRegionVisit && (isInSCF || isInContainer)
  348. ? internalRegionEndpoint
  349. : internetRegionEndpoint;
  350. const envEndpoint = envId ? `${envId}.${endpoint}` : endpoint;
  351. const protocol = isInSCF ? 'http' : this.getProtocol();
  352. // 注意:云函数环境下有地域信息,云应用环境下不确定是否有,如果没有,用户必须显式的传入
  353. const defaultUrl = `${protocol}://${envEndpoint}${this.urlPath}`;
  354. const url = serviceUrl || serverInjectUrl || defaultUrl;
  355. const qs = cloudbase_1.CloudBase.scfContext
  356. ? `&eventId=${eventId}&seqId=${seqId}&scfRequestId=${cloudbase_1.CloudBase.scfContext.requestId}`
  357. : `&eventId=${eventId}&seqId=${seqId}`;
  358. return url.includes('?') ? `${url}${qs}` : `${url}?${qs}`;
  359. }
  360. }
  361. exports.Request = Request;
  362. // 业务逻辑都放在这里处理
  363. exports.default = async (args) => {
  364. const req = new Request(args);
  365. const config = args.config;
  366. const { action } = args.params;
  367. if (action === 'wx.openApi' || action === 'wx.wxPayApi') {
  368. req.setHooks({ handleData: requestHook_1.handleWxOpenApiData });
  369. }
  370. if (action.startsWith('database')) {
  371. req.setSlowWarning(3000);
  372. }
  373. try {
  374. const res = await req.request();
  375. // 检查res是否为return {code, message}回包
  376. if (res && res.code) {
  377. // 判断是否设置config._returnCodeByThrow = false
  378. return processReturn(config.throwOnCode, res);
  379. }
  380. return res;
  381. }
  382. finally {
  383. //
  384. }
  385. };