|
2 | 2 | * @Author: Sky.Sun
|
3 | 3 | * @Date: 2018-01-17 16:07:30
|
4 | 4 | * @Last Modified by: Sky.Sun
|
5 |
| - * @Last Modified time: 2019-05-15 11:15:16 |
| 5 | + * @Last Modified time: 2019-06-12 16:22:08 |
6 | 6 | */
|
7 | 7 | const express = require('express');
|
8 | 8 | const app = express();
|
@@ -76,71 +76,132 @@ process.on('unhandledRejection', reason => {
|
76 | 76 | schedule.startJob();
|
77 | 77 |
|
78 | 78 | /**
|
79 |
| - * 生成一个筛选函数 |
80 |
| - * 筛选函数返回是否能找到对应的规则 |
| 79 | + * 从所配规则中,找出最匹配的那个规则并返回 |
81 | 80 | *
|
82 |
| - * @param {object} req - 请求 |
83 |
| - * @returns {function} - 筛选函数 |
| 81 | + * @param {*} rules - 规则数组 |
| 82 | + * @param {*} req - 请求对象 |
84 | 83 | */
|
85 |
| -function routeFilter(req) { |
86 |
| - return item => { |
| 84 | +function getMatchedRule (rules, req) { |
| 85 | + if (!rules || !rules.length) { |
| 86 | + return null; |
| 87 | + } |
| 88 | + |
| 89 | + // 临时匹配数组,最多只可能有2条,其中开头匹配最多1条,正则匹配最多1条 |
| 90 | + const arr = []; |
| 91 | + |
| 92 | + // 使用for而非forEach是为了方便continue流程控制 |
| 93 | + for (let i = 0; i < rules.length; i++) { |
| 94 | + const item = rules[i]; |
87 | 95 | if (item.active && !item.deleted) {
|
88 |
| - // 首先检测域名是否一致 |
| 96 | + // 匹配域名 |
89 | 97 | let domainMatch = false;
|
90 | 98 | if (!item.domainId) {
|
91 | 99 | domainMatch = true;
|
92 | 100 | } else {
|
93 |
| - const domain = settings.getDomains().find(t => t.id === item.domainId); |
94 |
| - if (domain) { |
| 101 | + const domains = settings.getDomains(); |
| 102 | + const domain = domains.find(t => t.id === item.domainId); |
| 103 | + if (!domain) { |
| 104 | + domainMatch = false; |
| 105 | + } else { |
95 | 106 | domainMatch = req.hostname === domain.domainPath;
|
96 |
| - if (isDebug && req.query.__domain === domain.domainPath) { |
97 |
| - // 链接参数中的 __domain 与规则一致也认为匹配成功,方便本地调试 |
98 |
| - domainMatch = true; |
99 |
| - } |
100 | 107 | }
|
101 | 108 | }
|
102 | 109 | if (!domainMatch) {
|
103 |
| - return false; |
| 110 | + continue; |
104 | 111 | }
|
105 | 112 |
|
106 |
| - // 判断请求方式 |
| 113 | + // 匹配请求方式 |
107 | 114 | if (item.method && item.method.toUpperCase() !== req.method.toUpperCase()) {
|
108 |
| - return false; |
| 115 | + continue; |
| 116 | + } |
| 117 | + |
| 118 | + // 匹配请求参数 |
| 119 | + if (item.params) { |
| 120 | + const query = req.query; |
| 121 | + const body = req.body; |
| 122 | + const cookies = req.cookies; |
| 123 | + try { |
| 124 | + if (!Boolean(eval(item.params))) { |
| 125 | + continue; |
| 126 | + } |
| 127 | + } catch (err) { |
| 128 | + logger.error('匹配参数异常!params:', item.params); |
| 129 | + continue; |
| 130 | + } |
109 | 131 | }
|
110 | 132 |
|
| 133 | + // 匹配路径 |
111 | 134 | let reqPath = req.path;
|
112 | 135 | let uri = item.uri;
|
| 136 | + if (item.type !== 'regexp') { |
| 137 | + // 对于没有后缀的 req.path,尝试加上末尾斜杠(/)后再判断,提高容错 |
| 138 | + if (!path.extname(reqPath) && reqPath.substr(-1) !== '/') { |
| 139 | + reqPath = `${reqPath}/`; |
| 140 | + } |
113 | 141 |
|
114 |
| - // 正则匹配 |
115 |
| - if (item.type === 'regexp') { |
116 |
| - uri = new RegExp(uri); |
117 |
| - return uri.test(reqPath); |
118 |
| - } |
119 |
| - |
120 |
| - // 对于没有后缀的 req.path,尝试加上末尾斜杠(/)后再判断,提高容错 |
121 |
| - if (!path.extname(reqPath) && reqPath.substr(-1) !== '/') { |
122 |
| - reqPath = `${reqPath}/`; |
123 |
| - } |
| 142 | + // 对于没有后缀的 uri,尝试加上末尾斜杠(/)后再判断,提高容错 |
| 143 | + if (!path.extname(uri) && uri.substr(-1) !== '/') { |
| 144 | + uri = `${uri}/`; |
| 145 | + } |
124 | 146 |
|
125 |
| - // 对于没有后缀的 uri,尝试加上末尾斜杠(/)后再判断,提高容错 |
126 |
| - if (!path.extname(uri) && uri.substr(-1) !== '/') { |
127 |
| - uri = `${uri}/`; |
| 147 | + if (item.type === 'exact') { |
| 148 | + // 精确匹配 |
| 149 | + if (reqPath === uri) { |
| 150 | + // 精确匹配成功的话,终止遍历,直接返回结果 |
| 151 | + return item; |
| 152 | + } |
| 153 | + continue; |
| 154 | + } else { |
| 155 | + // 开头匹配 |
| 156 | + if (reqPath.startsWith(uri)) { |
| 157 | + // 开头匹配成功的话,看数组中是否已经存在开头匹配的项了 |
| 158 | + const existsIdx = arr.findIndex(t => t.type === 'start'); |
| 159 | + if (existsIdx >= 0) { |
| 160 | + // 如果存在,再比较uri长度 |
| 161 | + let existsItemUri = arr[existsIdx].uri; |
| 162 | + if (!path.extname(existsItemUri) && existsItemUri.substr(-1) !== '/') { |
| 163 | + existsItemUri = `${existsItemUri}/`; |
| 164 | + } |
| 165 | + if (uri.length > existsItemUri.length) { |
| 166 | + // 删除之前存在的规则,并在末尾存入uri更长的那条规则 |
| 167 | + arr.splice(existsIdx, 1); |
| 168 | + arr.push(item); |
| 169 | + } |
| 170 | + } else { |
| 171 | + // 不存在,直接加入 |
| 172 | + arr.push(item); |
| 173 | + } |
| 174 | + } |
| 175 | + } |
| 176 | + } else { |
| 177 | + // 正则表达式匹配 |
| 178 | + uri = new RegExp(uri); |
| 179 | + if (uri.test(reqPath)) { |
| 180 | + // 如果匹配成功,看下数组中是否已经存在正则匹配的项,如果不存在才存入 |
| 181 | + if (!arr.some(t => t.type === 'regexp')) { |
| 182 | + arr.push(item); |
| 183 | + } |
| 184 | + } |
128 | 185 | }
|
| 186 | + } |
| 187 | + } |
129 | 188 |
|
130 |
| - // 精确匹配 |
131 |
| - if (item.type === 'exact') { |
132 |
| - return reqPath === uri; |
133 |
| - } |
| 189 | + // 精确匹配成功会直接返回,所以走到这一步时,一定只剩下开头匹配和正则匹配了,数组长度只可能有3种情况:0, 1, 2 |
| 190 | + if (!arr.length) { |
| 191 | + return null; |
| 192 | + } |
| 193 | + if (arr.length === 1) { |
| 194 | + return arr[0]; |
| 195 | + } |
134 | 196 |
|
135 |
| - // 匹配开头 |
136 |
| - if (item.type === 'start') { |
137 |
| - return reqPath.startsWith(uri); |
138 |
| - } |
| 197 | + // 数组长度为2,说明一定是1条开头匹配,1条正则匹配,此时需要再判断下开头匹配是否是兜底规则 |
| 198 | + const bottomRuleIdx = arr.findIndex(t => (t.type === 'start' && t.uri === '/')); |
| 199 | + if (bottomRuleIdx >= 0) { |
| 200 | + arr.splice(bottomRuleIdx, 1); |
| 201 | + } |
139 | 202 |
|
140 |
| - return false; |
141 |
| - } |
142 |
| - return false; |
143 |
| - }; |
| 203 | + // 此时,一定不存在兜底规则了,则取数组中的第一项返回,因为第一项规则必定是排序靠前的 |
| 204 | + return arr[0]; |
144 | 205 | }
|
145 | 206 |
|
146 | 207 | /**
|
@@ -185,15 +246,11 @@ app.use((req, res, next) => {
|
185 | 246 | // 2. 缓存处理,在身份验证之后进行
|
186 | 247 | function cacheHandler() {
|
187 | 248 | // 尝试匹配缓存配置
|
188 |
| - const cacheConf = settings.getCaches().find(routeFilter(req)); |
| 249 | + const cacheConf = getMatchedRule(settings.getCaches(), req); |
189 | 250 | if (cacheConf) {
|
190 | 251 | logMsg += `命中缓存规则 {${cacheConf._id}} --> `;
|
191 | 252 |
|
192 |
| - if (req.headers['redis-cache-ignore']) { |
193 |
| - // ServerLog 支持绕过缓存,方便开发和测试 |
194 |
| - logMsg += '不走缓存 (通过 ServerLog 绕过缓存) --> '; |
195 |
| - routeHandler(); |
196 |
| - } else if (req.query[debugMode.debugParam] === 'true') { |
| 253 | + if (req.query[debugMode.debugParam] === 'true') { |
197 | 254 | // 调试模式绕过缓存
|
198 | 255 | logMsg += '不走缓存 (调试模式绕过缓存) --> ';
|
199 | 256 | routeHandler();
|
@@ -288,7 +345,7 @@ app.use((req, res, next) => {
|
288 | 345 | }
|
289 | 346 |
|
290 | 347 | // 尝试匹配普通路由规则
|
291 |
| - const route = settings.getRoutes().find(routeFilter(req)); |
| 348 | + const route = getMatchedRule(settings.getRoutes(), req); |
292 | 349 |
|
293 | 350 | // 找不到匹配的规则
|
294 | 351 | if (!route) {
|
@@ -395,13 +452,15 @@ app.use((req, res, next) => {
|
395 | 452 | }
|
396 | 453 |
|
397 | 454 | // 1. 尝试匹配身份验证规则
|
398 |
| - const permission = settings.getPermissions().find(routeFilter(req)); |
| 455 | + const permission = getMatchedRule(settings.getPermissions(), req); |
399 | 456 | if (permission) {
|
400 | 457 | logMsg += `命中身份验证规则 {${permission._id}} --> `;
|
401 | 458 |
|
402 | 459 | // 找到了规则,继续判断是否在排除项中
|
403 | 460 | const excludes = permission.excludes;
|
404 |
| - const exclude = excludes.find(routeFilter(req)); |
| 461 | + |
| 462 | + //TODO: |
| 463 | + const exclude = null; |
405 | 464 |
|
406 | 465 | // 无符合的排除项,说明必须走身份验证
|
407 | 466 | if (!exclude) {
|
@@ -485,5 +544,6 @@ if (config.ssl.enable) {
|
485 | 544 | server = http.createServer(app);
|
486 | 545 | }
|
487 | 546 | server.listen(app.get('port'), () => {
|
488 |
| - logger.info('Noginx listening on port', server.address().port, 'with pid', process.pid); |
| 547 | + const port = server.address().port; |
| 548 | + logger.info(`Noginx listening on port ${port} with pid ${process.pid}, Admin URL: ${config.ssl.enable ? 'https' : 'http'}://localhost:${port}/noginx`); |
489 | 549 | });
|
0 commit comments