Skip to content

Commit

Permalink
use async await and adopt koa eslint rules
Browse files Browse the repository at this point in the history
  • Loading branch information
scttcper committed Mar 8, 2017
1 parent 89eb2f9 commit 1b2ed3f
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 130 deletions.
12 changes: 0 additions & 12 deletions .eslintrc

This file was deleted.

22 changes: 22 additions & 0 deletions .eslintrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Support ES2016 features
parser: babel-eslint

extends: standard

plugins: [
"babel"
]

rules:
arrow-parens: 0
babel/arrow-parens: [2, "as-needed"]
comma-dangle: [2, "always-multiline"]
eqeqeq: 0
no-return-assign: 0 # fails for arrow functions
no-var: 2
semi: [2, always]
space-before-function-paren: [2, never]
yoda: 0
arrow-spacing: 2
dot-location: [2, "property"]
prefer-arrow-callback: 2
97 changes: 50 additions & 47 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,22 @@ const debug = require('debug')('koa-simple-ratelimit');
const ms = require('ms');

function find(db, p) {
return new Promise((resolve) => {
return new Promise((resolve, reject) => {
db.get(p, (err, reply) => {
if (err) {
reject(err);
}
resolve(reply);
});
});
}

function pttl(db, p) {
return new Promise((resolve) => {
return new Promise((resolve, reject) => {
db.pttl(p, (err, reply) => {
if (err) {
reject(err);
}
resolve(reply);
});
});
Expand Down Expand Up @@ -46,7 +52,7 @@ function ratelimit(opts) {
opts.headers.reset = opts.headers.reset || 'X-RateLimit-Reset';
opts.headers.total = opts.headers.total || 'X-RateLimit-Limit';

return function ratelimiter(ctx, next) {
return async function ratelimiter(ctx, next) {
const id = opts.id ? opts.id(ctx) : ctx.ip;

if (id === false) {
Expand All @@ -63,53 +69,50 @@ function ratelimit(opts) {
}

const name = `limit:${id}:count`;
return find(opts.db, name).then((cur) => {
const n = Math.floor(cur);
let t = Date.now();
t += opts.duration || 3600000;
t = new Date(t).getTime() / 1000 || 0;
const cur = await find(opts.db, name);
const n = Math.floor(cur);
let t = Date.now();
t += opts.duration || 3600000;
t = new Date(t).getTime() / 1000 || 0;

const headers = {};
headers[opts.headers.remaining] = opts.max - 1;
headers[opts.headers.reset] = t;
headers[opts.headers.total] = opts.max;
ctx.set(headers);
const headers = {};
headers[opts.headers.remaining] = opts.max - 1;
headers[opts.headers.reset] = t;
headers[opts.headers.total] = opts.max;
ctx.set(headers);

// not existing in redis
if (cur === null) {
debug('remaining %s/%s %s', opts.max - 1, opts.max, id);
opts.db.set(name, opts.max - 1, 'PX', opts.duration || 3600000, 'NX');
return next();
}
// not existing in redis
if (cur === null) {
debug('remaining %s/%s %s', opts.max - 1, opts.max, id);
opts.db.set(name, opts.max - 1, 'PX', opts.duration || 3600000, 'NX');
return next();
}

return pttl(opts.db, name).then((expires) => {
if (n - 1 >= 0) {
// existing in redis
opts.db.decr(name);
debug('remaining %s/%s %s', n - 1, opts.max, id);
headers[opts.headers.remaining] = n - 1;
ctx.set(headers);
return next();
}
if (expires < 0) {
debug(`${name} is stuck. Resetting.`);
debug('remaining %s/%s %s', opts.max - 1, opts.max, id);
opts.db.set(name, opts.max - 1, 'PX', opts.duration || 3600000, 'NX');
return next();
}
// user maxed
headers['Retry-After'] = t;
headers[opts.headers.remaining] = n;
ctx.set(headers);
ctx.status = 429;
const retryTime = ms(expires, { long: true });
ctx.body = `Rate limit exceeded, retry in ${retryTime}`;
if (opts.throw) {
ctx.throw(ctx.status, ctx.body, { headers: headers });
}
return;
});
});
const expires = await pttl(opts.db, name);
if (n - 1 >= 0) {
// existing in redis
opts.db.decr(name);
debug('remaining %s/%s %s', n - 1, opts.max, id);
headers[opts.headers.remaining] = n - 1;
ctx.set(headers);
return next();
}
if (expires < 0) {
debug(`${name} is stuck. Resetting.`);
debug('remaining %s/%s %s', opts.max - 1, opts.max, id);
opts.db.set(name, opts.max - 1, 'PX', opts.duration || 3600000, 'NX');
return next();
}
// user maxed
headers['Retry-After'] = t;
headers[opts.headers.remaining] = n;
ctx.set(headers);
ctx.status = 429;
const retryTime = ms(expires, { long: true });
ctx.body = `Rate limit exceeded, retry in ${retryTime}`;
if (opts.throw) {
ctx.throw(ctx.status, ctx.body, { headers: headers });
}
};
}

Expand Down
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "koa-simple-ratelimit",
"description": "Simple Rate limiter middleware for koa v2",
"version": "2.1.4",
"version": "2.2.0",
"scripts": {
"test": "NODE_ENV=test node_modules/mocha/bin/mocha --reporter spec",
"lint": "eslint index.js test; exit 0",
Expand All @@ -13,14 +13,18 @@
"ms": "^0.7.2"
},
"devDependencies": {
"babel-eslint": "^7.1.1",
"chai": "^3.5.0",
"eslint": "^3.17.1",
"eslint-config-airbnb-base": "^11.1.1",
"eslint-config-standard": "^7.0.1",
"eslint-plugin-babel": "^4.1.1",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-promise": "^3.5.0",
"eslint-plugin-standard": "^2.1.1",
"koa": "^2.1.0",
"mocha": "^3.2.0",
"nyc": "^10.1.2",
"redis": "^2.6.5",
"should": "^11.2.0",
"supertest": "^3.0.0"
},
"nyc": {
Expand Down
17 changes: 5 additions & 12 deletions test/.eslintrc
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
{
"extends": "../.eslintrc",
"env": {
"mocha": true
},
"rules": {
"prefer-arrow-callback": 0,
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }],
"func-names": 0,
"no-unused-vars": 0
}
}
env:
mocha: true

rules:
space-before-blocks: [2, {functions: never, keywords: always}]
27 changes: 14 additions & 13 deletions test/ratelimit.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use strict';

const expect = require('chai').expect;
const Koa = require('koa');
const request = require('supertest');
const should = require('should');
const redis = require('redis');

const ratelimit = require('..');
Expand All @@ -15,6 +15,9 @@ describe('ratelimit middleware', () => {

before((done) => {
db.keys('limit:*', (err, rows) => {
if (err) {
throw new Error(err);
}
rows.forEach(n => db.del(n));
});

Expand All @@ -26,7 +29,7 @@ describe('ratelimit middleware', () => {
let app;

const routeHitOnlyOnce = () => {
guard.should.be.equal(1);
expect(guard).to.eq(1);
};

beforeEach((done) => {
Expand Down Expand Up @@ -79,10 +82,10 @@ describe('ratelimit middleware', () => {
let app;

const routeHitOnlyOnce = () => {
guard.should.be.equal(1);
expect(guard).to.eq(1);
};
const routeHitTwice = () => {
guard.should.be.equal(2);
expect(guard).to.eq(2);
};

beforeEach((done) => {
Expand Down Expand Up @@ -142,7 +145,7 @@ describe('ratelimit middleware', () => {
let app;

const routeHitOnlyOnce = () => {
guard.should.be.equal(1);
expect(guard).to.eq(1);
};

beforeEach((done) => {
Expand All @@ -169,6 +172,7 @@ describe('ratelimit middleware', () => {
request(app.listen())
.get('/')
.expect('X-RateLimit-Remaining', '0')
.expect(routeHitOnlyOnce)
.expect(200)
.end(done);
});
Expand All @@ -179,7 +183,7 @@ describe('ratelimit middleware', () => {
let app;

const routeHitOnlyOnce = () => {
guard.should.be.equal(1);
expect(guard).to.eq(1);
};

beforeEach((done) => {
Expand Down Expand Up @@ -239,12 +243,12 @@ describe('ratelimit middleware', () => {
.get('/')
.set('foo', 'bar')
.expect((res) => {
res.header['x-ratelimit-remaining'].should.equal('0');
expect(res.header['x-ratelimit-remaining']).to.eq('0');
})
.end(done);
});

it('should not limit if `id` returns `false`', (done) => {
it('should not limit if `id` returns `false`', () => {
const app = new Koa();

app.use(ratelimit({
Expand All @@ -254,12 +258,9 @@ describe('ratelimit middleware', () => {
max: 5,
}));

request(app.listen())
return request(app.listen())
.get('/')
.expect((res) => {
res.header.should.not.have.property('x-ratelimit-remaining');
})
.end(done);
.expect((res) => expect(res.header['x-ratelimit-remaining']).to.not.exist);
});

it('should limit using the `id` value', (done) => {
Expand Down
Loading

0 comments on commit 1b2ed3f

Please sign in to comment.