@gravity-ui/gateway 4.6.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 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`.
@@ -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(Object.assign({}, headers), 'grpc'));
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(Object.assign({}, headers), 'grpc'));
104
+ Object.assign(metadata, proxyHeadersCaller(config.proxyHeaders));
105
105
  }
106
106
  else if (Array.isArray(config.proxyHeaders)) {
107
107
  proxyHeaders.push(...config.proxyHeaders);
@@ -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: actionConfig.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
- if ('protoPath' in config) {
418
- debugHeaders['x-api-request-protopath'] = config.protoPath;
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(Object.assign({}, headers), 'grpc'));
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) {
@@ -506,6 +514,7 @@ function createGrpcAction({ root, credentials }, endpoints, config, serviceKey,
506
514
  config,
507
515
  params,
508
516
  serviceName,
517
+ proxyHeadersCaller,
509
518
  ctx,
510
519
  });
511
520
  if (!service[action]) {
@@ -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(Object.assign({}, requestHeaders), 'rest'));
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(Object.assign({}, requestHeaders), 'rest'));
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(Object.assign({}, requestHeaders), 'rest'));
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) {
@@ -68,7 +68,13 @@ export interface GatewayError {
68
68
  }
69
69
  export type GrpcRetryCondition = (error: ServiceError) => boolean;
70
70
  export type AxiosRetryCondition = IAxiosRetryConfig['retryCondition'];
71
- export type ProxyHeadersFunction = (headers: IncomingHttpHeaders, type: ControllerType) => IncomingHttpHeaders;
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;
72
78
  export type ProxyHeaders = string[] | ProxyHeadersFunction;
73
79
  export type ProxyResponseHeadersFunction = (headers: Headers, type: ControllerType) => Headers;
74
80
  export type ProxyResponseHeaders = string[] | ProxyResponseHeadersFunction;
@@ -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;
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/gateway",
3
- "version": "4.6.0",
3
+ "version": "4.7.0",
4
4
  "description": "",
5
5
  "license": "MIT",
6
6
  "main": "build/index.js",