UNPKG

@cloudbase/node-sdk

Version:

tencent cloud base server sdk for node.js

246 lines (202 loc) 7.46 kB
/* eslint-disable-next-line */ import { parse } from 'url' import { sign } from '@cloudbase/signature-nodejs' import { ICloudBaseConfig, ICustomReqOpts } from '../../types' import { IRequestInfo, IReqOpts } from '../../types/internal' import { SYMBOL_CURRENT_ENV } from '../const/symbol' import { CloudBase } from '../cloudbase' import { generateTracingInfo, ITracingInfo } from './tracing' import { checkIsInScf, checkIsInternalAsync, getCurrRunEnvTag } from './cloudplatform' import { buildUrl } from './tcbopenapiendpoint' import { prepareCredentials } from './tcbapirequester' import { extraRequest } from './request' import { version } from './version' import * as utils from './utils' const { second } = utils export function getEnvIdFromContext(): string { const { TCB_ENV, SCF_NAMESPACE } = CloudBase.getCloudbaseContext() return TCB_ENV || SCF_NAMESPACE || '' } type ICallContainerRequestInfo = Omit<IRequestInfo, 'params'> & { path: string data: any token?: string cloudrun: { name: string // version: string } } export class TcbOpenApiHttpRequester { private readonly args: ICallContainerRequestInfo private readonly config: ICloudBaseConfig private readonly opts: ICustomReqOpts private readonly defaultTimeout = 15000 private readonly timestamp: number = new Date().valueOf() private readonly tracingInfo: ITracingInfo /* eslint-disable no-undef */ private readonly slowWarnTimer: NodeJS.Timer = null /* eslint-enable no-undef */ public constructor(args: ICallContainerRequestInfo) { this.args = args this.config = args.config this.opts = args.opts || {} this.tracingInfo = generateTracingInfo(args.config?.context?.eventID) } public async request(): Promise<any> { if (!this.config.accessKey) { await this.prepareCredentials() } const opts = this.makeReqOpts() // console.log('opts', opts) const argopts: any = this.opts const config = this.config // 注意:必须初始化为 null let retryOptions: any = null if (argopts.retryOptions) { retryOptions = argopts.retryOptions } else if (config.retries && typeof config.retries === 'number') { retryOptions = { retries: config.retries } } return await extraRequest(opts, { debug: config.debug, op: `${opts.method}:${opts.url}`, seqId: this.tracingInfo.seqId, retryOptions, timingsMeasurerOptions: config.timingsMeasurerOptions || {} }).then((response: any) => { this.slowWarnTimer && clearTimeout(this.slowWarnTimer) return response }) } private makeReqOpts(): IReqOpts { const config = this.config const args = this.args const envId = args.config.envName === SYMBOL_CURRENT_ENV ? getEnvIdFromContext() : (args.config.envName as string) const url = buildUrl({ envId, region: this.config.region, // protocol: this.config.protocol || 'https', // serviceUrl: this.config.serviceUrl, // seqId: this.tracingInfo.seqId, // isInternal: this.args.isInternal, name: args.cloudrun.name, path: args.path }) const timeout = this.args.opts?.timeout || this.config.timeout || this.defaultTimeout const opts: IReqOpts = { url, method: args.method, timeout, headers: this.buildHeaders(args.method, url), proxy: config.proxy } if (typeof config.keepalive === 'undefined' && !checkIsInScf()) { // 非云函数环境下,默认开启 keepalive opts.keepalive = true } else { /** eslint-disable-next-line */ opts.keepalive = typeof config.keepalive === 'boolean' && config.keepalive } if (args.data) { if (args.method.toLowerCase() === 'post') { if (args.isFormData) { opts.formData = args.data opts.encoding = null } else { opts.body = args.data opts.json = true } } else { /* istanbul ignore next */ // 这里 qs 参数暂时并没使用 opts.qs = args.data } } else { opts.noBody = true } return opts } private async prepareCredentials(): Promise<void> { prepareCredentials.bind(this)() } private buildHeaders(method: string, url: string): any { const config = this.config const { context, secretId, secretKey, sessionToken, accessKey } = config const args = this.args const { TCB_SOURCE } = CloudBase.getCloudbaseContext() // Note: 云函数被调用时可能调用端未传递 SOURCE,TCB_SOURCE 可能为空 const SOURCE = `${context?.extendedContext?.source || TCB_SOURCE || ''},${args.opts.runEnvTag}` // 注意:因为 url.parse 和 url.URL 存在差异,因 url.parse 已被废弃,这里可能会需要改动。 // 因 @cloudbase/signature-nodejs sign 方法目前内部使用 url.parse 解析 url, // 如果这里需要改动,需要注意与 @cloudbase/signature-nodejs 的兼容性 // 否则将导致签名存在问题 const parsedUrl = parse(url) // const parsedUrl = new URL(url) 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: parsedUrl.host } if (config.version) { requiredHeaders['X-SDK-Version'] = config.version } if (this.tracingInfo.trace) { requiredHeaders['X-TCB-Tracelog'] = this.tracingInfo.trace } const region = this.config.region || process.env.TENCENTCLOUD_REGION || '' if (region) { requiredHeaders['X-TCB-Region'] = region } requiredHeaders = { ...config.headers, ...args.headers, ...requiredHeaders } let params = args.data || '' // GET 方法不支持传 BODY if (method.toLowerCase() === 'get') { params = '' } // TODO: 升级SDK版本,否则没传 args.data 时会签名失败 const { authorization, timestamp } = sign({ secretId, secretKey, method, url, params, headers: requiredHeaders, timestamp: second() - 1, withSignedParams: false, isCloudApi: true }) let token = '' // 如果请求参数里面传了 token,优先使用 token if (args.token) { token = makeBearerToken(args.token) } else if (accessKey) { // 如果配置了 API_KEY,优先使用 API_KEY token = makeBearerToken(accessKey) } else if (typeof sessionToken === 'string' && sessionToken !== '') { // 如果配置了 sessionToken,使用 sessionToken token = `${authorization}, Timestamp=${timestamp}, Token=${sessionToken}` } else { token = `${authorization}, Timestamp=${timestamp}` } /* eslint-disable @typescript-eslint/dot-notation */ requiredHeaders['Authorization'] = token return { ...requiredHeaders } } } export async function request(args: ICallContainerRequestInfo & { path: string }): Promise<any> { if (typeof args.isInternal === 'undefined') { args.isInternal = await checkIsInternalAsync() } args.opts = args.opts || {} args.opts.runEnvTag = await getCurrRunEnvTag() const requester = new TcbOpenApiHttpRequester(args) return await requester.request() } function makeBearerToken(token: string): string { const trimmed = token.trim() return trimmed.startsWith('Bearer ') ? trimmed : `Bearer ${trimmed}` }