@gravity-ui/gateway 4.4.0 → 4.6.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/build/components/grpc.js +35 -1
- package/build/components/mixed.js +1 -1
- package/build/components/rest.js +4 -3
- package/build/index.js +7 -0
- package/build/models/common.d.ts +4 -0
- 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/build/components/grpc.js
CHANGED
|
@@ -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]`, {
|
|
@@ -461,6 +461,7 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
|
|
|
461
461
|
(0, common_2.handleError)(ErrorConstructor, error, ctx, 'getService failed');
|
|
462
462
|
throw error;
|
|
463
463
|
}
|
|
464
|
+
let stopListeningForAbort = null;
|
|
464
465
|
// eslint-disable-next-line complexity
|
|
465
466
|
return new Promise((resolve, reject) => {
|
|
466
467
|
var _a, _b, _c, _d;
|
|
@@ -522,6 +523,12 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
|
|
|
522
523
|
});
|
|
523
524
|
const actionCall = service[action].bind(service);
|
|
524
525
|
const stream = actionCall(body, serviceMetadata, serviceOptions);
|
|
526
|
+
stopListeningForAbort = (0, grpc_1.listenForAbort)({
|
|
527
|
+
signal: abortSignal,
|
|
528
|
+
config,
|
|
529
|
+
call: stream,
|
|
530
|
+
reject,
|
|
531
|
+
});
|
|
525
532
|
stream.on('error', (error) => {
|
|
526
533
|
ctx.log('ServerStream error', {
|
|
527
534
|
debugHeaders: (0, common_2.sanitizeDebugHeaders)(debugHeaders),
|
|
@@ -532,6 +539,7 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
|
|
|
532
539
|
ctx.log('ServerStream status changed', status);
|
|
533
540
|
});
|
|
534
541
|
stream.on('end', () => {
|
|
542
|
+
stopListeningForAbort === null || stopListeningForAbort === void 0 ? void 0 : stopListeningForAbort();
|
|
535
543
|
ctx.log('ServerStream request completed', {
|
|
536
544
|
debugHeaders: (0, common_2.sanitizeDebugHeaders)(debugHeaders),
|
|
537
545
|
});
|
|
@@ -553,6 +561,15 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
|
|
|
553
561
|
}
|
|
554
562
|
const actionCall = service[action].bind(service);
|
|
555
563
|
const stream = actionCall(serviceMetadata, serviceOptions, actionConfig.callback);
|
|
564
|
+
stopListeningForAbort = (0, grpc_1.listenForAbort)({
|
|
565
|
+
signal: abortSignal,
|
|
566
|
+
config,
|
|
567
|
+
call: stream,
|
|
568
|
+
reject,
|
|
569
|
+
});
|
|
570
|
+
stream.once('end', () => {
|
|
571
|
+
stopListeningForAbort === null || stopListeningForAbort === void 0 ? void 0 : stopListeningForAbort();
|
|
572
|
+
});
|
|
556
573
|
resolve({ debugHeaders, stream });
|
|
557
574
|
return;
|
|
558
575
|
}
|
|
@@ -562,6 +579,12 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
|
|
|
562
579
|
});
|
|
563
580
|
const actionCall = service[action].bind(service);
|
|
564
581
|
const stream = actionCall(serviceMetadata, serviceOptions);
|
|
582
|
+
stopListeningForAbort = (0, grpc_1.listenForAbort)({
|
|
583
|
+
signal: abortSignal,
|
|
584
|
+
config,
|
|
585
|
+
call: stream,
|
|
586
|
+
reject,
|
|
587
|
+
});
|
|
565
588
|
stream.on('error', (error) => {
|
|
566
589
|
ctx.log('BidiStream error', {
|
|
567
590
|
debugHeaders: (0, common_2.sanitizeDebugHeaders)(debugHeaders),
|
|
@@ -572,6 +595,7 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
|
|
|
572
595
|
ctx.log('BidiStream status changed', status);
|
|
573
596
|
});
|
|
574
597
|
stream.on('end', () => {
|
|
598
|
+
stopListeningForAbort === null || stopListeningForAbort === void 0 ? void 0 : stopListeningForAbort();
|
|
575
599
|
ctx.log('BidiStream request completed', {
|
|
576
600
|
debugHeaders: (0, common_2.sanitizeDebugHeaders)(debugHeaders),
|
|
577
601
|
});
|
|
@@ -619,12 +643,14 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
|
|
|
619
643
|
(0, common_2.handleError)(ErrorConstructor, error, ctx, 'getService failed');
|
|
620
644
|
throw error;
|
|
621
645
|
}
|
|
646
|
+
stopListeningForAbort === null || stopListeningForAbort === void 0 ? void 0 : stopListeningForAbort();
|
|
622
647
|
// Update service
|
|
623
648
|
actionCall = service[action].bind(service);
|
|
624
649
|
callAction();
|
|
625
650
|
return;
|
|
626
651
|
}
|
|
627
652
|
if (error) {
|
|
653
|
+
stopListeningForAbort === null || stopListeningForAbort === void 0 ? void 0 : stopListeningForAbort();
|
|
628
654
|
reject(new parse_error_1.GrpcError('gRPC request error', (0, parse_error_1.parseGrpcError)(error, root, lang, config.decodeAnyMessageProtoLoaderOptions), error));
|
|
629
655
|
return;
|
|
630
656
|
}
|
|
@@ -660,11 +686,18 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
|
|
|
660
686
|
debugHeaders: (0, common_2.sanitizeDebugHeaders)(debugHeaders),
|
|
661
687
|
});
|
|
662
688
|
ctx.end();
|
|
689
|
+
stopListeningForAbort === null || stopListeningForAbort === void 0 ? void 0 : stopListeningForAbort();
|
|
663
690
|
return resolve({ responseData, responseHeaders, debugHeaders });
|
|
664
691
|
});
|
|
665
692
|
call.on('status', (status) => {
|
|
666
693
|
trailingMetadata = status.metadata.toJSON();
|
|
667
694
|
});
|
|
695
|
+
stopListeningForAbort = (0, grpc_1.listenForAbort)({
|
|
696
|
+
signal: abortSignal,
|
|
697
|
+
config,
|
|
698
|
+
call,
|
|
699
|
+
reject,
|
|
700
|
+
});
|
|
668
701
|
};
|
|
669
702
|
callAction();
|
|
670
703
|
}
|
|
@@ -672,6 +705,7 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
|
|
|
672
705
|
}).catch((error) => {
|
|
673
706
|
const grpcError = (0, parse_error_1.isGrpcError)(error) ? error : (0, parse_error_1.grpcErrorFactory)(error);
|
|
674
707
|
processError(grpcError);
|
|
708
|
+
stopListeningForAbort === null || stopListeningForAbort === void 0 ? void 0 : stopListeningForAbort();
|
|
675
709
|
return Promise.reject({ error: grpcError.getGatewayError(), debugHeaders });
|
|
676
710
|
});
|
|
677
711
|
};
|
|
@@ -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
|
@@ -34,13 +34,13 @@ function getConfigSerializerFunction(config) {
|
|
|
34
34
|
return undefined;
|
|
35
35
|
}
|
|
36
36
|
function createRestAction(endpoints, config, serviceKey, actionName, options, ErrorConstructor) {
|
|
37
|
-
var _a, _b, _c;
|
|
37
|
+
var _a, _b, _c, _d;
|
|
38
38
|
const timeout = (_c = (_a = config === null || config === void 0 ? void 0 : config.timeout) !== null && _a !== void 0 ? _a : (_b = options === null || options === void 0 ? void 0 : options.axiosConfig) === null || _b === void 0 ? void 0 : _b.timeout) !== null && _c !== void 0 ? _c : options === null || options === void 0 ? void 0 : options.timeout;
|
|
39
|
-
const defaultAxiosClient = (0, axios_1.getAxiosClient)(timeout, config === null || config === void 0 ? void 0 : config.retries, options === null || options === void 0 ? void 0 : options.axiosRetryCondition, options === null || options === void 0 ? void 0 : options.axiosConfig, options === null || options === void 0 ? void 0 : options.axiosInterceptors);
|
|
39
|
+
const defaultAxiosClient = (0, axios_1.getAxiosClient)(timeout, config === null || config === void 0 ? void 0 : config.retries, (_d = config === null || config === void 0 ? void 0 : config.axiosRetryCondition) !== null && _d !== void 0 ? _d : options === null || options === void 0 ? void 0 : options.axiosRetryCondition, options === null || options === void 0 ? void 0 : options.axiosConfig, options === null || options === void 0 ? void 0 : options.axiosInterceptors);
|
|
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;
|
|
@@ -220,6 +220,7 @@ function createRestAction(endpoints, config, serviceKey, actionName, options, Er
|
|
|
220
220
|
params: query,
|
|
221
221
|
headers: ctx ? Object.assign(Object.assign({}, ctx.getMetadata()), headers) : headers,
|
|
222
222
|
maxRedirects: config.maxRedirects,
|
|
223
|
+
signal: config.abortOnClientDisconnect ? abortSignal : undefined,
|
|
223
224
|
};
|
|
224
225
|
if (config.paramsSerializer) {
|
|
225
226
|
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;
|
|
@@ -131,6 +132,7 @@ export interface ApiServiceBaseActionConfig<Context extends GatewayContext, TOut
|
|
|
131
132
|
proxyHeaders?: ProxyHeaders;
|
|
132
133
|
proxyResponseHeaders?: ProxyResponseHeaders;
|
|
133
134
|
metadata?: Record<string, string | number | boolean>;
|
|
135
|
+
abortOnClientDisconnect?: boolean;
|
|
134
136
|
}
|
|
135
137
|
export interface ApiServiceRestActionConfig<Context extends GatewayContext, TOutput, TParams = undefined, TTransformed = TOutput> extends ApiServiceBaseActionConfig<Context, TOutput, TParams, TTransformed> {
|
|
136
138
|
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
|
|
@@ -139,6 +141,7 @@ export interface ApiServiceRestActionConfig<Context extends GatewayContext, TOut
|
|
|
139
141
|
responseType?: AxiosRequestConfig['responseType'];
|
|
140
142
|
expectedResponseContentType?: ResponseContentType | ResponseContentType[];
|
|
141
143
|
maxRedirects?: number;
|
|
144
|
+
axiosRetryCondition?: AxiosRetryCondition;
|
|
142
145
|
}
|
|
143
146
|
export interface ApiServiceBaseGrpcActionConfig<Context extends GatewayContext, TOutput, TParams = undefined, TTransformed = TOutput> extends ApiServiceBaseActionConfig<Context, TOutput, TParams, TTransformed> {
|
|
144
147
|
action: string;
|
|
@@ -167,6 +170,7 @@ export interface ApiServiceMixedExtra<Context extends GatewayContext, Req extend
|
|
|
167
170
|
ctx: Context;
|
|
168
171
|
config: GatewayConfig<Context, Req, Res>;
|
|
169
172
|
grpcContext: GrpcContext;
|
|
173
|
+
abortSignal?: AbortSignal;
|
|
170
174
|
}
|
|
171
175
|
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>;
|
|
172
176
|
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/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 =
|