UNPKG

@cloudbase/node-sdk

Version:

tencent cloud base server sdk for node.js

393 lines (342 loc) 12.2 kB
import http from 'http' import { generateTracingInfo } from './tracing' import * as utils from './utils' import { ICloudBaseConfig, IRequestInfo, ICustomParam, IReqOpts, IReqHooks } from '../type/index' import { ERROR } from '../const/code' import { SYMBOL_CURRENT_ENV } from '../const/symbol' import { CloudBase } from '../cloudbase' import { extraRequest } from './request' import { handleWxOpenApiData } from './requestHook' import { getWxCloudApiToken } from './getWxCloudApiToken' import { sign } from '@cloudbase/signature-nodejs' import URL from 'url' // import { version } from '../../package.json' const { version } = require('../../package.json') const { E, second, processReturn, getServerInjectUrl } = utils export class Request { private args: IRequestInfo private config: ICloudBaseConfig private defaultEndPoint = 'tcb-admin.tencentcloudapi.com' private inScfHost = 'tcb-admin.tencentyun.com' // private openApiHost: string = 'tcb-open.tencentcloudapi.com' private urlPath = '/admin' private defaultTimeout = 15000 private timestamp: number = new Date().valueOf() private tracingInfo: { eventId: string seqId: string } = generateTracingInfo() private slowWarnTimer: NodeJS.Timer = null // 请求参数 private params: { [key: string]: any } = {} private hooks: IReqHooks = {} public constructor(args: IRequestInfo) { this.args = args this.config = args.config // 密钥检查及设置 this.initSecret() this.params = this.makeParams() } /** * 最终发送请求 */ public request(): Promise<any> { const action = this.getAction() const key = { functions: 'function_name', database: 'collectionName', wx: 'apiName' }[action.split('.')[0]] const argopts: any = this.args.opts || {} const config = this.config const opts = this.makeReqOpts() // 发请求时未找到有效环境字段 if (!this.params.envName) { // 检查config中是否有设置 if (config.envName) { return processReturn(config.throwOnCode, { ...ERROR.INVALID_PARAM, message: '未取到init 指定 env!' }) } else { console.warn(`当前未指定env,将默认使用第一个创建的环境!`) } } // 注意:必须初始化为 null let retryOptions: any = null if (argopts.retryOptions) { retryOptions = argopts.retryOptions } else if (config.retries && typeof config.retries === 'number') { retryOptions = { retries: config.retries } } return extraRequest(opts, { debug: config.debug, op: `${action}:${this.args.params[key]}@${this.params.envName}`, seqId: this.getSeqId(), retryOptions: retryOptions, timingsMeasurerOptions: config.timingsMeasurerOptions || {} }).then((response: any) => { this.slowWarnTimer && clearTimeout(this.slowWarnTimer) const { body } = response if (response.statusCode === 200) { let res try { res = typeof body === 'string' ? JSON.parse(body) : body if (this.hooks && this.hooks.handleData) { res = this.hooks.handleData(res, null, response, body) } } catch (e) { res = body } return res } else { const e = E({ code: response.statusCode, message: ` ${response.statusCode} ${ http.STATUS_CODES[response.statusCode] } | [${opts.url}]` }) throw e } }) } public setHooks(hooks: IReqHooks) { Object.assign(this.hooks, hooks) } public getSeqId(): string { return this.tracingInfo.seqId } /** * 接口action */ public getAction(): string { const { params } = this.args const { action } = params return action } /** * 设置超时warning */ public setSlowWarning(timeout: number) { const action = this.getAction() const { seqId } = this.tracingInfo this.slowWarnTimer = setTimeout(() => { const msg = `Your current request ${action || ''} is longer than 3s, it may be due to the network or your query performance | [${seqId}]` console.warn(msg) }, timeout) } /** * 构造params */ public makeParams(): any { const { TCB_SESSIONTOKEN, TCB_ENV, SCF_NAMESPACE } = CloudBase.getCloudbaseContext() const args = this.args const config = this.config const { eventId } = this.tracingInfo const params: ICustomParam = { ...args.params, envName: config.envName, eventId, // wxCloudApiToken: process.env.WX_API_TOKEN || '', wxCloudApiToken: getWxCloudApiToken(), // 对应服务端 wxCloudSessionToken tcb_sessionToken: TCB_SESSIONTOKEN || '', sessionToken: config.sessionToken, sdk_version: version // todo 可去掉该参数 } // 取当前云函数环境时,替换为云函数下环境变量 if (params.envName === SYMBOL_CURRENT_ENV) { params.envName = TCB_ENV || SCF_NAMESPACE } // 过滤value undefined utils.filterUndefined(params) return params } /** * 构造请求项 */ public makeReqOpts(): IReqOpts { const config = this.config const args = this.args const url = this.getUrl() const method = this.getMethod() const params = this.params const opts: IReqOpts = { url, method, // 先取模块的timeout,没有则取sdk的timeout,还没有就使用默认值 // timeout: args.timeout || config.timeout || 15000, timeout: this.getTimeout(), // todo 细化到api维度 timeout // 优先取config,其次取模块,最后取默认 headers: this.getHeaders(), proxy: config.proxy } if (config.forever === true) { opts.forever = true } if (args.method === 'post') { if (args.isFormData) { opts.formData = params opts.encoding = null } else { opts.body = params opts.json = true } } else { opts.qs = params } return opts } /** * 协议 */ private getProtocol(): string { return this.config.isHttp === true ? 'http' : 'https' } /** * 请求方法 */ private getMethod(): string { return this.args.method || 'get' } /** * 超时时间 */ private getTimeout(): number { const { opts = {} } = this.args // timeout优先级 自定义接口timeout > config配置timeout > 默认timeout return opts.timeout || this.config.timeout || this.defaultTimeout } /** * 校验密钥和token是否存在 */ private initSecret(): void { const { TENCENTCLOUD_SECRETID, TENCENTCLOUD_SECRETKEY, TENCENTCLOUD_SESSIONTOKEN } = CloudBase.getCloudbaseContext() const isInSCF = utils.checkIsInScf() const { secretId, secretKey } = this.config if (!secretId || !secretKey) { // 用户init未传入密钥对,读process.env const envSecretId = TENCENTCLOUD_SECRETID const envSecretKey = TENCENTCLOUD_SECRETKEY const sessionToken = TENCENTCLOUD_SESSIONTOKEN if (!envSecretId || !envSecretKey) { if (isInSCF) { throw E({ ...ERROR.INVALID_PARAM, message: 'missing authoration key, redeploy the function' }) } else { throw E({ ...ERROR.INVALID_PARAM, message: 'missing secretId or secretKey of tencent cloud' }) } } else { this.config = { ...this.config, secretId: envSecretId, secretKey: envSecretKey, sessionToken: sessionToken } return } } } /** * * 获取headers 此函数中设置authorization */ private getHeaders(): any { let { TCB_SOURCE } = CloudBase.getCloudbaseContext() const config = this.config const { secretId, secretKey } = config const args = this.args const method = this.getMethod() const isInSCF = utils.checkIsInScf() // Note: 云函数被调用时可能调用端未传递 SOURCE,TCB_SOURCE 可能为空 TCB_SOURCE = TCB_SOURCE || '' const SOURCE = isInSCF ? `${TCB_SOURCE},scf` : ',not_scf' const url = this.getUrl() // 默认 let requiredHeaders = { 'User-Agent': `tcb-node-sdk/${version}`, 'x-tcb-source': SOURCE, 'x-client-timestamp': this.timestamp, 'X-SDK-Version': `tcb-node-sdk/${version}`, Host: URL.parse(url).host } if (config.version) { requiredHeaders['X-SDK-Version'] = config.version } requiredHeaders = { ...config.headers, ...args.headers, ...requiredHeaders } const { authorization, timestamp } = sign({ secretId: secretId, secretKey: secretKey, method: method, url: url, params: this.params, headers: requiredHeaders, withSignedParams: true, timestamp: second() - 1 }) requiredHeaders['Authorization'] = authorization requiredHeaders['X-Signature-Expires'] = 600 requiredHeaders['X-Timestamp'] = timestamp return { ...requiredHeaders } } /** * 获取url * @param action */ private getUrl(): string { const protocol = this.getProtocol() const isInSCF = utils.checkIsInScf() const { eventId, seqId } = this.tracingInfo const { customApiUrl } = this.args const { serviceUrl } = this.config const serverInjectUrl = getServerInjectUrl() const defaultUrl = isInSCF ? `http://${this.inScfHost}${this.urlPath}` : `${protocol}://${this.defaultEndPoint}${this.urlPath}` let url = serviceUrl || serverInjectUrl || customApiUrl || defaultUrl let urlQueryStr = `&eventId=${eventId}&seqId=${seqId}` const scfContext = CloudBase.scfContext if (scfContext) { urlQueryStr = `&eventId=${eventId}&seqId=${seqId}&scfRequestId=${scfContext.request_id}` } if (url.includes('?')) { url = `${url}${urlQueryStr}` } else { url = `${url}?${urlQueryStr}` } return url } } // 业务逻辑都放在这里处理 export default async (args: IRequestInfo): Promise<any> => { const req = new Request(args) const config = args.config const { action } = args.params if (action === 'wx.openApi' || action === 'wx.wxPayApi') { req.setHooks({ handleData: handleWxOpenApiData }) } if (action.startsWith('database')) { req.setSlowWarning(3000) } try { const res = await req.request() // 检查res是否为return {code, message}回包 if (res && res.code) { // 判断是否设置config._returnCodeByThrow = false return processReturn(config.throwOnCode, res) } return res } finally { // } }