@jianghujs/jianghu
Version:
Progressive Enterprise Framework
88 lines (69 loc) • 2.36 kB
JavaScript
;
const { BizError, errorInfoEnum } = require('../../../../app/constant/error');
// todo 这个记录应该是进程间共享的
// 每个 ip 请求统计,记录在内存
const ipStatMap = new Map();
// 获取 ip 的统计情况
const getStat = (ip, rateLimiterDuration, rateLimiterMax) => {
const entry = ipStatMap.get(ip);
const now = new Date().getTime();
const reset = now + rateLimiterDuration;
const expired = entry !== undefined && entry.reset < now;
const hasKey = ipStatMap.has(ip);
const shouldReInit = !hasKey || expired;
if (shouldReInit) {
const initState = {
reset,
remaining: rateLimiterMax,
total: rateLimiterMax,
};
ipStatMap.set(ip, initState);
return initState;
}
entry.remaining = entry.remaining > 0 ? entry.remaining - 1 : 0;
return entry;
};
const checkPathPrefixMatch = (path, pathPrefixList, appId) => {
for (let pathPrefix of pathPrefixList) {
if (pathPrefix.includes('${appId}')) {
pathPrefix = pathPrefix.replace('${appId}', appId);
}
if (path.toLowerCase().startsWith(pathPrefix.toLowerCase())) {
return true;
}
}
};
module.exports = options => {
return async (ctx, next) => {
const { logger, config } = ctx.app;
const { jianghuConfig = {}, appId } = config;
if (!jianghuConfig.enableRateLimiter) {
return await next();
}
// 忽略路径
if (jianghuConfig.rateLimiterIgnorePathPrefix
&& checkPathPrefixMatch(ctx.request.path, jianghuConfig.rateLimiterIgnorePathPrefix, appId)) {
return await next();
}
const ip = ctx.ip;
// ip 白名单
if (jianghuConfig.rateLimiterWhitelist.includes(ip)) {
return await next();
}
const limit = getStat(ip, jianghuConfig.rateLimiterDuration, jianghuConfig.rateLimiterMax);
const currentRemaining = limit.remaining > 0 ? limit.remaining - 1 : 0;
const headers = {
'X-RateLimit-Remaining': currentRemaining,
'X-RateLimit-Reset': limit.reset,
'X-RateLimit-Limit': limit.total,
};
ctx.set(headers);
logger.info('requestRateLimit', limit);
// 放行
if (limit.remaining > 0) return await next();
// 限制
const after = limit.reset - (Date.now());
ctx.set('Retry-After', after);
throw new BizError(errorInfoEnum.request_rate_limit_exceeded);
};
};