Skip to content

Commit

Permalink
EWN-17662/express-rate-limit - add skipSuccessfulCallback (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
has77a committed Jun 25, 2021
1 parent 7d3f979 commit 52a1a89
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 8 deletions.
3 changes: 3 additions & 0 deletions README.md
Expand Up @@ -201,6 +201,9 @@ Defaults to `false`.
### skipSuccessfulRequests

When set to `true` successful requests (response status < 400) won't be counted.

Could be set to `skipSuccessfulRequests: function (req, res): boolean`. Used to manually decide if request was successful and therefore should not be counted.

(Technically they are counted and then un-counted, so a large number of slow requests all at once could still trigger a rate-limit. This may be fixed in a future release.)

Defaults to `false`.
Expand Down
27 changes: 19 additions & 8 deletions lib/express-rate-limit.js
Expand Up @@ -12,6 +12,10 @@ function RateLimit(options) {
draft_polli_ratelimit_headers: false, //Support for the new RateLimit standardization headers
skipFailedRequests: false, // Do not count failed requests (status >= 400)
skipSuccessfulRequests: false, // Do not count successful requests (status < 400)
// allows to manually decide if request was successful and should be skipped
// skipSuccessfulRequests: function (req, res) {
// return res.statusCode < 400;
// },
// allows to create custom keys (by default user IP is used)
keyGenerator: function (req /*, res*/) {
return req.ip;
Expand Down Expand Up @@ -100,9 +104,18 @@ function RateLimit(options) {
}
}

const skipSuccessfulRequests =
typeof options.skipSuccessfulRequests === "function"
? options.skipSuccessfulRequests
: function (req, res) {
return options.skipSuccessfulRequests
? res.statusCode < 400
: false;
};

if (
options.skipFailedRequests ||
options.skipSuccessfulRequests
skipSuccessfulRequests(req, res)
) {
let decremented = false;
const decrementKey = () => {
Expand All @@ -128,13 +141,11 @@ function RateLimit(options) {
res.on("error", () => decrementKey());
}

if (options.skipSuccessfulRequests) {
res.on("finish", function () {
if (res.statusCode < 400) {
options.store.decrement(key);
}
});
}
res.on("finish", function () {
if (skipSuccessfulRequests(req, res)) {
options.store.decrement(key);
}
});
}

if (max && current === max + 1) {
Expand Down
31 changes: 31 additions & 0 deletions test/express-rate-limit-test.js
Expand Up @@ -425,6 +425,37 @@ describe("express-rate-limit node module", () => {
assert(!store.decrement_was_called, "decrement was called on the store");
});

it("should decrement hits with success response and skipSuccessfulRequests: statusCode < 400", async () => {
const store = new MockStore();
createAppWith(
rateLimit({
skipSuccessfulRequests: function (req, res) {
return res.statusCode < 400;
},
store: store,
})
);

await request(app).get("/").expect(200);
assert(store.decrement_was_called, "decrement was not called on the store");
});

it("should not decrement hits with failed response and skipSuccessfulRequests: statusCode < 400", async () => {
const store = new MockStore();
createAppWith(
rateLimit({
skipSuccessfulRequests: function (req, res) {
return res.statusCode < 400;
},
store: store,
})
);

await request(app).get("/bad_response_status").expect(403);

assert(!store.decrement_was_called, "decrement was called on the store");
});

it("should decrement hits with failed response and skipFailedRequests", async () => {
const store = new MockStore();
createAppWith(
Expand Down

0 comments on commit 52a1a89

Please sign in to comment.