@tramvai/module-request-limiter 1.75.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 +70 -0
- package/lib/browser.d.ts +2 -0
- package/lib/browser.js +12 -0
- package/lib/requestLimiter.d.ts +31 -0
- package/lib/server.d.ts +4 -0
- package/lib/server.es.js +181 -0
- package/lib/server.js +192 -0
- package/lib/tokens.d.ts +4 -0
- package/lib/utils/doubleLinkedList.d.ts +15 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# @tramvai/module-request-limiter
|
|
2
|
+
|
|
3
|
+
This module provides a request limiter, designed to dynamically limit the number of requests handled concurrently by the application.
|
|
4
|
+
Request limiter monitors the application server health through event loop lag checks.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
You need to install `@tramvai/module-request-limiter`
|
|
9
|
+
|
|
10
|
+
```bash npm2yarn
|
|
11
|
+
yarn add @tramvai/module-request-limiter
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
And connect in the project
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import { createApp } from '@tramvai/core';
|
|
18
|
+
import { RequestLimiterModule } from '@tramvai/module-request-limiter';
|
|
19
|
+
|
|
20
|
+
createApp({
|
|
21
|
+
name: 'tincoin',
|
|
22
|
+
modules: [ RequestLimiterModule ],
|
|
23
|
+
});
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
### Activation
|
|
29
|
+
|
|
30
|
+
To activate the limiter, use token `REQUESTS_LIMITER_ACTIVATE_TOKEN` with `true` value:
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { REQUESTS_LIMITER_ACTIVATE_TOKEN } from '@tramvai/module-request-limiter';
|
|
34
|
+
|
|
35
|
+
const provider = {
|
|
36
|
+
provide: REQUESTS_LIMITER_ACTIVATE_TOKEN,
|
|
37
|
+
useValue: true,
|
|
38
|
+
};
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Configuration
|
|
42
|
+
|
|
43
|
+
You can pass options to request limiter by `REQUESTS_LIMITER_OPTIONS_TOKEN` token:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { REQUESTS_LIMITER_OPTIONS_TOKEN } from '@tramvai/module-request-limiter';
|
|
47
|
+
|
|
48
|
+
const provider = {
|
|
49
|
+
provide: REQUESTS_LIMITER_OPTIONS_TOKEN,
|
|
50
|
+
// default options
|
|
51
|
+
useValue: {
|
|
52
|
+
limit: 10,
|
|
53
|
+
queue: 100,
|
|
54
|
+
maxEventLoopDelay: 150,
|
|
55
|
+
error: { httpStatus: 429, message: 'Too Many Requests' },
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Explanation
|
|
61
|
+
|
|
62
|
+
After the server starts, request limiter can handle `options.limit` parallel connections.
|
|
63
|
+
Requests over this limit will fall in queue, the size of which limited by `options.queue`.
|
|
64
|
+
Other connections that exceed the size of the queue will be terminated with an error `options.error`.
|
|
65
|
+
|
|
66
|
+
Requests from queue will be processed from the end of the queue.
|
|
67
|
+
|
|
68
|
+
Every second limiter checking the current event loop lag.
|
|
69
|
+
If current lag exceed the `options.maxEventLoopDelay`, limit number of parallel connections will be decremented.
|
|
70
|
+
Otherwise, this limit will be incremented.
|
package/lib/browser.d.ts
ADDED
package/lib/browser.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { __decorate } from 'tslib';
|
|
2
|
+
import { Module } from '@tramvai/core';
|
|
3
|
+
|
|
4
|
+
let RequestLimiterModule = class RequestLimiterModule {
|
|
5
|
+
};
|
|
6
|
+
RequestLimiterModule = __decorate([
|
|
7
|
+
Module({
|
|
8
|
+
providers: [],
|
|
9
|
+
})
|
|
10
|
+
], RequestLimiterModule);
|
|
11
|
+
|
|
12
|
+
export { RequestLimiterModule };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
2
|
+
export interface RequestLimiterOptions {
|
|
3
|
+
limit?: number;
|
|
4
|
+
queue?: number;
|
|
5
|
+
maxEventLoopDelay?: number;
|
|
6
|
+
error?: {
|
|
7
|
+
httpStatus: number;
|
|
8
|
+
message: string;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export interface RequestLimiterRequest {
|
|
12
|
+
req: Request;
|
|
13
|
+
res: Response;
|
|
14
|
+
next: NextFunction;
|
|
15
|
+
}
|
|
16
|
+
export declare class RequestLimiter {
|
|
17
|
+
private currentActive;
|
|
18
|
+
private eventLoopDelay;
|
|
19
|
+
private queue;
|
|
20
|
+
private activeRequestLimit;
|
|
21
|
+
private queueLimit;
|
|
22
|
+
private error;
|
|
23
|
+
private minimalActiveRequestLimit;
|
|
24
|
+
private maxEventLoopDelay;
|
|
25
|
+
private eventLoopHistogram;
|
|
26
|
+
constructor(options?: RequestLimiterOptions);
|
|
27
|
+
private nextTick;
|
|
28
|
+
add(request: RequestLimiterRequest): void;
|
|
29
|
+
private loop;
|
|
30
|
+
private run;
|
|
31
|
+
}
|
package/lib/server.d.ts
ADDED
package/lib/server.es.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { __decorate } from 'tslib';
|
|
2
|
+
import { Module, provide, Scope } from '@tramvai/core';
|
|
3
|
+
import { WEB_APP_LIMITER_TOKEN } from '@tramvai/tokens-server';
|
|
4
|
+
import onFinished from 'on-finished';
|
|
5
|
+
import { HttpError } from '@tinkoff/errors';
|
|
6
|
+
import { monitorEventLoopDelay } from 'perf_hooks';
|
|
7
|
+
import { createToken } from '@tinkoff/dippy';
|
|
8
|
+
|
|
9
|
+
class DoubleLinkedList {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.length = 0;
|
|
12
|
+
this.start = null;
|
|
13
|
+
this.end = null;
|
|
14
|
+
}
|
|
15
|
+
push(value) {
|
|
16
|
+
const newNode = {
|
|
17
|
+
value,
|
|
18
|
+
next: null,
|
|
19
|
+
prev: null,
|
|
20
|
+
};
|
|
21
|
+
this.length++;
|
|
22
|
+
if (this.start === null) {
|
|
23
|
+
this.start = newNode;
|
|
24
|
+
this.end = newNode;
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const currentEnd = this.end;
|
|
28
|
+
this.end = newNode;
|
|
29
|
+
this.end.prev = currentEnd;
|
|
30
|
+
currentEnd.next = this.end;
|
|
31
|
+
}
|
|
32
|
+
pop() {
|
|
33
|
+
if (this.end === null) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
this.length--;
|
|
37
|
+
// if equal we have only 1 node, so we just remove start
|
|
38
|
+
if (this.end === this.start) {
|
|
39
|
+
this.start = null;
|
|
40
|
+
}
|
|
41
|
+
const { value } = this.end;
|
|
42
|
+
this.end = this.end.prev;
|
|
43
|
+
if (this.end) {
|
|
44
|
+
this.end.next = null;
|
|
45
|
+
}
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
shift() {
|
|
49
|
+
if (this.start === null) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
this.length--;
|
|
53
|
+
// if equal we have only 1 node, so we just remove end
|
|
54
|
+
if (this.end === this.start) {
|
|
55
|
+
this.end = null;
|
|
56
|
+
}
|
|
57
|
+
const { value } = this.start;
|
|
58
|
+
this.start = this.start.next;
|
|
59
|
+
if (this.start) {
|
|
60
|
+
this.start.prev = null;
|
|
61
|
+
}
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
size() {
|
|
65
|
+
return this.length;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const DEFAULT_OPTIONS = {
|
|
70
|
+
limit: 10,
|
|
71
|
+
queue: 100,
|
|
72
|
+
maxEventLoopDelay: 150,
|
|
73
|
+
error: { httpStatus: 429, message: 'Too Many Requests' },
|
|
74
|
+
};
|
|
75
|
+
const resolution = 10;
|
|
76
|
+
class RequestLimiter {
|
|
77
|
+
constructor(options = DEFAULT_OPTIONS) {
|
|
78
|
+
this.currentActive = 0;
|
|
79
|
+
this.eventLoopDelay = 0;
|
|
80
|
+
this.queue = new DoubleLinkedList();
|
|
81
|
+
const { limit = DEFAULT_OPTIONS.limit, queue = DEFAULT_OPTIONS.queue, maxEventLoopDelay = DEFAULT_OPTIONS.maxEventLoopDelay, error = DEFAULT_OPTIONS.error, } = options;
|
|
82
|
+
this.activeRequestLimit = limit;
|
|
83
|
+
this.minimalActiveRequestLimit = Math.floor(limit / 2);
|
|
84
|
+
this.queueLimit = queue;
|
|
85
|
+
this.error = error;
|
|
86
|
+
this.maxEventLoopDelay = maxEventLoopDelay;
|
|
87
|
+
this.eventLoopHistogram = monitorEventLoopDelay({ resolution });
|
|
88
|
+
this.eventLoopHistogram.enable();
|
|
89
|
+
const timer = setInterval(() => this.nextTick(), 1000);
|
|
90
|
+
timer.unref();
|
|
91
|
+
}
|
|
92
|
+
// General idea is change limits ever second. Because if DDOS was happened we need some time to get problem with event loop. And better if we slowly adapt
|
|
93
|
+
nextTick() {
|
|
94
|
+
this.eventLoopDelay = Math.max(0, this.eventLoopHistogram.mean / 1e6 - resolution);
|
|
95
|
+
if (Number.isNaN(this.eventLoopDelay))
|
|
96
|
+
this.eventLoopDelay = Infinity;
|
|
97
|
+
this.eventLoopHistogram.reset();
|
|
98
|
+
if (this.currentActive >= this.activeRequestLimit &&
|
|
99
|
+
this.activeRequestLimit > this.minimalActiveRequestLimit) {
|
|
100
|
+
if (this.eventLoopDelay <= this.maxEventLoopDelay) {
|
|
101
|
+
this.activeRequestLimit++;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
this.activeRequestLimit--;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
add(request) {
|
|
109
|
+
if (this.currentActive < this.activeRequestLimit) {
|
|
110
|
+
this.run(request);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (this.queue.length >= this.queueLimit) {
|
|
114
|
+
const lastNode = this.queue.shift();
|
|
115
|
+
lastNode.next(new HttpError(this.error));
|
|
116
|
+
}
|
|
117
|
+
this.queue.push(request);
|
|
118
|
+
}
|
|
119
|
+
loop() {
|
|
120
|
+
while (this.queue.length > 0 && this.currentActive < this.activeRequestLimit) {
|
|
121
|
+
// better if we start with new requests. Because more opportunity to answer before client cancel request
|
|
122
|
+
this.run(this.queue.pop());
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
run({ req, res, next }) {
|
|
126
|
+
this.currentActive++;
|
|
127
|
+
// onFinished doesn't work OK in DEV mode. Just stuck with high load without any reasons
|
|
128
|
+
onFinished(res, () => {
|
|
129
|
+
this.currentActive--;
|
|
130
|
+
this.loop();
|
|
131
|
+
});
|
|
132
|
+
next();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const REQUESTS_LIMITER_TOKEN = createToken('requestsLimiterToken');
|
|
137
|
+
const REQUESTS_LIMITER_ACTIVATE_TOKEN = createToken('requestsLimiterActivateToken');
|
|
138
|
+
const REQUESTS_LIMITER_OPTIONS_TOKEN = createToken('requestsLimiterOptionsToken');
|
|
139
|
+
|
|
140
|
+
let RequestLimiterModule = class RequestLimiterModule {
|
|
141
|
+
};
|
|
142
|
+
RequestLimiterModule = __decorate([
|
|
143
|
+
Module({
|
|
144
|
+
providers: [
|
|
145
|
+
provide({
|
|
146
|
+
provide: REQUESTS_LIMITER_TOKEN,
|
|
147
|
+
scope: Scope.SINGLETON,
|
|
148
|
+
useFactory: ({ options, featureEnable }) => {
|
|
149
|
+
if (featureEnable !== true) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
return new RequestLimiter(options);
|
|
153
|
+
},
|
|
154
|
+
deps: {
|
|
155
|
+
options: { token: REQUESTS_LIMITER_OPTIONS_TOKEN, optional: true },
|
|
156
|
+
featureEnable: { token: REQUESTS_LIMITER_ACTIVATE_TOKEN, optional: true },
|
|
157
|
+
},
|
|
158
|
+
}),
|
|
159
|
+
provide({
|
|
160
|
+
provide: WEB_APP_LIMITER_TOKEN,
|
|
161
|
+
multi: true,
|
|
162
|
+
useFactory: ({ requestsLimiter, featureEnable }) => {
|
|
163
|
+
return function addRequestsLimiterMiddleware(app) {
|
|
164
|
+
if (featureEnable !== true) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
app.use((req, res, next) => {
|
|
168
|
+
requestsLimiter.add({ req, res, next });
|
|
169
|
+
});
|
|
170
|
+
};
|
|
171
|
+
},
|
|
172
|
+
deps: {
|
|
173
|
+
requestsLimiter: REQUESTS_LIMITER_TOKEN,
|
|
174
|
+
featureEnable: { token: REQUESTS_LIMITER_ACTIVATE_TOKEN, optional: true },
|
|
175
|
+
},
|
|
176
|
+
}),
|
|
177
|
+
],
|
|
178
|
+
})
|
|
179
|
+
], RequestLimiterModule);
|
|
180
|
+
|
|
181
|
+
export { REQUESTS_LIMITER_ACTIVATE_TOKEN, REQUESTS_LIMITER_OPTIONS_TOKEN, REQUESTS_LIMITER_TOKEN, RequestLimiter, RequestLimiterModule };
|
package/lib/server.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var tslib = require('tslib');
|
|
6
|
+
var core = require('@tramvai/core');
|
|
7
|
+
var tokensServer = require('@tramvai/tokens-server');
|
|
8
|
+
var onFinished = require('on-finished');
|
|
9
|
+
var errors = require('@tinkoff/errors');
|
|
10
|
+
var perf_hooks = require('perf_hooks');
|
|
11
|
+
var dippy = require('@tinkoff/dippy');
|
|
12
|
+
|
|
13
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
14
|
+
|
|
15
|
+
var onFinished__default = /*#__PURE__*/_interopDefaultLegacy(onFinished);
|
|
16
|
+
|
|
17
|
+
class DoubleLinkedList {
|
|
18
|
+
constructor() {
|
|
19
|
+
this.length = 0;
|
|
20
|
+
this.start = null;
|
|
21
|
+
this.end = null;
|
|
22
|
+
}
|
|
23
|
+
push(value) {
|
|
24
|
+
const newNode = {
|
|
25
|
+
value,
|
|
26
|
+
next: null,
|
|
27
|
+
prev: null,
|
|
28
|
+
};
|
|
29
|
+
this.length++;
|
|
30
|
+
if (this.start === null) {
|
|
31
|
+
this.start = newNode;
|
|
32
|
+
this.end = newNode;
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const currentEnd = this.end;
|
|
36
|
+
this.end = newNode;
|
|
37
|
+
this.end.prev = currentEnd;
|
|
38
|
+
currentEnd.next = this.end;
|
|
39
|
+
}
|
|
40
|
+
pop() {
|
|
41
|
+
if (this.end === null) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
this.length--;
|
|
45
|
+
// if equal we have only 1 node, so we just remove start
|
|
46
|
+
if (this.end === this.start) {
|
|
47
|
+
this.start = null;
|
|
48
|
+
}
|
|
49
|
+
const { value } = this.end;
|
|
50
|
+
this.end = this.end.prev;
|
|
51
|
+
if (this.end) {
|
|
52
|
+
this.end.next = null;
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
shift() {
|
|
57
|
+
if (this.start === null) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
this.length--;
|
|
61
|
+
// if equal we have only 1 node, so we just remove end
|
|
62
|
+
if (this.end === this.start) {
|
|
63
|
+
this.end = null;
|
|
64
|
+
}
|
|
65
|
+
const { value } = this.start;
|
|
66
|
+
this.start = this.start.next;
|
|
67
|
+
if (this.start) {
|
|
68
|
+
this.start.prev = null;
|
|
69
|
+
}
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
size() {
|
|
73
|
+
return this.length;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const DEFAULT_OPTIONS = {
|
|
78
|
+
limit: 10,
|
|
79
|
+
queue: 100,
|
|
80
|
+
maxEventLoopDelay: 150,
|
|
81
|
+
error: { httpStatus: 429, message: 'Too Many Requests' },
|
|
82
|
+
};
|
|
83
|
+
const resolution = 10;
|
|
84
|
+
class RequestLimiter {
|
|
85
|
+
constructor(options = DEFAULT_OPTIONS) {
|
|
86
|
+
this.currentActive = 0;
|
|
87
|
+
this.eventLoopDelay = 0;
|
|
88
|
+
this.queue = new DoubleLinkedList();
|
|
89
|
+
const { limit = DEFAULT_OPTIONS.limit, queue = DEFAULT_OPTIONS.queue, maxEventLoopDelay = DEFAULT_OPTIONS.maxEventLoopDelay, error = DEFAULT_OPTIONS.error, } = options;
|
|
90
|
+
this.activeRequestLimit = limit;
|
|
91
|
+
this.minimalActiveRequestLimit = Math.floor(limit / 2);
|
|
92
|
+
this.queueLimit = queue;
|
|
93
|
+
this.error = error;
|
|
94
|
+
this.maxEventLoopDelay = maxEventLoopDelay;
|
|
95
|
+
this.eventLoopHistogram = perf_hooks.monitorEventLoopDelay({ resolution });
|
|
96
|
+
this.eventLoopHistogram.enable();
|
|
97
|
+
const timer = setInterval(() => this.nextTick(), 1000);
|
|
98
|
+
timer.unref();
|
|
99
|
+
}
|
|
100
|
+
// General idea is change limits ever second. Because if DDOS was happened we need some time to get problem with event loop. And better if we slowly adapt
|
|
101
|
+
nextTick() {
|
|
102
|
+
this.eventLoopDelay = Math.max(0, this.eventLoopHistogram.mean / 1e6 - resolution);
|
|
103
|
+
if (Number.isNaN(this.eventLoopDelay))
|
|
104
|
+
this.eventLoopDelay = Infinity;
|
|
105
|
+
this.eventLoopHistogram.reset();
|
|
106
|
+
if (this.currentActive >= this.activeRequestLimit &&
|
|
107
|
+
this.activeRequestLimit > this.minimalActiveRequestLimit) {
|
|
108
|
+
if (this.eventLoopDelay <= this.maxEventLoopDelay) {
|
|
109
|
+
this.activeRequestLimit++;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
this.activeRequestLimit--;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
add(request) {
|
|
117
|
+
if (this.currentActive < this.activeRequestLimit) {
|
|
118
|
+
this.run(request);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (this.queue.length >= this.queueLimit) {
|
|
122
|
+
const lastNode = this.queue.shift();
|
|
123
|
+
lastNode.next(new errors.HttpError(this.error));
|
|
124
|
+
}
|
|
125
|
+
this.queue.push(request);
|
|
126
|
+
}
|
|
127
|
+
loop() {
|
|
128
|
+
while (this.queue.length > 0 && this.currentActive < this.activeRequestLimit) {
|
|
129
|
+
// better if we start with new requests. Because more opportunity to answer before client cancel request
|
|
130
|
+
this.run(this.queue.pop());
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
run({ req, res, next }) {
|
|
134
|
+
this.currentActive++;
|
|
135
|
+
// onFinished doesn't work OK in DEV mode. Just stuck with high load without any reasons
|
|
136
|
+
onFinished__default["default"](res, () => {
|
|
137
|
+
this.currentActive--;
|
|
138
|
+
this.loop();
|
|
139
|
+
});
|
|
140
|
+
next();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const REQUESTS_LIMITER_TOKEN = dippy.createToken('requestsLimiterToken');
|
|
145
|
+
const REQUESTS_LIMITER_ACTIVATE_TOKEN = dippy.createToken('requestsLimiterActivateToken');
|
|
146
|
+
const REQUESTS_LIMITER_OPTIONS_TOKEN = dippy.createToken('requestsLimiterOptionsToken');
|
|
147
|
+
|
|
148
|
+
exports.RequestLimiterModule = class RequestLimiterModule {
|
|
149
|
+
};
|
|
150
|
+
exports.RequestLimiterModule = tslib.__decorate([
|
|
151
|
+
core.Module({
|
|
152
|
+
providers: [
|
|
153
|
+
core.provide({
|
|
154
|
+
provide: REQUESTS_LIMITER_TOKEN,
|
|
155
|
+
scope: core.Scope.SINGLETON,
|
|
156
|
+
useFactory: ({ options, featureEnable }) => {
|
|
157
|
+
if (featureEnable !== true) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
return new RequestLimiter(options);
|
|
161
|
+
},
|
|
162
|
+
deps: {
|
|
163
|
+
options: { token: REQUESTS_LIMITER_OPTIONS_TOKEN, optional: true },
|
|
164
|
+
featureEnable: { token: REQUESTS_LIMITER_ACTIVATE_TOKEN, optional: true },
|
|
165
|
+
},
|
|
166
|
+
}),
|
|
167
|
+
core.provide({
|
|
168
|
+
provide: tokensServer.WEB_APP_LIMITER_TOKEN,
|
|
169
|
+
multi: true,
|
|
170
|
+
useFactory: ({ requestsLimiter, featureEnable }) => {
|
|
171
|
+
return function addRequestsLimiterMiddleware(app) {
|
|
172
|
+
if (featureEnable !== true) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
app.use((req, res, next) => {
|
|
176
|
+
requestsLimiter.add({ req, res, next });
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
},
|
|
180
|
+
deps: {
|
|
181
|
+
requestsLimiter: REQUESTS_LIMITER_TOKEN,
|
|
182
|
+
featureEnable: { token: REQUESTS_LIMITER_ACTIVATE_TOKEN, optional: true },
|
|
183
|
+
},
|
|
184
|
+
}),
|
|
185
|
+
],
|
|
186
|
+
})
|
|
187
|
+
], exports.RequestLimiterModule);
|
|
188
|
+
|
|
189
|
+
exports.REQUESTS_LIMITER_ACTIVATE_TOKEN = REQUESTS_LIMITER_ACTIVATE_TOKEN;
|
|
190
|
+
exports.REQUESTS_LIMITER_OPTIONS_TOKEN = REQUESTS_LIMITER_OPTIONS_TOKEN;
|
|
191
|
+
exports.REQUESTS_LIMITER_TOKEN = REQUESTS_LIMITER_TOKEN;
|
|
192
|
+
exports.RequestLimiter = RequestLimiter;
|
package/lib/tokens.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { RequestLimiter, RequestLimiterOptions } from './requestLimiter';
|
|
2
|
+
export declare const REQUESTS_LIMITER_TOKEN: RequestLimiter;
|
|
3
|
+
export declare const REQUESTS_LIMITER_ACTIVATE_TOKEN: boolean;
|
|
4
|
+
export declare const REQUESTS_LIMITER_OPTIONS_TOKEN: RequestLimiterOptions;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
interface ListNode<Value> {
|
|
2
|
+
next: ListNode<Value>;
|
|
3
|
+
prev: ListNode<Value>;
|
|
4
|
+
value: Value;
|
|
5
|
+
}
|
|
6
|
+
export declare class DoubleLinkedList<Value> {
|
|
7
|
+
length: number;
|
|
8
|
+
start: null | ListNode<Value>;
|
|
9
|
+
end: null | ListNode<Value>;
|
|
10
|
+
push(value: Value): void;
|
|
11
|
+
pop(): Value;
|
|
12
|
+
shift(): Value;
|
|
13
|
+
size(): number;
|
|
14
|
+
}
|
|
15
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tramvai/module-request-limiter",
|
|
3
|
+
"version": "1.75.0",
|
|
4
|
+
"description": "Enable different rendering modes for pages",
|
|
5
|
+
"main": "lib/server.js",
|
|
6
|
+
"module": "lib/server.es.js",
|
|
7
|
+
"browser": "lib/browser.js",
|
|
8
|
+
"typings": "lib/server.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"lib"
|
|
11
|
+
],
|
|
12
|
+
"sideEffects": false,
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git@github.com:Tinkoff/tramvai.git"
|
|
16
|
+
},
|
|
17
|
+
"license": "Apache-2.0",
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tramvai-build --for-publish",
|
|
20
|
+
"watch": "tsc -w",
|
|
21
|
+
"build-for-publish": "true"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"registry": "https://registry.npmjs.org/"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@tinkoff/errors": "0.2.18",
|
|
28
|
+
"on-finished": "^2.3.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/express": "^4.17.9"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@tinkoff/dippy": "0.7.38",
|
|
35
|
+
"@tramvai/core": "1.75.0",
|
|
36
|
+
"@tramvai/tokens-server": "1.75.0",
|
|
37
|
+
"express": "^4.17.1",
|
|
38
|
+
"tslib": "^2.0.3"
|
|
39
|
+
}
|
|
40
|
+
}
|