leaky-bucket
Advanced tools
Comparing version 4.0.1 to 4.1.0
{ | ||
"name": "leaky-bucket", | ||
"description": "A fast and efficient leaky bucket implementation", | ||
"version": "4.0.1", | ||
"version": "4.1.0", | ||
"homepage": "https://github.com/linaGirl/leaky-bucket", | ||
@@ -6,0 +6,0 @@ "author": "Lina van der Weg <lina@vanderweg.ch> (http://vanderweg.ch/)", |
207
README.md
@@ -5,10 +5,15 @@ # leaky-bucket | ||
Leaky buckets are often used to rate limits calls to APIs. They can be used on the server, to make sure | ||
the client does not send too many requests and on the client, to make sure to not to send too many | ||
requests to a server rate limiting using a leaky bucket. Leaky buckets are burstable: if a server lets a | ||
client send 10 requests per minute, it normally lets the user burst those 10 reuests. after that only one | ||
request per 6 seconds may be sent (60 seconds / 10 requests). If the user stops sending requests, the bucket | ||
is filled up again so that the user may send a burst of requests again. | ||
Leaky buckets are often used to rate limits calls to APIs. They can be | ||
used on the server, to make sure the client does not send too many | ||
requests in a short time or on the client, to make sure to not to send | ||
too many requests to a server, that is rate limiting using a leaky | ||
bucket. | ||
Leaky buckets are burstable: if a server lets a client send 10 requests | ||
per minute, it normally lets the user burst those 10 requests in a short | ||
time. After that only one request every 6 seconds may be sent (60 seconds | ||
/ 10 requests). If the user stops sending requests, the bucket is filled | ||
up again so that the user may send a burst of requests again. | ||
New in Version 4: | ||
@@ -21,2 +26,4 @@ - dropped node.js support for node <12 (es modules) | ||
- added the canExecuteNow method | ||
- added the getCapacity method | ||
- added the getCurrentCapacity method | ||
@@ -33,13 +40,2 @@ | ||
Sets up the leaky bucket. Accpets three optional options | ||
- capacity: this is the amount of items that may be processed per interval, if the items cost is 1 (which is the default) | ||
- interval: this is the interval, in which the capacity may be used | ||
- timeout: defines, how long it takes until items are rejected due to an overflow. defaults to the value of the interval, so that the overflow occurs at the same time the bucket is empty. | ||
- debug: print log messages using console.log | ||
- initialCapacity: the bucket starts normally with the full capacity, this lets you override this with a custom value | ||
- idleTimeout: if set, the bucket will emit the idleTimeout event after the specified amount of milliseconds as soon the bucket is empty and at full capacity | ||
```javascript | ||
@@ -55,21 +51,68 @@ import LeakyBucket from 'leaky-bucket'; | ||
#### option: capacity | ||
### throttle() | ||
The capacity defines how many requests may be sent oer interval. If the | ||
capacity is 100 and the interval is 60 seconds and a request has a cost | ||
of 1, every 60 seconds 1000 request may be processed. If the request | ||
cost is 4, jsut 25 requests may be processed every 60 seconds. | ||
The throttle method is used to delay items until the bucket leaks them, thus rate limiting them. If the bucket is overflowing, which is when items cannot be executed within the timeout, the throttle method will reject using an error. | ||
The complete capacity can be used in a burst. This means for the example | ||
above, 100 requests can be processed immediately. Thereafter every request | ||
has to weit for 0.6 seconds (60 seconds / 100 capacity) if the request | ||
cost is 1. | ||
This method accepts two optional paramters: | ||
#### option: interval | ||
- cost: the cost of the item, defaults to 1 | ||
- append: if the ittem should be appended or added at the first position in the queue, defaults to true | ||
The interval defines, how much seconds it takes to refill the bucket to | ||
its full capacity. The bucket is not filled every interval, but continously. | ||
#### option timeout | ||
Normally, the bucket will throw errors when the throttle() method is called | ||
when the bucket is empty. When a timeout is defined, the bucket will queue | ||
items as long they can be executed within the timeout. Defaults to 0, which | ||
will not queue any items if the bucket is empty. | ||
#### option initialCapacity | ||
Some rate limited services will start out with an empty bucket, or refill | ||
the bucket not continusly but in an interval. This option can be used to | ||
set a starting capacity beween 0 and the configured capacity. If set to 0 | ||
and a request shall be processed immediately and the timeout is 0, the | ||
bucket will reject the request. | ||
#### idleTimeout | ||
If this option is set, the bucket will emti a idleTimeout event after the | ||
bucket is filled completely and no requests are waiting. Configured in | ||
milliseconds. | ||
#### debug | ||
If set to true, the bucket will print debug logs using console.log() | ||
### async bucket.throttle(cost = 1) | ||
This is the main method used for procsessing requests. If this method is | ||
called and the bucket has more capacity left that the request costs, it | ||
will continue. If the capacity is less than the cost, it will throw an | ||
error. If the timeout option is configured, the method will sleep until | ||
there is enough capacity to process it. | ||
This method accepts two optional parameters: | ||
- cost: the cost of the item, defaults to 1. | ||
- append: if set to false, the item is added at the beginning of the queue and will thus executed before all other queued items. Defaults to true; | ||
```javascript | ||
/// throttle an individual item | ||
// throttle an individual item | ||
await bucket.throttle(); | ||
doThings(); | ||
// Throttle aset of items, waiting for each one to complete, before it's added to the bucket | ||
// Throttle a set of items, waiting for each one to complete before the next one is executed | ||
for (const item of set.values()) { | ||
await bucket.throttle(); | ||
doThings(); | ||
@@ -79,6 +122,5 @@ } | ||
// throttle items, add them to the bucket in paralle | ||
// throttle multiple items and wait untiul all are finished | ||
await Promise.all(Array.from(set).map(async(item) => { | ||
await bucket.throttle(); | ||
doThings(); | ||
@@ -89,9 +131,10 @@ })); | ||
### pause() | ||
### pause(seconds) | ||
The pause method can be use to pause the bucket for n seconds until it is allwed to resume. | ||
The pause method can be use to pause the bucket for n seconds. Same as the throttle call but does not throw errors when the bucket is over its capacity. | ||
```javascript | ||
bucket.pause(); | ||
bucket.pause(2); | ||
@@ -101,5 +144,16 @@ ```` | ||
### pauseByCost(cost) | ||
The pause method can be use to pause the bucket for a specific cost. Same as the throttle call but does not throw errors when the bucket is over its capacity. | ||
```javascript | ||
bucket.pauseByCost(300); | ||
```` | ||
### pay(cost) | ||
removes the defined cost from the bucket | ||
Removes the defined cost from the bucket without taking any action. Reduces the current capacity. | ||
@@ -115,3 +169,3 @@ | ||
shuts down the bucket. Removes all pending items wihtout executing them. The bucket cannot be reused thereafter! | ||
Shuts down the bucket, clears all timers. Removes all pending items wihtout executing them. The bucket cannot be reused thereafter! | ||
@@ -124,6 +178,54 @@ | ||
### getCapacity() | ||
Returns the total capacity of the bucket. | ||
```javascript | ||
const capacity = bucket.getCapacity(); | ||
```` | ||
### getCurrentCapacity() | ||
Returns the current capacity of the bucket. | ||
```javascript | ||
const currentCapacity = bucket.getCurrentCapacity(); | ||
```` | ||
### setTimeout(seconds) | ||
Sets the amount of seconds the bucket queue items before it starts to reject them. Same as the timeout option in the constructor | ||
```javascript | ||
bucket.setTimeout(300); | ||
```` | ||
### setInterval(seconds) | ||
Sets the interval it takes to refill the bucket completely. Same as the interval option in the constructor | ||
```javascript | ||
bucket.setInterval(60); | ||
```` | ||
### setCapacity(capacity) | ||
Sets the capacity of the bucket. Same as the capacity option in the constructor | ||
```javascript | ||
bucket.setTimeout(1000); | ||
```` | ||
### event 'idleTimeout' | ||
Is emitted, if the bucket is at full capacity and idle for N milliseconds | ||
This event is emitted, if the bucket is at full capacity and idle for N milliseconds | ||
```javascript | ||
@@ -146,3 +248,24 @@ const bucket = new Bucket({ | ||
### event 'idle' | ||
This event is emitted, when the bucket is idle, thus no items are waiting to be executed. | ||
```javascript | ||
const bucket = new Bucket({ | ||
capacity: 60, | ||
interval: 60, | ||
idleTimeout: 2000, | ||
}); | ||
bucket.on('idle', (bucketInstance) => { | ||
console.log('bucket is idling'); | ||
}); | ||
// you may remove the listener if you want | ||
bucket.off('idle'); | ||
```` | ||
## Browser | ||
@@ -175,6 +298,8 @@ | ||
const app = express() | ||
const buckets = new Map(); | ||
const costOfOperation = 50; | ||
app.use((req, res, next) => { | ||
// your cookie should be secure and not guessable by the user | ||
const userUid = req.cookies.userUid; | ||
@@ -185,3 +310,3 @@ | ||
const bucket = new Bucket({ | ||
capacity: 60, | ||
capacity: 1000, | ||
interval: 60, | ||
@@ -191,3 +316,3 @@ idleTimeout: 120 * 1000 // 120 seconds | ||
// end the bucket, remove it from memory | ||
// end the bucket, remove it from memory when it becomes idle | ||
bucket.on('idleTimeout', () => { | ||
@@ -198,3 +323,3 @@ bucket.end(); | ||
// store for later use | ||
// store for later access | ||
buckets.set(userUid, bucket); | ||
@@ -218,6 +343,10 @@ } | ||
// all set and fine, continue to process the request | ||
res.set('x-request-cost', costOfOperation); | ||
res.set('x-rate-limit', `${bucket.currentCapacity}/${bucket.capacity}`); | ||
res.set('x-rate-limit-cost', costOfOperation); | ||
res.set('x-rate-limit-bucket-size', bucket.getCapacity()); | ||
res.set('x-rate-limit-remaining-size', bucket.getCurrentCapacity()); | ||
next(); | ||
}); | ||
app.listen(8080); | ||
```` |
@@ -133,4 +133,23 @@ import EventEmitter from './EventEmitter.js'; | ||
/** | ||
* returns the capacity | ||
* | ||
* @return {number} The capacity. | ||
*/ | ||
getCapacity() { | ||
return this.capacity; | ||
} | ||
/** | ||
* returns the current capacity | ||
* | ||
* @return {number} The current capacity. | ||
*/ | ||
getCurrentCapacity() { | ||
return this.currentCapacity; | ||
} | ||
/** | ||
* either executes directly when enough capacity is present or delays the | ||
@@ -190,4 +209,8 @@ * execution until enough capacity is available. | ||
if (this.queue.length === 0 && this.emptyPromiseResolver) { | ||
this.emptyPromiseResolver(); | ||
if (this.queue.length === 0) { | ||
if (this.emptyPromiseResolver) { | ||
this.emptyPromiseResolver(); | ||
} | ||
this.emit('idle', this); | ||
} | ||
@@ -198,10 +221,2 @@ } | ||
/** | ||
* Determines ability to execute an item now. | ||
* | ||
* @param {number} cost The cost | ||
*/ | ||
canExecuteNow(cost) { | ||
this.currentCapacity >= cost; | ||
} | ||
@@ -213,2 +228,4 @@ | ||
* that is emitted | ||
* | ||
* @private | ||
*/ | ||
@@ -340,2 +357,4 @@ async isEmpty() { | ||
* it shoudl reach ful capacity. used for the idle event | ||
* | ||
* @private | ||
*/ | ||
@@ -358,2 +377,4 @@ startRefillTimer() { | ||
* Stops the refill timer. | ||
* | ||
* @private | ||
*/ | ||
@@ -370,2 +391,4 @@ stopRefillTimer() { | ||
* Stops the idle timer. | ||
* | ||
* @private | ||
*/ | ||
@@ -382,2 +405,4 @@ stopIdleTimer() { | ||
* at full capacity | ||
* | ||
* @private | ||
*/ | ||
@@ -461,3 +486,3 @@ startIdleTimer() { | ||
/** | ||
* pasue the bucket for the given cost. means that an item is added in the | ||
* pause the bucket for the given cost. means that an item is added in the | ||
* front of the queue with the cost passed to this method | ||
@@ -464,0 +489,0 @@ * |
38920
791
337