UNPKG

@alicloud/tea-typescript

Version:

[![NPM version][npm-image]][npm-url] [![build status][travis-image]][travis-url] [![codecov][cov-image]][cov-url] [![David deps][david-image]][david-url] [![npm download][download-image]][download-url]

485 lines (416 loc) 13.2 kB
import * as querystring from 'querystring'; import { IncomingMessage, IncomingHttpHeaders, Agent as HttpAgent } from 'http'; import { Agent as HttpsAgent } from 'https'; import { Readable } from 'stream'; import * as httpx from 'httpx'; import { parse } from 'url'; type TeaDict = { [key: string]: string }; type TeaObject = { [key: string]: any }; type AgentOptions = { keepAlive: boolean }; export class BytesReadable extends Readable { value: Buffer constructor(value: string | Buffer) { super(); if (typeof value === 'string') { this.value = Buffer.from(value); } else if (Buffer.isBuffer(value)) { this.value = value; } } _read() { this.push(this.value); this.push(null); } } export class Request { protocol: string; port: number; method: string; pathname: string; query: TeaDict; headers: TeaDict; body: Readable; constructor() { this.headers = {}; this.query = {}; } } export class Response { statusCode: number; statusMessage: string; headers: TeaDict; body: IncomingMessage; constructor(httpResponse: IncomingMessage) { this.statusCode = httpResponse.statusCode; this.statusMessage = httpResponse.statusMessage; this.headers = this.convertHeaders(httpResponse.headers); this.body = httpResponse; } convertHeaders(headers: IncomingHttpHeaders): TeaDict { let results: TeaDict = {}; const keys = Object.keys(headers); for (let index = 0; index < keys.length; index++) { const key = keys[index]; results[key] = <string>headers[key]; } return results; } async readBytes(): Promise<Buffer> { let buff = await httpx.read(this.body, ''); return <Buffer>buff; } } function buildURL(request: Request) { let url = `${request.protocol}://${request.headers['host']}`; if (request.port) { url += `:${request.port}`; } url += `${request.pathname}`; const urlInfo = parse(url); if (request.query && Object.keys(request.query).length > 0) { if (urlInfo.query) { url += `&${querystring.stringify(request.query)}`; } else { url += `?${querystring.stringify(request.query)}`; } } return url; } function isModelClass(t: any): boolean { if (!t) { return false; } return typeof t.types === 'function' && typeof t.names === 'function'; } export async function doAction(request: Request, runtime: TeaObject = null): Promise<Response> { let url = buildURL(request); let method = (request.method || 'GET').toUpperCase(); let options: httpx.Options = { method: method, headers: request.headers }; if (method !== 'GET' && method !== 'HEAD') { options.data = request.body; } if (runtime) { if (typeof runtime.timeout !== 'undefined') { options.timeout = Number(runtime.timeout); } if (typeof runtime.readTimeout !== 'undefined') { options.readTimeout = Number(runtime.readTimeout); } if (typeof runtime.connectTimeout !== 'undefined') { options.connectTimeout = Number(runtime.connectTimeout); } if (typeof runtime.ignoreSSL !== 'undefined') { options.rejectUnauthorized = !runtime.ignoreSSL; } if (typeof runtime.key !== 'undefined') { options.key = String(runtime.key); } if (typeof runtime.cert !== 'undefined') { options.cert = String(runtime.cert); } if (typeof runtime.ca !== 'undefined') { options.ca = String(runtime.ca); } // keepAlive: default true let agentOptions: AgentOptions = { keepAlive: true, }; if (typeof runtime.keepAlive !== 'undefined') { agentOptions.keepAlive = runtime.keepAlive; if (request.protocol && request.protocol.toLowerCase() === 'https') { options.agent = new HttpsAgent(agentOptions); } else { options.agent = new HttpAgent(agentOptions); } } } let response = await httpx.request(url, options); return new Response(response); } class ResponseError extends Error { code: string statusCode: number data: any description: string accessDeniedDetail: any constructor(map: any) { super(`${map.code}: ${map.message}`); this.code = map.code; this.data = map.data; this.description = map.description; this.accessDeniedDetail = map.accessDeniedDetail; if (this.data && this.data.statusCode) { this.statusCode = Number(this.data.statusCode); } } } export function newError(data: any): ResponseError { return new ResponseError(data); } function getValue(type: any, value: any): any { if (typeof type === 'string') { // basic type return value; } if (type.type === 'array') { if (!Array.isArray(value)) { throw new Error(`expect: array, actual: ${typeof value}`); } return value.map((item: any) => { return getValue(type.itemType, item); }); } if (typeof type === 'function') { if (isModelClass(type)) { return new type(value); } return value; } return value; } export function toMap(value: any = undefined): any { if (typeof value === 'undefined' || value == null) { return null; } if (value instanceof Model) { return value.toMap(); } // 如果是另一个版本的 tea-typescript 创建的 model,instanceof 会判断不通过 // 这里做一下处理 if (typeof value.toMap === 'function') { return value.toMap(); } if (Array.isArray(value)) { return value.map((item) => { return toMap(item); }) } return value; } export class Model { [key: string]: any constructor(map?: TeaObject) { if (map == null) { return; } let clz = <any>this.constructor; let names = <TeaDict>clz.names(); let types = <TeaObject>clz.types(); Object.keys(names).forEach((name => { let value = map[name]; if (value === undefined || value === null) { return; } let type = types[name]; this[name] = getValue(type, value); })); } toMap(): TeaObject { const map: TeaObject = {}; let clz = <any>this.constructor; let names = <TeaDict>clz.names(); Object.keys(names).forEach((name => { const originName = names[name]; const value = this[name]; if (typeof value === 'undefined' || value == null) { return; } map[originName] = toMap(value); })); return map; } } export function cast<T>(obj: any, t: T): T { if (!obj) { throw new Error('can not cast to Map'); } if (typeof obj !== 'object') { throw new Error('can not cast to Map'); } let map = obj as TeaObject; let clz = t.constructor as any; let names: TeaDict = clz.names(); let types: TeaObject = clz.types(); Object.keys(names).forEach((key) => { let originName = names[key]; let value = map[originName]; let type = types[key]; if (typeof value === 'undefined' || value == null) { return; } if (typeof type === 'string') { if (type === 'Readable' || type === 'map' || type === 'Buffer' || type === 'any' || typeof value === type) { (<any>t)[key] = value; return; } if (type === 'string' && (typeof value === 'number' || typeof value === 'boolean')) { (<any>t)[key] = value.toString(); return; } if (type === 'boolean') { if (value === 1 || value === 0) { (<any>t)[key] = !!value; return; } if (value === 'true' || value === 'false') { (<any>t)[key] = value === 'true'; return; } } if (type === 'number' && typeof value === 'string') { if (value.match(/^\d*$/)) { (<any>t)[key] = parseInt(value); return; } if (value.match(/^[\.\d]*$/)) { (<any>t)[key] = parseFloat(value); return; } } throw new Error(`type of ${key} is mismatch, expect ${type}, but ${typeof value}`); } else if (type.type === 'map') { if (!(value instanceof Object)) { throw new Error(`type of ${key} is mismatch, expect object, but ${typeof value}`); } (<any>t)[key] = value; } else if (type.type === 'array') { if (!Array.isArray(value)) { throw new Error(`type of ${key} is mismatch, expect array, but ${typeof value}`); } if (typeof type.itemType === 'function') { (<any>t)[key] = value.map((d: any) => { if (isModelClass(type.itemType)) { return cast(d, new type.itemType({})); } return d; }); } else { (<any>t)[key] = value; } } else if (typeof type === 'function') { if (!(value instanceof Object)) { throw new Error(`type of ${key} is mismatch, expect object, but ${typeof value}`); } if (isModelClass(type)) { (<any>t)[key] = cast(value, new type({})); return; } (<any>t)[key] = value; } else { } }); return t; } export function sleep(ms: number): Promise<void> { return new Promise((resolve) => { setTimeout(resolve, ms); }); } export function allowRetry(retry: TeaObject, retryTimes: number, startTime: number): boolean { // 还未重试 if (retryTimes === 0) { return true; } if (retry.retryable !== true) { return false; } if (retry.policy === 'never') { return false; } if (retry.policy === 'always') { return true; } if (retry.policy === 'simple') { return (retryTimes < retry['maxAttempts']); } if (retry.policy === 'timeout') { return Date.now() - startTime < retry.timeout; } if (retry.maxAttempts && typeof retry.maxAttempts === 'number') { return retry.maxAttempts >= retryTimes; } // 默认不重试 return false; } export function getBackoffTime(backoff: TeaObject, retryTimes: number): number { if (retryTimes === 0) { // 首次调用,不使用退避策略 return 0; } if (backoff.policy === 'no') { // 不退避 return 0; } if (backoff.policy === 'fixed') { // 固定退避 return backoff.period; } if (backoff.policy === 'random') { // 随机退避 let min = backoff['minPeriod']; let max = backoff['maxPeriod']; return min + (max - min) * Math.random(); } if (backoff.policy === 'exponential') { // 指数退避 let init = backoff.initial; let multiplier = backoff.multiplier; let time = init * Math.pow(1 + multiplier, retryTimes - 1); let max = backoff.max; return Math.min(time, max); } if (backoff.policy === 'exponential_random') { // 指数随机退避 let init = backoff.initial; let multiplier = backoff.multiplier; let time = init * Math.pow(1 + multiplier, retryTimes - 1); let max = backoff.max; return Math.min(time * (0.5 + Math.random()), max); } return 0; } class UnretryableError extends Error { data: any constructor(message: string) { super(message); this.name = 'UnretryableError'; } } export function newUnretryableError(request: Request): Error { var e = new UnretryableError(''); e.data = { lastRequest: request }; return e; } class RetryError extends Error { retryable: boolean data: any constructor(message: string) { super(message); this.name = 'RetryError'; } } export function retryError(request: Request, response: Response): Error { let e = new RetryError(''); e.data = { request: request, response: response }; return e; } export function isRetryable(err: Error): boolean { if (typeof err === 'undefined' || err === null) { return false; } return err.name === 'RetryError'; }