UNPKG

3.71 kBPlain TextView Raw
1import request = require('request');
2import { sleep, never } from './utils';
3import { logger, ExternalException, Exception, BaseException } from './logger';
4
5export interface RequestOptions extends request.CoreOptions {
6 attemptsCount?: number;
7 attemptDelay?: number;
8}
9
10type Response = {
11 res?: request.Response;
12 statusCode: number;
13 body: Buffer | string | object;
14};
15type ResponseError = {
16 statusCode: number;
17 body: Buffer | string | object;
18 error?: Error;
19 url: string;
20 options: object;
21};
22
23function requestAsync(url: string, options: RequestOptions = {}) {
24 return new Promise<Response>((resolve, reject) =>
25 request(url, options, (err: Error, res: request.Response | undefined, body: Buffer | undefined) => {
26 const statusCode = res ? res.statusCode : 0;
27 if (err)
28 return reject(
29 new Exception<ResponseError>('Request error', {
30 statusCode,
31 body: body || '',
32 error: err,
33 url,
34 options,
35 }),
36 );
37 return resolve({ res, statusCode, body: body || '' });
38 }),
39 );
40}
41async function _requestRaw(url: string, options: RequestOptions) {
42 const { attemptsCount = 5, attemptDelay = 1000 } = options;
43 for (let i = 1; i <= attemptsCount; i++) {
44 logger.trace('Request', { url, options, attempt: i === 1 ? undefined : i });
45 let res;
46 try {
47 res = await requestAsync(url, options);
48 } catch (error) {
49 if (error instanceof Exception) {
50 const jsonErr = error.json as ResponseError;
51 const nativeErr = jsonErr.error as NodeJS.ErrnoException;
52 const timeoutError = nativeErr && (nativeErr.code === 'ETIMEDOUT' || nativeErr.code === 'ESOCKETTIMEDOUT');
53 if (timeoutError || error.json.statusCode >= 500) {
54 if (i < attemptsCount) {
55 await sleep(attemptDelay);
56 continue;
57 } else {
58 throw new ExternalException<ResponseError>(timeoutError ? 'Request timeout' : '500', error.json);
59 }
60 }
61 }
62 throw error;
63 }
64 const errJson: ResponseError = { body: res.body, options, url, statusCode: res.statusCode };
65 if (res.statusCode >= 500) throw new ExternalException<ResponseError>('500', errJson);
66 if (res.statusCode >= 400) throw new Exception<ResponseError>('400', errJson);
67 if (res.statusCode >= 200 && res.statusCode < 300) {
68 return res;
69 }
70 throw new Exception<ResponseError>('Request error', errJson);
71 }
72 throw never();
73}
74export async function requestRaw(url: string, options: RequestOptions = {}) {
75 const res = await _requestRaw(url, options);
76 logger.trace('RequestResponse', { statusCode: res.statusCode, body: res.body });
77 return res;
78}
79
80export async function requestJSON<T>(url: string, options?: RequestOptions): Promise<{ data: T } & Response> {
81 let d;
82 try {
83 d = await _requestRaw(url, { headers: { 'content-type': 'application/json' }, ...options });
84 } catch (e) {
85 if (e instanceof BaseException) {
86 const err = e.json as Response;
87 if (typeof err.body === 'string') {
88 try {
89 err.body = JSON.parse(err.body);
90 } catch (e) {}
91 }
92 }
93 throw e;
94 }
95 try {
96 let json;
97 if (d.body instanceof Object) {
98 json = (d.body as unknown) as T;
99 } else {
100 json = JSON.parse(d.body.toString() || '{}') as T;
101 }
102 logger.trace('RequestJSONResponse', { statusCode: d.statusCode, body: json });
103 return { ...d, data: json };
104 } catch {
105 throw new Exception(`Response is not json`, d);
106 }
107}
108
109export function mockJsonRequest(_method: string, _url: string, _json: object | undefined, _result: object) {}
110export function mockGetJsonRequest(url: string, result: object) {
111 return mockJsonRequest('get', url, undefined, result);
112}
113export function mockPostJsonRequest(url: string, json: object | undefined, result: object) {
114 return mockJsonRequest('post', url, json, result);
115}
116
\No newline at end of file