UNPKG

12 kBPlain TextView Raw
1import * as querystring from 'querystring';
2import { IncomingMessage, IncomingHttpHeaders } from 'http';
3import { Readable } from 'stream';
4import * as httpx from 'httpx';
5import { parse } from 'url';
6
7type TeaDict = { [key: string]: string };
8type TeaObject = { [key: string]: any };
9
10export class BytesReadable extends Readable {
11 value: Buffer
12
13 constructor(value: string | Buffer) {
14 super();
15 if (typeof value === 'string') {
16 this.value = Buffer.from(value);
17 } else if (Buffer.isBuffer(value)) {
18 this.value = value;
19 }
20 }
21
22 _read() {
23 this.push(this.value);
24 this.push(null);
25 }
26}
27
28export class Request {
29 protocol: string;
30 port: number;
31 method: string;
32 pathname: string;
33 query: TeaDict;
34 headers: TeaDict;
35 body: Readable;
36
37 constructor() {
38 this.headers = {};
39 this.query = {};
40 }
41}
42
43export class Response {
44 statusCode: number;
45 statusMessage: string;
46 headers: TeaDict;
47 body: IncomingMessage;
48 constructor(httpResponse: IncomingMessage) {
49 this.statusCode = httpResponse.statusCode;
50 this.statusMessage = httpResponse.statusMessage;
51 this.headers = this.convertHeaders(httpResponse.headers);
52 this.body = httpResponse;
53 }
54
55 convertHeaders(headers: IncomingHttpHeaders): TeaDict {
56 let results: TeaDict = {};
57 const keys = Object.keys(headers);
58 for (let index = 0; index < keys.length; index++) {
59 const key = keys[index];
60 results[key] = <string>headers[key];
61 }
62 return results;
63 }
64
65 async readBytes(): Promise<Buffer> {
66 let buff = await httpx.read(this.body, '');
67 return <Buffer>buff;
68 }
69}
70
71function buildURL(request: Request) {
72 let url = `${request.protocol}://${request.headers['host']}`;
73 if (request.port) {
74 url += `:${request.port}`;
75 }
76 url += `${request.pathname}`;
77 const urlInfo = parse(url);
78 if (request.query && Object.keys(request.query).length > 0) {
79 if (urlInfo.query) {
80 url += `&${querystring.stringify(request.query)}`;
81 } else {
82 url += `?${querystring.stringify(request.query)}`;
83 }
84 }
85 return url;
86}
87
88function isModelClass(t: any): boolean {
89 if (!t) {
90 return false;
91 }
92 return typeof t.types === 'function' && typeof t.names === 'function';
93}
94
95export async function doAction(request: Request, runtime: TeaObject = null): Promise<Response> {
96 let url = buildURL(request);
97 let method = (request.method || 'GET').toUpperCase();
98 let options: httpx.Options = {
99 method: method,
100 headers: request.headers
101 };
102
103 if (method !== 'GET' && method !== 'HEAD') {
104 options.data = request.body;
105 }
106
107 if (runtime) {
108 if (typeof runtime.timeout !== 'undefined') {
109 options.timeout = Number(runtime.timeout);
110 }
111
112 if (typeof runtime.readTimeout !== 'undefined') {
113 options.readTimeout = Number(runtime.readTimeout);
114 }
115
116 if (typeof runtime.connectTimeout !== 'undefined') {
117 options.connectTimeout = Number(runtime.connectTimeout);
118 }
119
120 if (typeof runtime.ignoreSSL !== 'undefined') {
121 options.rejectUnauthorized = !runtime.ignoreSSL;
122 }
123
124 if (typeof runtime.key !== 'undefined') {
125 options.key = String(runtime.key);
126 }
127
128 if (typeof runtime.cert !== 'undefined') {
129 options.cert = String(runtime.cert);
130 }
131
132 if (typeof runtime.ca !== 'undefined') {
133 options.ca = String(runtime.ca);
134 }
135 }
136
137 let response = await httpx.request(url, options);
138
139 return new Response(response);
140}
141
142export function newError(data: any): Error {
143 let message = `${data.code}: ${data.message}`;
144 return new Error(message);
145}
146
147function getValue(type: any, value: any): any {
148 if (typeof type === 'string') {
149 // basic type
150 return value;
151 }
152 if (type.type === 'array') {
153 if (!Array.isArray(value)) {
154 throw new Error(`expect: array, actual: ${typeof value}`);
155 }
156 return value.map((item: any) => {
157 return getValue(type.itemType, item);
158 });
159 }
160 if (typeof type === 'function') {
161 if (isModelClass(type)) {
162 return new type(value);
163 }
164 return value;
165 }
166 return value;
167}
168
169export function toMap(value: any = undefined): any {
170 if (typeof value === 'undefined' || value == null) {
171 return null;
172 }
173
174 if (value instanceof Model) {
175 return value.toMap();
176 }
177
178 // 如果是另一个版本的 tea-typescript 创建的 model,instanceof 会判断不通过
179 // 这里做一下处理
180 if (typeof value.toMap === 'function') {
181 return value.toMap();
182 }
183
184 if (Array.isArray(value)) {
185 return value.map((item) => {
186 return toMap(item);
187 })
188 }
189
190 return value;
191}
192
193export class Model {
194 [key: string]: any
195
196 constructor(map?: TeaObject) {
197 if (map == null) {
198 return;
199 }
200
201 let clz = <any>this.constructor;
202 let names = <TeaDict>clz.names();
203 let types = <TeaObject>clz.types();
204 Object.keys(names).forEach((name => {
205 let value = map[name];
206 if (value === undefined || value === null) {
207 return;
208 }
209 let type = types[name];
210 this[name] = getValue(type, value);
211 }));
212 }
213
214 toMap(): TeaObject {
215 const map: TeaObject = {};
216 let clz = <any>this.constructor;
217 let names = <TeaDict>clz.names();
218 Object.keys(names).forEach((name => {
219 const originName = names[name];
220 const value = this[name];
221 if (typeof value === 'undefined' || value == null) {
222 return;
223 }
224 map[originName] = toMap(value);
225 }));
226 return map;
227 }
228}
229
230export function cast<T>(obj: any, t: T): T {
231 if (!obj) {
232 throw new Error('can not cast to Map');
233 }
234
235 if (typeof obj !== 'object') {
236 throw new Error('can not cast to Map');
237 }
238
239 let map = obj as TeaObject;
240 let clz = t.constructor as any;
241 let names: TeaDict = clz.names();
242 let types: TeaObject = clz.types();
243 Object.keys(names).forEach((key) => {
244 let originName = names[key];
245 let value = map[originName];
246 let type = types[key];
247 if (typeof value === 'undefined' || value == null) {
248 return;
249 }
250 if (typeof type === 'string') {
251 if (type === 'Readable' ||
252 type === 'map' ||
253 type === 'Buffer' ||
254 type === 'any' ||
255 typeof value === type) {
256 (<any>t)[key] = value;
257 return;
258 }
259 if (type === 'string' &&
260 (typeof value === 'number' ||
261 typeof value === 'boolean')) {
262 (<any>t)[key] = value.toString();
263 return;
264 }
265 if (type === 'boolean') {
266 if (value === 1 || value === 0) {
267 (<any>t)[key] = !!value;
268 return;
269 }
270 if (value === 'true' || value === 'false') {
271 (<any>t)[key] = value === 'true';
272 return;
273 }
274 }
275
276 if (type === 'number' && typeof value === 'string') {
277 if (value.match(/^\d*$/)) {
278 (<any>t)[key] = parseInt(value);
279 return;
280 }
281 if (value.match(/^[\.\d]*$/)) {
282 (<any>t)[key] = parseFloat(value);
283 return;
284 }
285 }
286 throw new Error(`type of ${key} is mismatch, expect ${type}, but ${typeof value}`);
287 } else if (type.type === 'map') {
288 if (!(value instanceof Object)) {
289 throw new Error(`type of ${key} is mismatch, expect object, but ${typeof value}`);
290 }
291 (<any>t)[key] = value;
292 } else if (type.type === 'array') {
293 if (!Array.isArray(value)) {
294 throw new Error(`type of ${key} is mismatch, expect array, but ${typeof value}`);
295 }
296 if (typeof type.itemType === 'function') {
297 (<any>t)[key] = value.map((d: any) => {
298 if (isModelClass(type.itemType)) {
299 return cast(d, new type.itemType({}));
300 }
301 return d;
302 });
303 } else {
304 (<any>t)[key] = value;
305 }
306
307 } else if (typeof type === 'function') {
308 if (!(value instanceof Object)) {
309 throw new Error(`type of ${key} is mismatch, expect object, but ${typeof value}`);
310 }
311 if (isModelClass(type)) {
312 (<any>t)[key] = cast(value, new type({}));
313 return;
314 }
315 (<any>t)[key] = value;
316 } else {
317
318 }
319 });
320
321 return t;
322}
323
324export function sleep(ms: number): Promise<void> {
325 return new Promise((resolve) => {
326 setTimeout(resolve, ms);
327 });
328}
329
330export function allowRetry(retry: TeaObject, retryTimes: number, startTime: number): boolean {
331 // 还未重试
332 if (retryTimes === 0) {
333 return true;
334 }
335
336 if (retry.retryable !== true) {
337 return false;
338 }
339
340 if (retry.policy === 'never') {
341 return false;
342 }
343
344 if (retry.policy === 'always') {
345 return true;
346 }
347
348 if (retry.policy === 'simple') {
349 return (retryTimes < retry['maxAttempts']);
350 }
351
352 if (retry.policy === 'timeout') {
353 return Date.now() - startTime < retry.timeout;
354 }
355
356 // 默认不重试
357 return false;
358}
359
360export function getBackoffTime(backoff: TeaObject, retryTimes: number): number {
361 if (retryTimes === 0) {
362 // 首次调用,不使用退避策略
363 return 0;
364 }
365
366 if (backoff.policy === 'no') {
367 // 不退避
368 return 0;
369 }
370
371 if (backoff.policy === 'fixed') {
372 // 固定退避
373 return backoff.period;
374 }
375
376 if (backoff.policy === 'random') {
377 // 随机退避
378 let min = backoff['minPeriod'];
379 let max = backoff['maxPeriod'];
380 return min + (max - min) * Math.random();
381 }
382
383 if (backoff.policy === 'exponential') {
384 // 指数退避
385 let init = backoff.initial;
386 let multiplier = backoff.multiplier;
387 let time = init * Math.pow(1 + multiplier, retryTimes - 1);
388 let max = backoff.max;
389 return Math.min(time, max);
390 }
391
392 if (backoff.policy === 'exponential_random') {
393 // 指数随机退避
394 let init = backoff.initial;
395 let multiplier = backoff.multiplier;
396 let time = init * Math.pow(1 + multiplier, retryTimes - 1);
397 let max = backoff.max;
398 return Math.min(time * (0.5 + Math.random()), max);
399 }
400
401 return 0;
402}
403
404class UnretryableError extends Error {
405 data: any
406
407 constructor(message: string) {
408 super(message);
409 this.name = 'UnretryableError';
410 }
411}
412
413export function newUnretryableError(request: Request): Error {
414 var e = new UnretryableError('');
415 e.data = {
416 lastRequest: request
417 };
418 return e;
419}
420
421class RetryError extends Error {
422 retryable: boolean
423 data: any
424
425 constructor(message: string) {
426 super(message);
427 this.name = 'RetryError';
428 }
429}
430
431export function retryError(request: Request, response: Response): Error {
432 let e = new RetryError('');
433 e.data = {
434 request: request,
435 response: response
436 };
437 return e;
438}
439
440export function isRetryable(err: Error): boolean {
441 if (typeof err === 'undefined' || err === null) {
442 return false;
443 }
444 return err.name === 'RetryError';
445}