Showing
8 changed files
with
308 additions
and
0 deletions
doraemon/middleware/limiter.js
0 → 100644
doraemon/middleware/limiter/index.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +const _ = require('lodash'); | ||
4 | +const logger = global.yoho.logger; | ||
5 | +const ip = require('./rules/ip-list'); | ||
6 | +const userAgent = require('./rules/useragent'); | ||
7 | +const qpsLimiter = require('./rules/qps-limit'); | ||
8 | +const fakerLimiter = require('./rules/faker-limit'); | ||
9 | +const captchaPolicy = require('./policies/captcha'); | ||
10 | +const reporterPolicy = require('./policies/reporter'); | ||
11 | + | ||
12 | +const IP_WHITE_LIST = [ | ||
13 | + // '106.38.38.146', | ||
14 | + // '218.94.75.58' | ||
15 | +]; | ||
16 | + | ||
17 | +const limiter = (rule, policy, context) => { | ||
18 | + return rule(context, policy); | ||
19 | +}; | ||
20 | + | ||
21 | +module.exports = (req, res, next) => { | ||
22 | + let remoteIp = req.get('X-Forwarded-For') || req.connection.remoteAddress; | ||
23 | + logger.debug('request remote ip: ', remoteIp); | ||
24 | + | ||
25 | + if (remoteIp.indexOf(',') > 0) { | ||
26 | + let arr = remoteIp.split(','); | ||
27 | + | ||
28 | + remoteIp = arr[0]; | ||
29 | + } | ||
30 | + | ||
31 | + const excluded = _.includes(IP_WHITE_LIST, remoteIp); | ||
32 | + const enabled = !_.get(req.app.locals, 'wap.sys.noLimiter'); | ||
33 | + | ||
34 | + // 判断获取remoteIp成功,并且开关未关闭 | ||
35 | + if (enabled && remoteIp && !excluded) { | ||
36 | + const context = { | ||
37 | + req: req, | ||
38 | + res: res, | ||
39 | + next: next, | ||
40 | + remoteIp: remoteIp | ||
41 | + }; | ||
42 | + | ||
43 | + Promise.all([ | ||
44 | + limiter(userAgent, captchaPolicy, context), | ||
45 | + limiter(ip, captchaPolicy, context), | ||
46 | + limiter(qpsLimiter, captchaPolicy, context), | ||
47 | + //limiter(fakerLimiter, reporterPolicy, context) | ||
48 | + ]).then((results) => { | ||
49 | + let allPass = true, exclusion = false, policy = null; | ||
50 | + | ||
51 | + logger.debug('limiter result: ' + JSON.stringify(results)); | ||
52 | + | ||
53 | + _.forEach(results, (result) => { | ||
54 | + if (typeof result === 'object' && !exclusion) { | ||
55 | + exclusion = result.exclusion; | ||
56 | + } | ||
57 | + | ||
58 | + if (!excluded && typeof result === 'function') { | ||
59 | + allPass = false; | ||
60 | + } | ||
61 | + | ||
62 | + if (typeof result === 'function') { | ||
63 | + policy = result; | ||
64 | + } | ||
65 | + }); | ||
66 | + | ||
67 | + if (exclusion) { | ||
68 | + return next(); | ||
69 | + } else if (!allPass && policy) { | ||
70 | + policy(req, res, next); | ||
71 | + } else { | ||
72 | + return next(); | ||
73 | + } | ||
74 | + | ||
75 | + }).catch((err) => { | ||
76 | + logger.error(err); | ||
77 | + return next(); | ||
78 | + }); | ||
79 | + } else { | ||
80 | + return next(); | ||
81 | + } | ||
82 | +}; |
1 | +'use strict'; | ||
2 | + | ||
3 | +const helpers = global.yoho.helpers; | ||
4 | +const _ = require('lodash'); | ||
5 | + | ||
6 | +const WHITE_LIST = [ | ||
7 | + '/3party/check', | ||
8 | + '/passport/imagesNode', | ||
9 | + '/passport/cert/headerTip' | ||
10 | +]; | ||
11 | + | ||
12 | +module.exports = (req, res, next) => { | ||
13 | + let refer = req.method === 'GET' ? req.get('Referer') : ''; | ||
14 | + let limitAPI = helpers.urlFormat('/3party/check', {refer: refer}); | ||
15 | + let limitPage = helpers.urlFormat('/3party/check', {refer: req.protocol + '://' + req.get('host') + req.originalUrl}); | ||
16 | + | ||
17 | + if (_.indexOf(WHITE_LIST, req.path) >= 0) { | ||
18 | + return next(); | ||
19 | + } | ||
20 | + | ||
21 | + if (req.xhr) { | ||
22 | + return res.json({ | ||
23 | + code: 400, | ||
24 | + data: {refer: limitAPI} | ||
25 | + }); | ||
26 | + } | ||
27 | + | ||
28 | + return res.redirect(limitPage); | ||
29 | +}; |
1 | +'use strict'; | ||
2 | + | ||
3 | +const logger = global.yoho.logger; | ||
4 | +const cache = global.yoho.cache.master; | ||
5 | +const ONE_DAY = 60 * 60 * 24; | ||
6 | + | ||
7 | +module.exports = (limiter, policy) => { | ||
8 | + const req = limiter.req, | ||
9 | + res = limiter.res, | ||
10 | + next = limiter.next; | ||
11 | + | ||
12 | + const key = `pc:limiter:faker:${limiter.remoteIp}`; | ||
13 | + | ||
14 | + if (req.header('X-Requested-With') === 'XMLHttpRequest') { | ||
15 | + cache.decrAsync(key, 1); | ||
16 | + } | ||
17 | + | ||
18 | + res.on('render', function() { | ||
19 | + cache.incrAsync(key, 1); | ||
20 | + }); | ||
21 | + | ||
22 | + return cache.getAsync(key).then((result) => { | ||
23 | + if (result) { | ||
24 | + if (result > 100) { | ||
25 | + return Promise.resolve(policy);//policy(req, res, next); | ||
26 | + } else { | ||
27 | + return Promise.resolve(true); | ||
28 | + } | ||
29 | + } else { | ||
30 | + cache.setAsync(key, 1, ONE_DAY); // 设置key,1m失效 | ||
31 | + return Promise.resolve(true); | ||
32 | + } | ||
33 | + }); | ||
34 | +}; |
doraemon/middleware/limiter/rules/ip-list.js
0 → 100644
1 | +'use strict'; | ||
2 | + | ||
3 | +const cache = global.yoho.cache.master; | ||
4 | +const _ = require('lodash'); | ||
5 | + | ||
6 | +module.exports = (limiter) => { | ||
7 | + const key = `pc:limiter:${limiter.remoteIp}`; | ||
8 | + | ||
9 | + return cache.getAsync(key).then((result) => { | ||
10 | + if (result && _.isNumber(result)) { | ||
11 | + return Promise.resolve({ | ||
12 | + exclusion: result === -1 | ||
13 | + }); | ||
14 | + } else { | ||
15 | + return Promise.resolve(true); | ||
16 | + } | ||
17 | + }); | ||
18 | +}; |
1 | +'use strict'; | ||
2 | + | ||
3 | +const logger = global.yoho.logger; | ||
4 | +const cache = global.yoho.cache.master; | ||
5 | +const config = global.yoho.config; | ||
6 | +const ONE_DAY = 60 * 60 * 24; | ||
7 | +const MAX_QPS = config.maxQps; | ||
8 | +const _ = require('lodash'); | ||
9 | + | ||
10 | +const PAGES = { | ||
11 | + '/product/\\/pro_([\\d]+)_([\\d]+)\\/(.*)/': 5, | ||
12 | + '/product/list/index': 5 | ||
13 | +}; | ||
14 | + | ||
15 | +function urlJoin(a, b) { | ||
16 | + if (_.endsWith(a, '/') && _.startsWith(b, '/')) { | ||
17 | + return a + b.substring(1, b.length); | ||
18 | + } else if (!_.endsWith(a, '/') && !_.startsWith(b, '/')) { | ||
19 | + return a + '/' + b; | ||
20 | + } else { | ||
21 | + return a + b; | ||
22 | + } | ||
23 | +} | ||
24 | + | ||
25 | +module.exports = (limiter, policy) => { | ||
26 | + const req = limiter.req, | ||
27 | + res = limiter.res, | ||
28 | + next = limiter.next; | ||
29 | + | ||
30 | + const key = `pc:limiter:${limiter.remoteIp}`; | ||
31 | + | ||
32 | + res.on('render', function() { | ||
33 | + let route = req.route ? req.route.path : ''; | ||
34 | + let appPath = req.app.mountpath; | ||
35 | + | ||
36 | + if (_.isArray(route) && route.length > 0) { | ||
37 | + route = route[0]; | ||
38 | + } | ||
39 | + | ||
40 | + let pageKey = urlJoin(appPath, route.toString()); // route may be a regexp | ||
41 | + let pageIncr = PAGES[pageKey] || 0; | ||
42 | + | ||
43 | + if (/^\/p([\d]+)/.test(req.path)) { | ||
44 | + pageIncr = 5; | ||
45 | + } | ||
46 | + | ||
47 | + if (pageIncr > 0) { | ||
48 | + cache.incrAsync(key, pageIncr); | ||
49 | + } | ||
50 | + }); | ||
51 | + | ||
52 | + return cache.getAsync(key).then((result) => { | ||
53 | + logger.debug('qps limiter: ' + key + '@' + result + ' max: ' + MAX_QPS); | ||
54 | + | ||
55 | + if (result && _.isNumber(result)) { | ||
56 | + | ||
57 | + if (result === -1) { | ||
58 | + return Promise.resolve(true); | ||
59 | + } | ||
60 | + | ||
61 | + if (result > MAX_QPS) { // 判断 qps | ||
62 | + cache.touch(key, ONE_DAY); | ||
63 | + logger.debug('req limit', key); | ||
64 | + | ||
65 | + return Promise.resolve(policy); | ||
66 | + } else { | ||
67 | + cache.incrAsync(key, 1); // qps + 1 | ||
68 | + return Promise.resolve(true); | ||
69 | + | ||
70 | + } | ||
71 | + } else { | ||
72 | + cache.setAsync(key, 1, 60); // 设置key,1m失效 | ||
73 | + return Promise.resolve(true); | ||
74 | + } | ||
75 | + }); | ||
76 | +}; |
1 | +'use strict'; | ||
2 | + | ||
3 | +const cache = global.yoho.cache.master; | ||
4 | +const _ = require('lodash'); | ||
5 | +const logger = global.yoho.logger; | ||
6 | + | ||
7 | + | ||
8 | +module.exports = (limiter, policy) => { | ||
9 | + const req = limiter.req, | ||
10 | + res = limiter.res, | ||
11 | + next = limiter.next; | ||
12 | + const blackKey = 'pc:limiter:ua:black', | ||
13 | + whiteKey = 'pc:limiter:ua:white'; | ||
14 | + | ||
15 | + const ua = limiter.req.header('User-Agent'); | ||
16 | + | ||
17 | + return Promise.all([ | ||
18 | + cache.getAsync(blackKey), | ||
19 | + cache.getAsync(whiteKey) | ||
20 | + ]).then((args) => { | ||
21 | + const blacklist = args[0] || [], whitelist = args[1] || []; | ||
22 | + | ||
23 | + if (blacklist.length === 0 && whitelist.length === 0) { | ||
24 | + return Promise.resolve(true); | ||
25 | + } | ||
26 | + | ||
27 | + const test = (list) => { | ||
28 | + let result = false; | ||
29 | + | ||
30 | + _.each(list, (item) => { | ||
31 | + let regexp; | ||
32 | + | ||
33 | + try { | ||
34 | + regexp = new RegExp(item); | ||
35 | + } catch (e) { | ||
36 | + logger.error(e); | ||
37 | + } | ||
38 | + | ||
39 | + if (regexp.test(ua)) { | ||
40 | + result = true; | ||
41 | + } | ||
42 | + }); | ||
43 | + | ||
44 | + return result; | ||
45 | + }; | ||
46 | + | ||
47 | + if (test(blacklist)) { | ||
48 | + return Promise.resolve(policy); | ||
49 | + } else if (test(whitelist)) { | ||
50 | + return Promise.resolve({ | ||
51 | + exclusion: true | ||
52 | + }); | ||
53 | + } else { | ||
54 | + return Promise.resolve(true); | ||
55 | + } | ||
56 | + }); | ||
57 | + | ||
58 | +}; |
-
Please register or login to post a comment