axios-rate-limit 1.4.0 → 1.5.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 +18 -11
- package/package.json +2 -2
- package/src/index.js +130 -28
- package/typings/index.d.ts +10 -3
package/README.md
CHANGED
|
@@ -23,21 +23,28 @@ npm install axios-rate-limit
|
|
|
23
23
|
import axios from 'axios';
|
|
24
24
|
import rateLimit from 'axios-rate-limit';
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
http.get('https://example.com/api/v1/users.json?page=
|
|
33
|
-
http.
|
|
34
|
-
|
|
35
|
-
// options hot-reloading
|
|
26
|
+
const http = rateLimit(axios.create(), {
|
|
27
|
+
limits: [
|
|
28
|
+
{ maxRequests: 5, duration: '2s' },
|
|
29
|
+
{ maxRequests: 2, duration: '500ms' }
|
|
30
|
+
]
|
|
31
|
+
})
|
|
32
|
+
http.get('https://example.com/api/v1/users.json?page=1')
|
|
33
|
+
http.getQueue()
|
|
34
|
+
|
|
35
|
+
// options hot-reloading (same options as constructor)
|
|
36
36
|
http.setMaxRPS(3)
|
|
37
37
|
http.getMaxRPS() // 3
|
|
38
|
-
http.setRateLimitOptions({ maxRequests: 6, perMilliseconds: 150 })
|
|
38
|
+
http.setRateLimitOptions({ maxRequests: 6, perMilliseconds: 150 })
|
|
39
|
+
http.setRateLimitOptions({ maxRequests: 10, duration: '1s' })
|
|
40
|
+
http.setRateLimitOptions({ limits: [{ maxRequests: 3, duration: '1s' }, { maxRequests: 1, duration: '200ms' }] })
|
|
39
41
|
```
|
|
40
42
|
|
|
43
|
+
## Tech Details
|
|
44
|
+
|
|
45
|
+
The axios-rate-limit implements fixed-window, queued rate limiter. The main disadvantage of this
|
|
46
|
+
approach is possibility of bursts at window boundaries in case of limit hit.
|
|
47
|
+
|
|
41
48
|
## Alternatives
|
|
42
49
|
|
|
43
50
|
Consider using Axios built-in [rate-limiting](https://www.npmjs.com/package/axios#user-content--rate-limiting) functionality.
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,6 +1,100 @@
|
|
|
1
|
+
var DURATION_MSG = " Expected format: number+unit ms, s, m, h (e.g. '1s')."
|
|
2
|
+
|
|
3
|
+
var DURATION_UNITS = { ms: 1, s: 1000, m: 60000, h: 3600000 }
|
|
4
|
+
|
|
5
|
+
function throwDurationError (value) {
|
|
6
|
+
var msg = "Unrecognized duration: '" + String(value) + "'." + DURATION_MSG
|
|
7
|
+
throw new Error(msg)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function parseDuration (value) {
|
|
11
|
+
if (typeof value === 'number' && !isNaN(value)) {
|
|
12
|
+
if (value < 0) throwDurationError(value)
|
|
13
|
+
return value
|
|
14
|
+
}
|
|
15
|
+
if (typeof value !== 'string') {
|
|
16
|
+
throwDurationError(value)
|
|
17
|
+
}
|
|
18
|
+
var s = value.trim()
|
|
19
|
+
var num
|
|
20
|
+
var mult
|
|
21
|
+
if (s.length >= 2 && s.slice(-2) === 'ms') {
|
|
22
|
+
num = parseFloat(s.slice(0, -2))
|
|
23
|
+
mult = DURATION_UNITS.ms
|
|
24
|
+
} else if (s.length >= 1) {
|
|
25
|
+
var u = s.slice(-1)
|
|
26
|
+
mult = DURATION_UNITS[u]
|
|
27
|
+
if (mult == null) throwDurationError(value)
|
|
28
|
+
num = parseFloat(s.slice(0, -1))
|
|
29
|
+
} else {
|
|
30
|
+
throwDurationError(value)
|
|
31
|
+
}
|
|
32
|
+
if (isNaN(num) || num < 0) {
|
|
33
|
+
throwDurationError(value)
|
|
34
|
+
}
|
|
35
|
+
return num * mult
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function buildWindows (options) {
|
|
39
|
+
var limits = options && options.limits
|
|
40
|
+
if (limits && limits.length > 0) {
|
|
41
|
+
return limits.map(function (limit, i) {
|
|
42
|
+
var max = limit.maxRequests
|
|
43
|
+
if (typeof max !== 'number' || !isFinite(max) || max <= 0) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
'Invalid rate limit option at limits[' + i + ']: ' +
|
|
46
|
+
'maxRequests is required and must be a positive number.'
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
var perMs = parseDuration(limit.duration)
|
|
50
|
+
if (typeof perMs !== 'number' || !isFinite(perMs) || perMs <= 0) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
'Invalid rate limit option at limits[' + i + ']: ' +
|
|
53
|
+
'duration must be a positive finite number.'
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
return { count: 0, max: max, perMs: perMs, timeoutId: null }
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
var maxRequests = options.maxRequests
|
|
60
|
+
var perMs
|
|
61
|
+
if (options.maxRPS != null) {
|
|
62
|
+
maxRequests = options.maxRPS
|
|
63
|
+
perMs = 1000
|
|
64
|
+
} else {
|
|
65
|
+
var optD = options.duration
|
|
66
|
+
perMs = optD != null ? parseDuration(optD) : options.perMilliseconds
|
|
67
|
+
}
|
|
68
|
+
if (typeof perMs !== 'number' || !isFinite(perMs) || perMs <= 0) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
'Invalid rate limit options: one of maxRPS, duration, or ' +
|
|
71
|
+
'perMilliseconds is required and must be positive.'
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
var maxInvalid = typeof maxRequests !== 'number' ||
|
|
75
|
+
!isFinite(maxRequests) || maxRequests <= 0
|
|
76
|
+
if (maxInvalid) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
'Invalid rate limit options: maxRequests is required and ' +
|
|
79
|
+
'must be a positive number.'
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
return [{ count: 0, max: maxRequests, perMs: perMs, timeoutId: null }]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function clearWindowsTimeouts (windows) {
|
|
86
|
+
if (!windows) return
|
|
87
|
+
for (var i = 0; i < windows.length; i++) {
|
|
88
|
+
if (windows[i].timeoutId != null) {
|
|
89
|
+
clearTimeout(windows[i].timeoutId)
|
|
90
|
+
windows[i].timeoutId = null
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
1
95
|
function AxiosRateLimit (axios) {
|
|
2
96
|
this.queue = []
|
|
3
|
-
this.
|
|
97
|
+
this.windows = []
|
|
4
98
|
|
|
5
99
|
this.interceptors = {
|
|
6
100
|
request: null,
|
|
@@ -14,8 +108,9 @@ function AxiosRateLimit (axios) {
|
|
|
14
108
|
}
|
|
15
109
|
|
|
16
110
|
AxiosRateLimit.prototype.getMaxRPS = function () {
|
|
17
|
-
var
|
|
18
|
-
|
|
111
|
+
var w = this.windows[0]
|
|
112
|
+
if (!w) return 0
|
|
113
|
+
return w.max / (w.perMs / 1000)
|
|
19
114
|
}
|
|
20
115
|
|
|
21
116
|
AxiosRateLimit.prototype.getQueue = function () {
|
|
@@ -30,12 +125,11 @@ AxiosRateLimit.prototype.setMaxRPS = function (rps) {
|
|
|
30
125
|
}
|
|
31
126
|
|
|
32
127
|
AxiosRateLimit.prototype.setRateLimitOptions = function (options) {
|
|
33
|
-
if (options
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
128
|
+
if (!options) return
|
|
129
|
+
var newWindows = buildWindows(options)
|
|
130
|
+
clearWindowsTimeouts(this.windows)
|
|
131
|
+
this.windows = newWindows
|
|
132
|
+
this.shift()
|
|
39
133
|
}
|
|
40
134
|
|
|
41
135
|
AxiosRateLimit.prototype.enable = function (axios) {
|
|
@@ -100,34 +194,40 @@ AxiosRateLimit.prototype.shiftInitial = function () {
|
|
|
100
194
|
|
|
101
195
|
AxiosRateLimit.prototype.shift = function () {
|
|
102
196
|
if (!this.queue.length) return
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
197
|
+
var windows = this.windows
|
|
198
|
+
for (var i = 0; i < windows.length; i++) {
|
|
199
|
+
if (windows[i].count === windows[i].max) {
|
|
200
|
+
var tid = windows[i].timeoutId
|
|
201
|
+
if (tid && typeof tid.ref === 'function') {
|
|
202
|
+
tid.ref()
|
|
203
|
+
}
|
|
204
|
+
return
|
|
106
205
|
}
|
|
107
|
-
|
|
108
|
-
return
|
|
109
206
|
}
|
|
110
207
|
|
|
111
208
|
var queued = this.queue.shift()
|
|
112
209
|
var resolved = queued.resolve()
|
|
113
210
|
|
|
114
|
-
if (this.timeslotRequests === 0) {
|
|
115
|
-
this.timeoutId = setTimeout(function () {
|
|
116
|
-
this.timeslotRequests = 0
|
|
117
|
-
this.shift()
|
|
118
|
-
}.bind(this), this.perMilliseconds)
|
|
119
|
-
|
|
120
|
-
if (typeof this.timeoutId.unref === 'function') {
|
|
121
|
-
if (this.queue.length === 0) this.timeoutId.unref()
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
211
|
if (!resolved) {
|
|
126
|
-
this.shift()
|
|
212
|
+
this.shift()
|
|
127
213
|
return
|
|
128
214
|
}
|
|
129
215
|
|
|
130
|
-
|
|
216
|
+
var self = this
|
|
217
|
+
for (var j = 0; j < windows.length; j++) {
|
|
218
|
+
var w = windows[j]
|
|
219
|
+
w.count += 1
|
|
220
|
+
if (w.count === 1) {
|
|
221
|
+
w.timeoutId = setTimeout(function (win) {
|
|
222
|
+
win.count = 0
|
|
223
|
+
win.timeoutId = null
|
|
224
|
+
self.shift()
|
|
225
|
+
}.bind(null, w), w.perMs)
|
|
226
|
+
if (typeof w.timeoutId.unref === 'function') {
|
|
227
|
+
if (this.queue.length === 0) w.timeoutId.unref()
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
131
231
|
}
|
|
132
232
|
|
|
133
233
|
/**
|
|
@@ -157,7 +257,9 @@ AxiosRateLimit.prototype.shift = function () {
|
|
|
157
257
|
*/
|
|
158
258
|
function axiosRateLimit (axios, options) {
|
|
159
259
|
var rateLimitInstance = new AxiosRateLimit(axios)
|
|
160
|
-
|
|
260
|
+
if (options != null) {
|
|
261
|
+
rateLimitInstance.setRateLimitOptions(options)
|
|
262
|
+
}
|
|
161
263
|
|
|
162
264
|
axios.getQueue = AxiosRateLimit.prototype.getQueue.bind(rateLimitInstance)
|
|
163
265
|
axios.getMaxRPS = AxiosRateLimit.prototype.getMaxRPS.bind(rateLimitInstance)
|
package/typings/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export interface RateLimitedAxiosInstance extends AxiosInstance {
|
|
|
8
8
|
getQueue: () => RateLimitRequestHandler[],
|
|
9
9
|
getMaxRPS: () => number,
|
|
10
10
|
setMaxRPS: (rps: number) => void,
|
|
11
|
-
setRateLimitOptions: (options
|
|
11
|
+
setRateLimitOptions: (options?: rateLimitOptions) => void,
|
|
12
12
|
// enable(axios: any): void,
|
|
13
13
|
// handleRequest(request:any):any,
|
|
14
14
|
// handleResponse(response: any): any,
|
|
@@ -17,10 +17,17 @@ export interface RateLimitedAxiosInstance extends AxiosInstance {
|
|
|
17
17
|
// shift():any
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
export type RateLimitEntry = {
|
|
21
|
+
maxRequests: number,
|
|
22
|
+
duration: string | number
|
|
23
|
+
};
|
|
24
|
+
|
|
20
25
|
export type rateLimitOptions = {
|
|
21
26
|
maxRequests?: number,
|
|
22
27
|
perMilliseconds?: number,
|
|
23
|
-
maxRPS?: number
|
|
28
|
+
maxRPS?: number,
|
|
29
|
+
duration?: string | number,
|
|
30
|
+
limits?: RateLimitEntry[]
|
|
24
31
|
};
|
|
25
32
|
|
|
26
33
|
/**
|
|
@@ -50,5 +57,5 @@ export type rateLimitOptions = {
|
|
|
50
57
|
*/
|
|
51
58
|
export default function axiosRateLimit(
|
|
52
59
|
axiosInstance: AxiosInstance,
|
|
53
|
-
options
|
|
60
|
+
options?: rateLimitOptions
|
|
54
61
|
): RateLimitedAxiosInstance;
|