@gravity-ui/gateway 4.5.0 → 4.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -1
- package/build/components/grpc.js +51 -8
- package/build/components/mixed.js +1 -1
- package/build/components/rest.js +12 -4
- package/build/index.js +7 -0
- package/build/models/common.d.ts +10 -1
- package/build/utils/common.d.ts +2 -1
- package/build/utils/common.js +12 -1
- package/build/utils/grpc.d.ts +10 -0
- package/build/utils/grpc.js +24 -1
- package/build/utils/parse-error.js +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,6 @@ A flexible and powerful Express controller for working with REST and gRPC APIs i
|
|
|
7
7
|
- [Installation](#installation)
|
|
8
8
|
- [Basic Usage](#basic-usage)
|
|
9
9
|
- [Configuration](#configuration)
|
|
10
|
-
- [Config Structure](#config-structure)
|
|
11
10
|
- [Validation Schema](#validation-schema)
|
|
12
11
|
- [Using the API in Node.js](#using-the-api-in-nodejs)
|
|
13
12
|
- [Schema Scopes](#schema-scopes)
|
|
@@ -92,9 +91,18 @@ type AxiosRetryCondition = IAxiosRetryConfig['retryCondition'];
|
|
|
92
91
|
|
|
93
92
|
type ControllerType = 'rest' | 'grpc';
|
|
94
93
|
|
|
94
|
+
type ProxyHeadersFunctionExtra = {
|
|
95
|
+
service: string;
|
|
96
|
+
action: string;
|
|
97
|
+
|
|
98
|
+
protopath?: string;
|
|
99
|
+
protokey?: string;
|
|
100
|
+
};
|
|
101
|
+
|
|
95
102
|
type ProxyHeadersFunction = (
|
|
96
103
|
headers: IncomingHttpHeaders,
|
|
97
104
|
type: ControllerType,
|
|
105
|
+
extra: ProxyHeadersFunctionExtra,
|
|
98
106
|
) => IncomingHttpHeaders;
|
|
99
107
|
type ProxyHeaders = string[] | ProxyHeadersFunction;
|
|
100
108
|
type ResponseContentType = AxiosResponse['headers']['Content-Type'];
|
|
@@ -205,6 +213,49 @@ interface GatewayConfig {
|
|
|
205
213
|
}
|
|
206
214
|
```
|
|
207
215
|
|
|
216
|
+
### `proxyHeaders`
|
|
217
|
+
|
|
218
|
+
`GatewayConfig.proxyHeaders` is an optional method that allows setting headers for requests at the entire `gateway` level:
|
|
219
|
+
|
|
220
|
+
```javascript
|
|
221
|
+
const proxyHeaders = (headers, actionType, {service, action}) => {
|
|
222
|
+
const normalizedHeaders = {...headers};
|
|
223
|
+
|
|
224
|
+
if (actionType === 'rest' && service === 'mail') {
|
|
225
|
+
normalizedHeaders['x-mail-service-action'] = action;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return normalizedHeaders;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const {controller: gatewayController} = getGatewayControllers(
|
|
232
|
+
{root: Schema},
|
|
233
|
+
{...config, proxyHeaders},
|
|
234
|
+
);
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
You can set headers for a specific action using `ApiServiceBaseActionConfig.proxyHeaders`:
|
|
238
|
+
|
|
239
|
+
```javascript
|
|
240
|
+
const schema = {
|
|
241
|
+
userService: {
|
|
242
|
+
serviceName: 'users',
|
|
243
|
+
endpoints: {...},
|
|
244
|
+
actions: {
|
|
245
|
+
getProfile: {
|
|
246
|
+
path: () => '/profile',
|
|
247
|
+
method: 'GET',
|
|
248
|
+
proxyHeaders: (headers) => ({...headers, ['x-users-service-action']: 'get-profile'}),
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
The `GatewayConfig.proxyHeaders` and `ApiServiceBaseActionConfig.proxyHeaders` are merged when the action is called. The strategy for merging headers is not guaranteed.
|
|
256
|
+
|
|
257
|
+
It is recommended to use `GatewayConfig.proxyHeaders` for assigning headers that are common to the entire application or a large number of actions. Otherwise, it is preferable to use `ApiServiceBaseActionConfig.proxyHeaders`.
|
|
258
|
+
|
|
208
259
|
### Validation Schema
|
|
209
260
|
|
|
210
261
|
By default, for path params in REST actions, the following regexp is used: `/^((?!(\.\.|\?|#|\\|\/)).)*$/i`.
|
package/build/components/grpc.js
CHANGED
|
@@ -86,7 +86,7 @@ function decodeResponse(response, packageRoot, ctx, encodedFields = [], ErrorCon
|
|
|
86
86
|
}
|
|
87
87
|
});
|
|
88
88
|
}
|
|
89
|
-
function createMetadata({ options, actionConfig, config, params, serviceName, ctx, }) {
|
|
89
|
+
function createMetadata({ options, actionConfig, config, params, serviceName, proxyHeadersCaller, ctx, }) {
|
|
90
90
|
var _a;
|
|
91
91
|
const { headers, requestId, authArgs } = actionConfig;
|
|
92
92
|
const proxyHeaders = [...constants_1.DEFAULT_PROXY_HEADERS];
|
|
@@ -95,13 +95,13 @@ function createMetadata({ options, actionConfig, config, params, serviceName, ct
|
|
|
95
95
|
'accept-language': headers[constants_1.DEFAULT_LANG_HEADER] || constants_1.Lang.Ru,
|
|
96
96
|
};
|
|
97
97
|
if (typeof options.proxyHeaders === 'function') {
|
|
98
|
-
Object.assign(metadata, options.proxyHeaders
|
|
98
|
+
Object.assign(metadata, proxyHeadersCaller(options.proxyHeaders));
|
|
99
99
|
}
|
|
100
100
|
else if (Array.isArray(options.proxyHeaders)) {
|
|
101
101
|
proxyHeaders.push(...options.proxyHeaders);
|
|
102
102
|
}
|
|
103
103
|
if (typeof config.proxyHeaders === 'function') {
|
|
104
|
-
Object.assign(metadata, config.proxyHeaders
|
|
104
|
+
Object.assign(metadata, proxyHeadersCaller(config.proxyHeaders));
|
|
105
105
|
}
|
|
106
106
|
else if (Array.isArray(config.proxyHeaders)) {
|
|
107
107
|
proxyHeaders.push(...config.proxyHeaders);
|
|
@@ -385,7 +385,7 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
|
|
|
385
385
|
}
|
|
386
386
|
return async function action(actionConfig) {
|
|
387
387
|
var _a;
|
|
388
|
-
const { args, requestId, headers, ctx: parentCtx, userId } = actionConfig;
|
|
388
|
+
const { args, requestId, headers, ctx: parentCtx, userId, abortSignal } = actionConfig;
|
|
389
389
|
const { action } = config;
|
|
390
390
|
const lang = headers[constants_1.DEFAULT_LANG_HEADER] || constants_1.Lang.Ru; // header might be empty string
|
|
391
391
|
const ctx = parentCtx.create(`Gateway ${serviceName} ${actionName} [grpc]`, {
|
|
@@ -401,7 +401,7 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
|
|
|
401
401
|
service: serviceName,
|
|
402
402
|
action: actionName,
|
|
403
403
|
requestTime: 0,
|
|
404
|
-
requestId
|
|
404
|
+
requestId,
|
|
405
405
|
requestMethod: action,
|
|
406
406
|
requestUrl: config.protoKey,
|
|
407
407
|
traceId: ((_a = ctx.getTraceId) === null || _a === void 0 ? void 0 : _a.call(ctx)) || '',
|
|
@@ -414,11 +414,19 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
|
|
|
414
414
|
'x-request-id': requestId,
|
|
415
415
|
'x-gateway-version': constants_1.VERSION,
|
|
416
416
|
};
|
|
417
|
-
|
|
418
|
-
|
|
417
|
+
const protopath = 'protoPath' in config ? config.protoPath : undefined;
|
|
418
|
+
if (protopath) {
|
|
419
|
+
debugHeaders['x-api-request-protopath'] = protopath;
|
|
419
420
|
}
|
|
421
|
+
let proxyHeadersExtra;
|
|
422
|
+
const proxyHeadersCaller = (proxyHeadersFunc) => {
|
|
423
|
+
if (proxyHeadersExtra === undefined) {
|
|
424
|
+
proxyHeadersExtra = (0, common_2.getProxyHeadersArgs)(serviceName, actionName, config);
|
|
425
|
+
}
|
|
426
|
+
return proxyHeadersFunc(Object.assign({}, headers), 'rest', proxyHeadersExtra);
|
|
427
|
+
};
|
|
420
428
|
if (typeof options.proxyDebugHeaders === 'function') {
|
|
421
|
-
Object.assign(debugHeaders, options.proxyDebugHeaders
|
|
429
|
+
Object.assign(debugHeaders, proxyHeadersCaller(options.proxyDebugHeaders));
|
|
422
430
|
}
|
|
423
431
|
else if (Array.isArray(options.proxyDebugHeaders)) {
|
|
424
432
|
for (const headerName of options.proxyDebugHeaders) {
|
|
@@ -461,6 +469,7 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
|
|
|
461
469
|
(0, common_2.handleError)(ErrorConstructor, error, ctx, 'getService failed');
|
|
462
470
|
throw error;
|
|
463
471
|
}
|
|
472
|
+
let stopListeningForAbort = null;
|
|
464
473
|
// eslint-disable-next-line complexity
|
|
465
474
|
return new Promise((resolve, reject) => {
|
|
466
475
|
var _a, _b, _c, _d;
|
|
@@ -505,6 +514,7 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
|
|
|
505
514
|
config,
|
|
506
515
|
params,
|
|
507
516
|
serviceName,
|
|
517
|
+
proxyHeadersCaller,
|
|
508
518
|
ctx,
|
|
509
519
|
});
|
|
510
520
|
if (!service[action]) {
|
|
@@ -522,6 +532,12 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
|
|
|
522
532
|
});
|
|
523
533
|
const actionCall = service[action].bind(service);
|
|
524
534
|
const stream = actionCall(body, serviceMetadata, serviceOptions);
|
|
535
|
+
stopListeningForAbort = (0, grpc_1.listenForAbort)({
|
|
536
|
+
signal: abortSignal,
|
|
537
|
+
config,
|
|
538
|
+
call: stream,
|
|
539
|
+
reject,
|
|
540
|
+
});
|
|
525
541
|
stream.on('error', (error) => {
|
|
526
542
|
ctx.log('ServerStream error', {
|
|
527
543
|
debugHeaders: (0, common_2.sanitizeDebugHeaders)(debugHeaders),
|
|
@@ -532,6 +548,7 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
|
|
|
532
548
|
ctx.log('ServerStream status changed', status);
|
|
533
549
|
});
|
|
534
550
|
stream.on('end', () => {
|
|
551
|
+
stopListeningForAbort === null || stopListeningForAbort === void 0 ? void 0 : stopListeningForAbort();
|
|
535
552
|
ctx.log('ServerStream request completed', {
|
|
536
553
|
debugHeaders: (0, common_2.sanitizeDebugHeaders)(debugHeaders),
|
|
537
554
|
});
|
|
@@ -553,6 +570,15 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
|
|
|
553
570
|
}
|
|
554
571
|
const actionCall = service[action].bind(service);
|
|
555
572
|
const stream = actionCall(serviceMetadata, serviceOptions, actionConfig.callback);
|
|
573
|
+
stopListeningForAbort = (0, grpc_1.listenForAbort)({
|
|
574
|
+
signal: abortSignal,
|
|
575
|
+
config,
|
|
576
|
+
call: stream,
|
|
577
|
+
reject,
|
|
578
|
+
});
|
|
579
|
+
stream.once('end', () => {
|
|
580
|
+
stopListeningForAbort === null || stopListeningForAbort === void 0 ? void 0 : stopListeningForAbort();
|
|
581
|
+
});
|
|
556
582
|
resolve({ debugHeaders, stream });
|
|
557
583
|
return;
|
|
558
584
|
}
|
|
@@ -562,6 +588,12 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
|
|
|
562
588
|
});
|
|
563
589
|
const actionCall = service[action].bind(service);
|
|
564
590
|
const stream = actionCall(serviceMetadata, serviceOptions);
|
|
591
|
+
stopListeningForAbort = (0, grpc_1.listenForAbort)({
|
|
592
|
+
signal: abortSignal,
|
|
593
|
+
config,
|
|
594
|
+
call: stream,
|
|
595
|
+
reject,
|
|
596
|
+
});
|
|
565
597
|
stream.on('error', (error) => {
|
|
566
598
|
ctx.log('BidiStream error', {
|
|
567
599
|
debugHeaders: (0, common_2.sanitizeDebugHeaders)(debugHeaders),
|
|
@@ -572,6 +604,7 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
|
|
|
572
604
|
ctx.log('BidiStream status changed', status);
|
|
573
605
|
});
|
|
574
606
|
stream.on('end', () => {
|
|
607
|
+
stopListeningForAbort === null || stopListeningForAbort === void 0 ? void 0 : stopListeningForAbort();
|
|
575
608
|
ctx.log('BidiStream request completed', {
|
|
576
609
|
debugHeaders: (0, common_2.sanitizeDebugHeaders)(debugHeaders),
|
|
577
610
|
});
|
|
@@ -619,12 +652,14 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
|
|
|
619
652
|
(0, common_2.handleError)(ErrorConstructor, error, ctx, 'getService failed');
|
|
620
653
|
throw error;
|
|
621
654
|
}
|
|
655
|
+
stopListeningForAbort === null || stopListeningForAbort === void 0 ? void 0 : stopListeningForAbort();
|
|
622
656
|
// Update service
|
|
623
657
|
actionCall = service[action].bind(service);
|
|
624
658
|
callAction();
|
|
625
659
|
return;
|
|
626
660
|
}
|
|
627
661
|
if (error) {
|
|
662
|
+
stopListeningForAbort === null || stopListeningForAbort === void 0 ? void 0 : stopListeningForAbort();
|
|
628
663
|
reject(new parse_error_1.GrpcError('gRPC request error', (0, parse_error_1.parseGrpcError)(error, root, lang, config.decodeAnyMessageProtoLoaderOptions), error));
|
|
629
664
|
return;
|
|
630
665
|
}
|
|
@@ -660,11 +695,18 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
|
|
|
660
695
|
debugHeaders: (0, common_2.sanitizeDebugHeaders)(debugHeaders),
|
|
661
696
|
});
|
|
662
697
|
ctx.end();
|
|
698
|
+
stopListeningForAbort === null || stopListeningForAbort === void 0 ? void 0 : stopListeningForAbort();
|
|
663
699
|
return resolve({ responseData, responseHeaders, debugHeaders });
|
|
664
700
|
});
|
|
665
701
|
call.on('status', (status) => {
|
|
666
702
|
trailingMetadata = status.metadata.toJSON();
|
|
667
703
|
});
|
|
704
|
+
stopListeningForAbort = (0, grpc_1.listenForAbort)({
|
|
705
|
+
signal: abortSignal,
|
|
706
|
+
config,
|
|
707
|
+
call,
|
|
708
|
+
reject,
|
|
709
|
+
});
|
|
668
710
|
};
|
|
669
711
|
callAction();
|
|
670
712
|
}
|
|
@@ -672,6 +714,7 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
|
|
|
672
714
|
}).catch((error) => {
|
|
673
715
|
const grpcError = (0, parse_error_1.isGrpcError)(error) ? error : (0, parse_error_1.grpcErrorFactory)(error);
|
|
674
716
|
processError(grpcError);
|
|
717
|
+
stopListeningForAbort === null || stopListeningForAbort === void 0 ? void 0 : stopListeningForAbort();
|
|
675
718
|
return Promise.reject({ error: grpcError.getGatewayError(), debugHeaders });
|
|
676
719
|
});
|
|
677
720
|
};
|
|
@@ -28,7 +28,7 @@ function createMixedAction(config, api, serviceName, actionName, extra, ErrorCon
|
|
|
28
28
|
});
|
|
29
29
|
const contextApi = (0, create_context_api_1.generateContextApi)(api, Object.assign(Object.assign({}, context), { ctx }));
|
|
30
30
|
try {
|
|
31
|
-
const responseData = await config(contextApi, args, Object.assign({ headers: actionConfig.headers, lang: actionConfig.headers[constants_1.DEFAULT_LANG_HEADER] || constants_1.Lang.Ru, ctx }, extra));
|
|
31
|
+
const responseData = await config(contextApi, args, Object.assign(Object.assign({ headers: actionConfig.headers, lang: actionConfig.headers[constants_1.DEFAULT_LANG_HEADER] || constants_1.Lang.Ru, ctx }, extra), { abortSignal: actionConfig.abortSignal }));
|
|
32
32
|
ctx.log('Request completed');
|
|
33
33
|
return {
|
|
34
34
|
responseData,
|
package/build/components/rest.js
CHANGED
|
@@ -40,7 +40,7 @@ function createRestAction(endpoints, config, serviceKey, actionName, options, Er
|
|
|
40
40
|
/* eslint-disable complexity */
|
|
41
41
|
return async function action(actionConfig) {
|
|
42
42
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
43
|
-
const { args, requestId, headers: requestHeaders, ctx: parentCtx, authArgs, userId, } = actionConfig;
|
|
43
|
+
const { args, requestId, headers: requestHeaders, ctx: parentCtx, authArgs, userId, abortSignal, } = actionConfig;
|
|
44
44
|
const debugHeaders = {};
|
|
45
45
|
const lang = requestHeaders[constants_1.DEFAULT_LANG_HEADER] || constants_1.Lang.Ru; // header might be empty string
|
|
46
46
|
const serviceName = (options === null || options === void 0 ? void 0 : options.serviceName) || serviceKey;
|
|
@@ -113,14 +113,21 @@ function createRestAction(endpoints, config, serviceKey, actionName, options, Er
|
|
|
113
113
|
'accept-language': lang,
|
|
114
114
|
'x-gateway-version': constants_1.VERSION,
|
|
115
115
|
};
|
|
116
|
+
let proxyHeadersExtra;
|
|
117
|
+
const proxyHeadersCaller = (proxyHeadersFunc) => {
|
|
118
|
+
if (proxyHeadersExtra === undefined) {
|
|
119
|
+
proxyHeadersExtra = (0, common_1.getProxyHeadersArgs)(serviceName, actionName);
|
|
120
|
+
}
|
|
121
|
+
return proxyHeadersFunc(Object.assign({}, requestHeaders), 'rest', proxyHeadersExtra);
|
|
122
|
+
};
|
|
116
123
|
if (typeof options.proxyHeaders === 'function') {
|
|
117
|
-
Object.assign(actionHeaders, options.proxyHeaders
|
|
124
|
+
Object.assign(actionHeaders, proxyHeadersCaller(options.proxyHeaders));
|
|
118
125
|
}
|
|
119
126
|
else if (Array.isArray(options.proxyHeaders)) {
|
|
120
127
|
proxyHeaders.push(...options.proxyHeaders);
|
|
121
128
|
}
|
|
122
129
|
if (typeof config.proxyHeaders === 'function') {
|
|
123
|
-
Object.assign(actionHeaders, config.proxyHeaders
|
|
130
|
+
Object.assign(actionHeaders, proxyHeadersCaller(config.proxyHeaders));
|
|
124
131
|
}
|
|
125
132
|
else if (Array.isArray(config.proxyHeaders)) {
|
|
126
133
|
proxyHeaders.push(...config.proxyHeaders);
|
|
@@ -185,7 +192,7 @@ function createRestAction(endpoints, config, serviceKey, actionName, options, Er
|
|
|
185
192
|
debugHeaders['x-api-content-type'] = headers['content-type'];
|
|
186
193
|
}
|
|
187
194
|
if (typeof options.proxyDebugHeaders === 'function') {
|
|
188
|
-
Object.assign(debugHeaders, options.proxyDebugHeaders
|
|
195
|
+
Object.assign(debugHeaders, proxyHeadersCaller(options.proxyDebugHeaders));
|
|
189
196
|
}
|
|
190
197
|
else if (Array.isArray(options.proxyDebugHeaders)) {
|
|
191
198
|
for (const headerName of options.proxyDebugHeaders) {
|
|
@@ -220,6 +227,7 @@ function createRestAction(endpoints, config, serviceKey, actionName, options, Er
|
|
|
220
227
|
params: query,
|
|
221
228
|
headers: ctx ? Object.assign(Object.assign({}, ctx.getMetadata()), headers) : headers,
|
|
222
229
|
maxRedirects: config.maxRedirects,
|
|
230
|
+
signal: config.abortOnClientDisconnect ? abortSignal : undefined,
|
|
223
231
|
};
|
|
224
232
|
if (config.paramsSerializer) {
|
|
225
233
|
Object.assign(requestConfig, {
|
package/build/index.js
CHANGED
|
@@ -176,6 +176,11 @@ function generateGatewayApiController(schemasByScope, Api, config, controllerAct
|
|
|
176
176
|
}
|
|
177
177
|
const args = req.method === 'GET' ? req.query : req.body;
|
|
178
178
|
try {
|
|
179
|
+
const abortController = new AbortController();
|
|
180
|
+
const handleCloseConnection = () => {
|
|
181
|
+
abortController.abort();
|
|
182
|
+
};
|
|
183
|
+
req.connection.once('close', handleCloseConnection);
|
|
179
184
|
const apiAction = Api[scope][service][action];
|
|
180
185
|
if (onBeforeAction) {
|
|
181
186
|
const actionConfig = (_c = (_b = (_a = schemasByScope[scope]) === null || _a === void 0 ? void 0 : _a[service]) === null || _b === void 0 ? void 0 : _b.actions) === null || _c === void 0 ? void 0 : _c[action];
|
|
@@ -194,7 +199,9 @@ function generateGatewayApiController(schemasByScope, Api, config, controllerAct
|
|
|
194
199
|
args,
|
|
195
200
|
authArgs: config.getAuthArgs(req, res),
|
|
196
201
|
userId,
|
|
202
|
+
abortSignal: abortController.signal,
|
|
197
203
|
});
|
|
204
|
+
req.connection.removeListener('close', handleCloseConnection);
|
|
198
205
|
if (withDebugHeaders) {
|
|
199
206
|
res.set(debugHeaders);
|
|
200
207
|
}
|
package/build/models/common.d.ts
CHANGED
|
@@ -32,6 +32,7 @@ export interface ApiActionConfig<Context extends GatewayContext, TRequestData, T
|
|
|
32
32
|
callback?: (response: TResponseData) => void;
|
|
33
33
|
authArgs?: Record<string, unknown>;
|
|
34
34
|
userId?: string;
|
|
35
|
+
abortSignal?: AbortSignal;
|
|
35
36
|
}
|
|
36
37
|
export interface GRPCActionData {
|
|
37
38
|
[key: string]: unknown;
|
|
@@ -67,7 +68,13 @@ export interface GatewayError {
|
|
|
67
68
|
}
|
|
68
69
|
export type GrpcRetryCondition = (error: ServiceError) => boolean;
|
|
69
70
|
export type AxiosRetryCondition = IAxiosRetryConfig['retryCondition'];
|
|
70
|
-
export type
|
|
71
|
+
export type ProxyHeadersFunctionExtra = {
|
|
72
|
+
service: string;
|
|
73
|
+
action: string;
|
|
74
|
+
protopath?: string;
|
|
75
|
+
protokey?: string;
|
|
76
|
+
};
|
|
77
|
+
export type ProxyHeadersFunction = (headers: IncomingHttpHeaders, type: ControllerType, extra: ProxyHeadersFunctionExtra) => IncomingHttpHeaders;
|
|
71
78
|
export type ProxyHeaders = string[] | ProxyHeadersFunction;
|
|
72
79
|
export type ProxyResponseHeadersFunction = (headers: Headers, type: ControllerType) => Headers;
|
|
73
80
|
export type ProxyResponseHeaders = string[] | ProxyResponseHeadersFunction;
|
|
@@ -131,6 +138,7 @@ export interface ApiServiceBaseActionConfig<Context extends GatewayContext, TOut
|
|
|
131
138
|
proxyHeaders?: ProxyHeaders;
|
|
132
139
|
proxyResponseHeaders?: ProxyResponseHeaders;
|
|
133
140
|
metadata?: Record<string, string | number | boolean>;
|
|
141
|
+
abortOnClientDisconnect?: boolean;
|
|
134
142
|
}
|
|
135
143
|
export interface ApiServiceRestActionConfig<Context extends GatewayContext, TOutput, TParams = undefined, TTransformed = TOutput> extends ApiServiceBaseActionConfig<Context, TOutput, TParams, TTransformed> {
|
|
136
144
|
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
|
|
@@ -168,6 +176,7 @@ export interface ApiServiceMixedExtra<Context extends GatewayContext, Req extend
|
|
|
168
176
|
ctx: Context;
|
|
169
177
|
config: GatewayConfig<Context, Req, Res>;
|
|
170
178
|
grpcContext: GrpcContext;
|
|
179
|
+
abortSignal?: AbortSignal;
|
|
171
180
|
}
|
|
172
181
|
export type ApiServiceMixedActionConfig<Context extends GatewayContext, Req extends GatewayRequest<Context>, Res extends GatewayResponse, TOutput, TParams = undefined, TTransformed = TOutput> = (api: unknown, args: TParams, extra: ApiServiceMixedExtra<Context, Req, Res>) => Promise<TTransformed>;
|
|
173
182
|
export type ApiServiceActionConfig<Context extends GatewayContext, Req extends GatewayRequest<Context>, Res extends GatewayResponse, TOutput, TParams = undefined, TTransformed = TOutput> = ApiServiceRestActionConfig<Context, TOutput, TParams, TTransformed> | ApiServiceGrpcActionConfig<Context, TOutput, TParams, TTransformed> | ApiServiceMixedActionConfig<Context, Req, Res, TOutput, TParams, TTransformed>;
|
package/build/utils/common.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as grpc from '@grpc/grpc-js';
|
|
2
2
|
import _ from 'lodash';
|
|
3
|
-
import { ActionEndpoint, ExtendedActionEndpoint, ExtendedGrpcActionEndpoint, ExtendedRestActionEndpoint, Headers } from '../models/common';
|
|
3
|
+
import { ActionEndpoint, ApiServiceGrpcActionConfig, ExtendedActionEndpoint, ExtendedGrpcActionEndpoint, ExtendedRestActionEndpoint, Headers, ProxyHeadersFunctionExtra } from '../models/common';
|
|
4
4
|
import { Dict, GatewayContext } from '../models/context';
|
|
5
5
|
import { AppErrorConstructor } from '../models/error';
|
|
6
6
|
export declare function isExtendedActionEndpoint(endpoint: ActionEndpoint): endpoint is ExtendedActionEndpoint;
|
|
@@ -13,3 +13,4 @@ export declare function getKeys<T extends object>(obj: T): (keyof T)[];
|
|
|
13
13
|
export declare function sanitizeDebugHeaders(debugHeaders: Headers): _.Omit<Headers, "x-api-request-body">;
|
|
14
14
|
export declare function getHeadersFromMetadata(metadata: Record<string, grpc.MetadataValue[]>, prefix?: string): Record<string, string>;
|
|
15
15
|
export declare function handleError<Context extends GatewayContext>(ErrorConstructor: AppErrorConstructor, error: unknown, ctx: Context, message: string, extra?: Dict): void;
|
|
16
|
+
export declare const getProxyHeadersArgs: <Context extends GatewayContext>(serviceName: string, actionName: string, grpcConfig?: ApiServiceGrpcActionConfig<Context, any, any> | undefined) => ProxyHeadersFunctionExtra;
|
package/build/utils/common.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.handleError = exports.getHeadersFromMetadata = exports.sanitizeDebugHeaders = exports.getKeys = exports.isExtendedRestActionEndpoint = exports.isExtendedGrpcActionEndpoint = exports.isExtendedActionEndpoint = void 0;
|
|
6
|
+
exports.getProxyHeadersArgs = exports.handleError = exports.getHeadersFromMetadata = exports.sanitizeDebugHeaders = exports.getKeys = exports.isExtendedRestActionEndpoint = exports.isExtendedGrpcActionEndpoint = exports.isExtendedActionEndpoint = void 0;
|
|
7
7
|
const lodash_1 = __importDefault(require("lodash"));
|
|
8
8
|
function isExtendedActionEndpoint(endpoint) {
|
|
9
9
|
return (endpoint === null || endpoint === void 0 ? void 0 : endpoint.path) !== undefined;
|
|
@@ -49,3 +49,14 @@ function handleError(ErrorConstructor, error, ctx, message, extra) {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
exports.handleError = handleError;
|
|
52
|
+
const getProxyHeadersArgs = (serviceName, actionName, grpcConfig) => {
|
|
53
|
+
const protopath = grpcConfig && 'protoPath' in grpcConfig ? grpcConfig.protoPath : undefined;
|
|
54
|
+
const protokey = grpcConfig === null || grpcConfig === void 0 ? void 0 : grpcConfig.protoKey;
|
|
55
|
+
return {
|
|
56
|
+
service: serviceName,
|
|
57
|
+
action: actionName,
|
|
58
|
+
protopath,
|
|
59
|
+
protokey,
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
exports.getProxyHeadersArgs = getProxyHeadersArgs;
|
package/build/utils/grpc.d.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import * as grpc from '@grpc/grpc-js';
|
|
2
|
+
import { ClientDuplexStream, ClientReadableStream, ClientUnaryCall, ClientWritableStream } from '@grpc/grpc-js';
|
|
2
3
|
import * as protobufjs from 'protobufjs';
|
|
3
4
|
export declare function decodeAnyMessageRecursively(root: protobufjs.Root, message?: unknown, decodeAnyMessageProtoLoaderOptions?: protobufjs.IConversionOptions): unknown;
|
|
4
5
|
export declare function isRetryableGrpcError(error?: grpc.ServiceError): boolean;
|
|
5
6
|
export declare function isRecreateServiceError(error?: grpc.ServiceError): boolean;
|
|
7
|
+
export type ListenForAbortArgs = {
|
|
8
|
+
signal?: AbortSignal;
|
|
9
|
+
config: {
|
|
10
|
+
abortOnClientDisconnect?: boolean;
|
|
11
|
+
};
|
|
12
|
+
call: ClientUnaryCall | ClientReadableStream<unknown> | ClientWritableStream<unknown> | ClientDuplexStream<unknown, unknown>;
|
|
13
|
+
reject: (err: Error) => void;
|
|
14
|
+
};
|
|
15
|
+
export declare function listenForAbort({ signal, config, call, reject }: ListenForAbortArgs): () => void;
|
package/build/utils/grpc.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/* eslint-disable camelcase */
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.isRecreateServiceError = exports.isRetryableGrpcError = exports.decodeAnyMessageRecursively = void 0;
|
|
4
|
+
exports.listenForAbort = exports.isRecreateServiceError = exports.isRetryableGrpcError = exports.decodeAnyMessageRecursively = void 0;
|
|
5
5
|
const constants_1 = require("../constants");
|
|
6
|
+
const parse_error_1 = require("./parse-error");
|
|
6
7
|
function isEncodedMessage(message) {
|
|
7
8
|
return Boolean(message.type_url && message.value);
|
|
8
9
|
}
|
|
@@ -54,3 +55,25 @@ function isRecreateServiceError(error) {
|
|
|
54
55
|
return constants_1.RECREATE_SERVICE_CODES.includes(error.code);
|
|
55
56
|
}
|
|
56
57
|
exports.isRecreateServiceError = isRecreateServiceError;
|
|
58
|
+
function listenForAbort({ signal, config, call, reject }) {
|
|
59
|
+
if (!signal || !config.abortOnClientDisconnect) {
|
|
60
|
+
return () => null;
|
|
61
|
+
}
|
|
62
|
+
const handleAbortSignal = () => {
|
|
63
|
+
call.cancel();
|
|
64
|
+
reject(new parse_error_1.GrpcError('Request was cancelled.', {
|
|
65
|
+
status: 499,
|
|
66
|
+
code: 'REQUEST_WAS_CANCELLED',
|
|
67
|
+
message: 'Request was cancelled because the original connection was disconnected.',
|
|
68
|
+
}));
|
|
69
|
+
};
|
|
70
|
+
if (signal.aborted) {
|
|
71
|
+
handleAbortSignal();
|
|
72
|
+
return () => null;
|
|
73
|
+
}
|
|
74
|
+
signal.addEventListener('abort', handleAbortSignal);
|
|
75
|
+
return () => {
|
|
76
|
+
signal.removeEventListener('abort', handleAbortSignal);
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
exports.listenForAbort = listenForAbort;
|
|
@@ -116,6 +116,10 @@ function parseRestError(error, lang) {
|
|
|
116
116
|
status = 504;
|
|
117
117
|
description = lang === constants_1.Lang.Ru ? 'Превышено время ожидания ответа' : 'Timeout exceeded';
|
|
118
118
|
}
|
|
119
|
+
else if (code === 'ERR_CANCELED') {
|
|
120
|
+
status = 499;
|
|
121
|
+
description = lang === constants_1.Lang.Ru ? 'Запрос был отменен.' : 'Request was cancelled.';
|
|
122
|
+
}
|
|
119
123
|
else {
|
|
120
124
|
status = 500;
|
|
121
125
|
description =
|