node cache service

This commit is contained in:
Tanya Byrne
2020-08-19 18:10:43 +01:00
parent ec1d2f71a5
commit 1d42e691d3
6 changed files with 122 additions and 65 deletions

8
api/package-lock.json generated
View File

@@ -5294,6 +5294,14 @@
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz",
"integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA=="
},
"node-cache": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz",
"integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==",
"requires": {
"clone": "2.x"
}
},
"node-exceptions": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/node-exceptions/-/node-exceptions-4.0.1.tgz",

View File

@@ -103,6 +103,7 @@
"lodash": "^4.17.19",
"ms": "^2.1.2",
"nanoid": "^3.1.12",
"node-cache": "^5.1.2",
"nodemailer": "^6.4.11",
"ora": "^4.1.1",
"otplib": "^12.0.1",

View File

@@ -14,10 +14,14 @@
{{ redisServer }}
####################################################################################################
# Rate Limiting and caching
# Rate Limiting
{{ rateLimits}}
####################################################################################################
# Caching
{{ caching }}
####################################################################################################
# File Storage

View File

@@ -1,64 +0,0 @@
/**
* RateLimiter using Redis
* and rate-limiter-flexible
* can extend with further options
* in future
*/
import { RequestHandler } from 'express';
import redis from 'redis';
import { RateLimiterRedis } from 'rate-limiter-flexible';
import { HitRateLimitException } from '../exceptions';
import { RedisNotFoundException } from '../exceptions';
import env from '../env';
const redisClient = redis.createClient({
enable_offline_queue: false,
host: env.REDIS_HOST,
port: env.REDIS_PORT,
password: env.REDIS_PASSWORD,
});
const rateLimiter: RequestHandler = (req, res, next) => {
try {
// first need to check that redis is running!
if (!redisClient) {
throw new RedisNotFoundException('Redis client does not exist');
}
// options for the rate limiter are set below. Opts can be found
// at https://github.com/animir/node-rate-limiter-flexible/wiki/Options
const opts = {
storeClient: redisClient,
points: 5, // Number of points
duration: 5, // Number of seconds before consumed points are reset.
// Custom
execEvenly: true, // delay actions after first action - this may need adjusting (leaky bucket)
blockDuration: 0, // Do not block if consumed more than points
keyPrefix: 'rlflx', // must be unique for limiters with different purpose
};
const rateLimiterRedis = new RateLimiterRedis(opts);
rateLimiterRedis
.consume(req.ip)
.then((rateLimiterRes) => {
// everything is ok - can put addition logic in there later for users etc
next();
})
.catch((rejRes) => {
if (rejRes instanceof RedisNotFoundException) {
throw new RedisNotFoundException('Redis insurance limiter not set up');
} else {
// If there is no error, rateLimiterRedis promise rejected with number of ms before next request allowed
const secs = Math.round(rejRes.msBeforeNext / 1000) || 1;
res.set('Retry-After', String(secs));
res.status(429).send('Too Many Requests');
throw new HitRateLimitException(`Too many requests, retry after ${secs}.`);
}
});
} catch (error) {
throw error;
}
};
export default rateLimiter;

View File

@@ -0,0 +1,25 @@
/** node cache service.
* Makes dealing with cache management easier
* in case a new cache is used in future
* sdtTTL is the amount of time a cache should be
* stored, in seconds, before being deleted. Keep
* this is mind
*/
import NodeCache from 'node-cache';
export default class NodeCacheService {
apiCache: NodeCache;
constructor(stdTTLSecs: number, checkPeriodSecs: number) {
// options found at https://github.com/node-cache/node-cache
this.apiCache = new NodeCache({
stdTTL: stdTTLSecs,
checkperiod: checkPeriodSecs,
useClones: false,
});
}
async flushCache() {
this.apiCache.flushAll();
}
}

View File

@@ -0,0 +1,83 @@
import redis from 'redis';
import {
RateLimiterRedis,
RateLimiterMemory,
IRateLimiterStoreOptions,
IRateLimiterOptions,
} from 'rate-limiter-flexible';
import { RedisNotFoundException } from '../exceptions';
import env from '../env';
// options for the rate limiter are set below. Opts can be found
// at https://github.com/animir/node-rate-limiter-flexible/wiki/Options
let rateLimiterConfig = new RateLimiterMemory(getRateLimiterConfig());
// need to pick redis or memory
if (env.RATE_LIMIT_TYPE === 'redis') {
rateLimiterConfig = new RateLimiterRedis(getRateLimiterRedisConfig());
}
export default rateLimiterConfig;
function getRateLimiterConfig(): IRateLimiterOptions {
const config: any = {};
config.keyPrefix = 'rlflx';
for (const [key, value] of Object.entries(env)) {
if (key === 'CONSUMED_POINTS_LIMIT') {
config.points = value;
continue;
}
if (key === 'CONSUMED_RESET_DURATION') {
config.duration = value;
continue;
}
}
return config;
}
function getRateLimiterRedisConfig(): IRateLimiterStoreOptions {
const redisConfig: any = {};
const redisClient = redis.createClient({
enable_offline_queue: false,
host: env.REDIS_HOST,
port: env.REDIS_PORT,
password: env.REDIS_PASSWORD,
});
if (!redisClient) {
throw new RedisNotFoundException('Redis client does not exist');
}
redisConfig.keyPrefix = 'rlflx';
redisConfig.storeClient = redisClient;
for (const [key, value] of Object.entries(env)) {
if (key === 'CONSUMED_POINTS_LIMIT') {
redisConfig.points = value;
continue;
}
if (key === 'CONSUMED_RESET_DURATION') {
redisConfig.duration = value;
continue;
}
if (key === 'EXEC_EVENLY') {
redisConfig.execEvenly = value;
continue;
}
if (key === 'BLOCK_POINT_DURATION') {
redisConfig.blockDuration = value;
continue;
}
if (key === 'INMEMORY_BLOCK_CONSUMED') {
redisConfig.inmemoryBlockOnConsumed = value;
continue;
}
if (key === 'INMEMEMORY_BLOCK_DURATION') {
redisConfig.inmemoryBlockDuration = value;
continue;
}
}
return redisConfig;
}