1 | import request = require('request');
|
2 | import { sleep, never } from './utils';
|
3 | import { logger, ExternalException, Exception, BaseException } from './logger';
|
4 |
|
5 | export interface RequestOptions extends request.CoreOptions {
|
6 | attemptsCount?: number;
|
7 | attemptDelay?: number;
|
8 | }
|
9 |
|
10 | type Response = {
|
11 | res?: request.Response;
|
12 | statusCode: number;
|
13 | body: Buffer | string | object;
|
14 | };
|
15 | type ResponseError = {
|
16 | statusCode: number;
|
17 | body: Buffer | string | object;
|
18 | error?: Error;
|
19 | url: string;
|
20 | options: object;
|
21 | };
|
22 |
|
23 | function 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 | }
|
41 | async 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 | }
|
74 | export 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 |
|
80 | export 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 |
|
109 | export function mockJsonRequest(_method: string, _url: string, _json: object | undefined, _result: object) {}
|
110 | export function mockGetJsonRequest(url: string, result: object) {
|
111 | return mockJsonRequest('get', url, undefined, result);
|
112 | }
|
113 | export function mockPostJsonRequest(url: string, json: object | undefined, result: object) {
|
114 | return mockJsonRequest('post', url, json, result);
|
115 | }
|
116 |
|
\ | No newline at end of file |