limiter.js
3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
'use strict';
const cache = global.yoho.cache.master;
const _ = require('lodash');
const logger = global.yoho.logger;
const config = global.yoho.config;
const helpers = global.yoho.helpers;
let ONE_DAY = 60 * 60 * 24;
const MAX_QPS = config.maxQps;
const PAGES = {
'/product/\\/pro_([\\d]+)_([\\d]+)\\/(.*)/': 5,
'/product/list/index': 5
};
const IP_WHITE_LIST = [
'106.38.38.146',
'218.94.75.58'
];
function urlJoin(a, b) {
if (_.endsWith(a, '/') && _.startsWith(b, '/')) {
return a + b.substring(1, b.length);
} else if (!_.endsWith(a, '/') && !_.startsWith(b, '/')) {
return a + '/' + b;
} else {
return a + b;
}
}
module.exports = (req, res, next) => {
let remoteIp = req.get('X-Forwarded-For') || '';
if (remoteIp.indexOf(',') > 0) {
let arr = remoteIp.split(',');
remoteIp = arr[0];
}
if (remoteIp &&
!_.get(req.app.locals, 'pc.sys.noLimiter') &&
!_.includes(IP_WHITE_LIST, remoteIp)) { // 判断获取remoteIp成功,并且开关未关闭
let key = `pc:limiter:${remoteIp}`;
res.on('render', function() {
let route = req.route ? req.route.path : '';
let appPath = req.app.mountpath;
if (_.isArray(route) && route.length > 0) {
route = route[0];
}
let pageKey = urlJoin(appPath, route.toString()); // route may be a regexp
let pageIncr = PAGES[pageKey] || 0;
if (pageIncr > 0) {
cache.incrAsync(key, pageIncr);
}
});
const limiterPage = () => {
let refer = req.method === 'GET' ? req.get('Referer') : '';
let limitAPI = helpers.urlFormat('/3party/check', {refer: refer});
let limitPage = helpers.urlFormat('/3party/check', {refer: req.protocol + '://' + req.get('host') + req.originalUrl});
if (_.indexOf(['/3party/check', '/passport/imagesNode', '/passport/cert/headerTip'], req.path) >= 0) {
return next();
}
if (req.xhr) {
return res.json({
code: 400,
data: {refer: limitAPI}
});
}
return res.redirect(limitPage);
};
cache.getAsync(key).then((result) => {
if (result && _.isNumber(result)) {
if (result > MAX_QPS) { // 判断 qps
cache.touch(key, ONE_DAY);
logger.info('req limit', key);
return limiterPage();
} else {
cache.incrAsync(key, 1); // qps + 1
return next();
}
} else {
cache.setAsync(key, 1, 60); // 设置key,1m失效
return next();
}
}).catch((err) => {
logger.error(`request limiter get key[${key}] from cache error.`, err);
return next();
});
} else {
return next();
}
};