Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add optional requestWasSuccessful callback for use with skipFailedReq…
…uests and skipSuccessfulRequests options (#241)
  • Loading branch information
nfriedly committed Jul 1, 2021
2 parents 7d3f979 + a137a45 commit e80b436
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 4 deletions.
13 changes: 13 additions & 0 deletions README.md
Expand Up @@ -186,6 +186,19 @@ function (req, res, options) {
}
```

### requestWasSuccessful

Function that is called when `skipFailedRequests` and/or `skipSuccessfulRequests` are set to `true`.
Could be useful for manual decision if request was successful based on request/response.

Defaults to

```js
function (req, res) {
return res.statusCode < 400;
}
```

### skipFailedRequests

When set to `true`, failed requests won't be counted. Request considered failed when:
Expand Down
12 changes: 8 additions & 4 deletions lib/express-rate-limit.js
Expand Up @@ -10,8 +10,12 @@ function RateLimit(options) {
statusCode: 429, // 429 status = Too Many Requests (RFC 6585)
headers: true, //Send custom rate limit header with limit and remaining
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)
// ability to manually decide if request was successful. Used when `skipSuccessfulRequests` and/or `skipFailedRequests` are set to `true`
requestWasSuccessful: function (req, res) {
return res.statusCode < 400;
},
skipFailedRequests: false, // Do not count failed requests
skipSuccessfulRequests: false, // Do not count successful requests
// allows to create custom keys (by default user IP is used)
keyGenerator: function (req /*, res*/) {
return req.ip;
Expand Down Expand Up @@ -114,7 +118,7 @@ function RateLimit(options) {

if (options.skipFailedRequests) {
res.on("finish", function () {
if (res.statusCode >= 400) {
if (!options.requestWasSuccessful(req, res)) {
decrementKey();
}
});
Expand All @@ -130,7 +134,7 @@ function RateLimit(options) {

if (options.skipSuccessfulRequests) {
res.on("finish", function () {
if (res.statusCode < 400) {
if (options.requestWasSuccessful(req, res)) {
options.store.decrement(key);
}
});
Expand Down
73 changes: 73 additions & 0 deletions test/express-rate-limit-test.js
Expand Up @@ -425,6 +425,79 @@ describe("express-rate-limit node module", () => {
assert(!store.decrement_was_called, "decrement was called on the store");
});

it("should decrement hits with success response with custom 'requestWasSuccessful' option", async () => {
const store = new MockStore();
createAppWith(
rateLimit({
skipSuccessfulRequests: true,
requestWasSuccessful: function (req, res) {
return res.statusCode === 200;
},
store: store,
})
);

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

it("should not decrement hits with success response with custom 'requestWasSuccessful' option", async () => {
const store = new MockStore();
createAppWith(
rateLimit({
skipSuccessfulRequests: true,
requestWasSuccessful: function (req, res) {
return res.statusCode === 200;
},
store: store,
})
);

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

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

it("should decrement hits with success response with custom 'requestWasSuccessful' option based on query parameter", async () => {
const store = new MockStore();
createAppWith(
rateLimit({
skipSuccessfulRequests: true,
requestWasSuccessful: function (req) {
return req.query.success === "1";
},
store: store,
})
);

await request(app).get("/?success=1");

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

it("should not decrement hits with failed response with custom 'requestWasSuccessful' option based on query parameter", async () => {
const store = new MockStore();
createAppWith(
rateLimit({
skipSuccessfulRequests: true,
requestWasSuccessful: function (req) {
return req.query.success === "1";
},
store: store,
})
);

await request(app).get("/?success=0");

assert(
!store.decrement_was_called,
"decrement was not 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 e80b436

Please sign in to comment.