未验证 提交 fbb208e1 编写于 作者: sinat_25235033's avatar sinat_25235033 提交者: GitHub

Feature support one ant match rule str*str,eg:*.html (#44)

* Support one ant match rule str*str, eg:*.html - the * in string match 0 or more characters
上级 9a694d53
......@@ -4,19 +4,16 @@ package com.usthe.sureness.matcher.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.*;
import java.util.regex.Pattern;
/**
* Improved dictionary matching tree
* support regular * **
* support regular: str*str, *, **
* the * in str*str can match zero or more character eg: *.html -> content.html
* the * can match zero or one directory
* the ** can match zero or more directories
* Match priority: Raw string > * > **
* Match priority: Raw string > str*str > * > **
* @author tomsun28
* @date 19:25 2019-01-18
*/
......@@ -34,6 +31,10 @@ public class TirePathTree {
private static final int PATH_NODE_NUM_3 = 3;
private static final int PATH_NODE_NUM_2 = 2;
private static final Pattern PATH_SPLIT_PATTERN = Pattern.compile("/+");
/**
* str - Pattern.compile(str)
*/
private static final Map<String, Pattern> PATTERN_MAP = new HashMap<>(8);
/**
* root node
......@@ -121,12 +122,15 @@ public class TirePathTree {
* @return java.lang.String roles eg: [role1,role2]
*/
public String searchPathFilterRoles(String path) {
if (path == null || "".equals(path) || !path.startsWith(URL_PATH_SPLIT)) {
if (path == null || "".equals(path)) {
return null;
}
if (logger.isTraceEnabled()) {
logger.trace("sureness - searchPathFilterRoles, path is {}", path);
}
if (!path.startsWith(URL_PATH_SPLIT)) {
path = URL_PATH_SPLIT.concat(path);
}
path = PATH_SPLIT_PATTERN.matcher(path).replaceAll("/");
path = path.substring(1).toLowerCase();
String[] tmp = path.split("===");
......@@ -156,8 +160,9 @@ public class TirePathTree {
return null;
}
String currentNodeData = current.getData();
// fast fail
if (isNoMatchString(current.getData(), urlPac[currentFlow])) {
if (isNoMatchString(currentNodeData, urlPac[currentFlow])) {
return null;
}
if (currentFlow == urlPac.length - 1 && (NODE_TYPE_MAY_PATH_END.equals(current.getNodeType()))) {
......@@ -181,13 +186,13 @@ public class TirePathTree {
}
String matchRole = null;
if (current.getData().equals(urlPac[currentFlow])) {
if (currentNodeData.equals(urlPac[currentFlow]) || isPatternStr(currentNodeData)) {
matchRole = searchPathRoleInChildren(current, urlPac, currentFlow, method);
if (matchRole != null) {
return matchRole;
}
}
if (current.getData().equals(MATCH_ONE)) {
if (currentNodeData.equals(MATCH_ONE)) {
matchRole = searchPathRoleInChildren(current, urlPac, currentFlow - 1, method);
if (matchRole != null) {
return matchRole;
......@@ -197,7 +202,7 @@ public class TirePathTree {
return matchRole;
}
}
if (current.getData().equals(MATCH_ALL)) {
if (currentNodeData.equals(MATCH_ALL)) {
matchRole = searchPathRoleInChildren(current, urlPac, currentFlow - 1, method);
if (matchRole != null) {
return matchRole;
......@@ -234,6 +239,15 @@ public class TirePathTree {
return matchRole;
}
}
if (current.getPatternChildren() != null) {
for (String patternChild : current.getPatternChildren()) {
Node matchPatternNode = current.getChildren().get(patternChild);
matchRole = searchPathRole(matchPatternNode, urlPac, currentFlow + 1, method);
if (matchRole != null) {
return matchRole;
}
}
}
if (current.getChildren().containsKey(MATCH_ONE)) {
Node matchOneNode = current.getChildren().get(MATCH_ONE);
matchRole = searchPathRole(matchOneNode, urlPac, currentFlow + 1, method);
......@@ -250,9 +264,9 @@ public class TirePathTree {
/**
* Determine whether the pattern does not match pathNode
* @param pattern pattern eg: * **
* @param pattern pattern eg: raw str, *html, *, **
* @param pathNode pathNode
* @return match return true, else false
* @return not match return true, else false
*/
private boolean isNoMatchString(String pattern, String pathNode) {
if (pattern == null && pathNode == null) {
......@@ -261,8 +275,47 @@ public class TirePathTree {
if (pattern == null || pathNode == null) {
return true;
}
return !(pattern.equals(pathNode) || MATCH_ONE.equals(pattern)
|| MATCH_ALL.equals(pattern));
if (pattern.equals(pathNode) || MATCH_ONE.equals(pattern)
|| MATCH_ALL.equals(pattern)) {
return false;
}
if (!isPatternStr(pattern)) {
return true;
}
Pattern matcher = PATTERN_MAP.get(pattern);
if (matcher == null) {
String patternStr = makeJavaPatternStr(pattern);
matcher = Pattern.compile(patternStr);
PATTERN_MAP.put(pattern, matcher);
}
return !matcher.matcher(pathNode).matches();
}
/**
* if is a pattern string eg: *html , *.js
* @param str string
* @return yes return true else false
*/
private boolean isPatternStr(String str) {
return str != null
&& str.contains(MATCH_ONE)
&& !MATCH_ONE.equals(str)
&& !MATCH_ALL.equals(str);
}
/**
* make the java pattern str for sureness rule
* @param pattern pattern
* @return java pattern
*/
private String makeJavaPatternStr(String pattern) {
return pattern
.replace("*", "\\S*")
.replace(".", "\\.")
.replace("?","\\?")
.replace("+", "\\+")
.replace("$", "\\$")
.replace("^","\\^");
}
/**
......@@ -270,12 +323,15 @@ public class TirePathTree {
* @param path path = /api/v1/host/detail===GET===[role2,role3,role4]
*/
private void insertNode(String path, Node rootNode) {
if (path == null || "".equals(path) || !path.startsWith(URL_PATH_SPLIT)) {
if (path == null || "".equals(path)) {
return;
}
if (logger.isTraceEnabled()) {
logger.trace("sureness - begin insertNode, path is {}", path);
}
if (!path.startsWith(URL_PATH_SPLIT)) {
path = URL_PATH_SPLIT.concat(path);
}
path = PATH_SPLIT_PATTERN.matcher(path).replaceAll("/");
// remove the first /
path = path.substring(1).toLowerCase();
......@@ -292,6 +348,16 @@ public class TirePathTree {
for (String urlData : urlPac) {
if (!current.getChildren().containsKey(urlData)) {
current.insertChild(urlData);
// if urlData is match *xxx, insert patternChildren eg: *html
if (isPatternStr(urlData)) {
Pattern pattern = PATTERN_MAP.get(urlData);
if (pattern == null) {
String patternStr = makeJavaPatternStr(urlData);
pattern = Pattern.compile(patternStr);
PATTERN_MAP.put(urlData, pattern);
}
current.insertPatternChild(urlData);
}
}
pre = current;
current = current.getChildren().get(urlData);
......@@ -330,6 +396,9 @@ public class TirePathTree {
/** children nodes **/
private Map<String, Node> children;
/** pattern children list **/
private List<String> patternChildren;
private Node(String data, String nodeType) {
this.data = data;
this.nodeType = nodeType;
......@@ -345,10 +414,17 @@ public class TirePathTree {
this.children.put(data, new Node(data));
}
private void insertChild(String data,String nodeType) {
private void insertChild(String data, String nodeType) {
this.children.put(data, new Node(data, nodeType));
}
private void insertPatternChild(String data) {
if (patternChildren == null) {
patternChildren = new LinkedList<>();
}
this.patternChildren.add(data);
}
private String getNodeType() {
return nodeType;
}
......@@ -373,5 +449,8 @@ public class TirePathTree {
this.children = children;
}
public List<String> getPatternChildren() {
return patternChildren;
}
}
}
......@@ -24,6 +24,9 @@ excludedResource:
- /api/v3/host===get
- /api/v3/book===get
- /api/v1/account/auth===post
- /**/*.html===get
- /**/*.js===get
- /**/*.css===get
# account info
# there are three account: admin, root, tom
......
......@@ -44,25 +44,37 @@ public class TirePathTreeTest {
paths.add("/api/v3/host===put===[role2,role3,role4]");
paths.add("/api/v2/detail===put===[role2,role3,role4]");
paths.add("/api/v2/mom===put===[role2,role3,role4]");
// match *xxx eg: *.html
paths.add("/api/v6/host/*.html===get===[role1]");
// priority: equals normal path > match *xxx
paths.add("/api/v6/book/content.js===get===[role3,role4]");
paths.add("/api/v6/book/*.js===get===[role2]");
// match *
paths.add("/api/*/ha/*===put===[role2,role4]");
// priority: equals normal path > match *
// priority: equals normal path > match *xxx > match *
paths.add("/api/v4/mom/ha===put===[role3,role4]");
paths.add("/api/*html/mom/ha===put===[role4]");
paths.add("/api/*/mom/ha===put===[role2,role4]");
//match **
paths.add("/api/mi/**===put===[role5]");
paths.add("/api/mo/**/day===get===[role6]");
paths.add("/api/day/**/day/mo===put===[role7]");
// priority: equals normal path > match * > match **
// priority: equals normal path > match *xxx > match * > match **
paths.add("/api/v5/day/book===put===[role5]");
paths.add("/api/v5/**===put===[role6]");
paths.add("/api/demo/book/*app/egg===get===[role3]");
paths.add("/api/demo/book/*/egg===get===[role1]");
paths.add("/api/demo/book/**/egg===get===[role2]");
paths.add("/**===get===[role9]");
paths.add("/er/**/swagger===get===[role10]");
paths.add("/swagger/**===get===[role11]");
// ignore resource
paths.add("/**/*.html===post===[role8]");
paths.add("/**/*.css===post===[role8]");
paths.add("/**/*.js===post===[role8]");
root.buildTree(paths);
Assert.assertEquals(22, root.getResourceNum());
Assert.assertEquals(30, root.getResourceNum());
}
@Test
......@@ -78,17 +90,24 @@ public class TirePathTreeTest {
Assert.assertEquals("[role3,role4]", root.searchPathFilterRoles("/api/v2/host===put"));
Assert.assertEquals("[role2,role3,role4]", root.searchPathFilterRoles("/api/v1/host===put"));
Assert.assertEquals("[role2,role3,role4]", root.searchPathFilterRoles("/api/v3/host===put"));
// match *xxx eg: *.html
Assert.assertEquals("[role1]", root.searchPathFilterRoles("/api/v6/host/content.html===get"));
// priority: equals normal path > match *xxx
Assert.assertEquals("[role3,role4]", root.searchPathFilterRoles("/api/v6/book/content.js===get"));
Assert.assertEquals("[role2]", root.searchPathFilterRoles("/api/v6/book/other.js===get"));
// match *
Assert.assertEquals("[role2,role4]", root.searchPathFilterRoles("/api/v2/ha/host===put"));
// priority: equals normal path > match *
// priority: equals normal path > match *xxx > match *
Assert.assertEquals("[role3,role4]", root.searchPathFilterRoles("/api/v4/mom/ha===put"));
Assert.assertEquals("[role4]", root.searchPathFilterRoles("/api/v4html/mom/ha===put"));
Assert.assertEquals("[role2,role4]", root.searchPathFilterRoles("/api/v6/mom/ha===put"));
// match **
Assert.assertEquals("[role5]", root.searchPathFilterRoles("/api/mi/tom/hello===put"));
Assert.assertEquals("[role6]", root.searchPathFilterRoles("/api/mo/tom/hello/day/day===get"));
Assert.assertEquals("[role7]", root.searchPathFilterRoles("/api/day/day/day/day/book/day/mo===put"));
// priority: equals normal path > match * > match **
// priority: equals normal path > match *xxx > match * > match **
Assert.assertEquals("[role5]", root.searchPathFilterRoles("/api/v5/day/book===put"));
Assert.assertEquals("[role3]", root.searchPathFilterRoles("/api/demo/book/tom-app/egg===get"));
Assert.assertEquals("[role1]", root.searchPathFilterRoles("/api/demo/book/tom/egg===get"));
Assert.assertEquals("[role2]", root.searchPathFilterRoles("/api/demo/book/tom/good/egg===get"));
Assert.assertEquals("[role6]", root.searchPathFilterRoles("/api/v5/mom/ha===put"));
......@@ -97,6 +116,10 @@ public class TirePathTreeTest {
Assert.assertEquals("[role10]", root.searchPathFilterRoles("/er/swagger===get"));
Assert.assertNull(root.searchPathFilterRoles("/api/v6/book/ha/good===put"));
Assert.assertEquals("[role11]", root.searchPathFilterRoles("/swagger===get"));
// ignore resource
Assert.assertEquals("[role8]", root.searchPathFilterRoles("/content-ui.html===post"));
Assert.assertEquals("[role8]", root.searchPathFilterRoles("/api/user/ui.js===post"));
Assert.assertEquals("[role8]", root.searchPathFilterRoles("/node/v2/demo.css===post"));
}
}
\ No newline at end of file
......@@ -2,20 +2,22 @@
我们配置的资源格式为:`requestUri===httpMethod`, 即请求的路径加上其请求方式(`post,get,put,delete...`)作为一个整体被视作一个资源
`eg: /api/v2/book===get` `get`方式请求`/api/v2/book`接口数据
这里的`requestUri`支持url路径匹配符匹配: `*`, `**`
这里的`requestUri`支持url路径匹配符匹配: `str*str`, `*`, `**`
通配符 | 描述
--- | ---
--- | ---
`str*str` | 字符串中的*匹配0个或者多个任意字符
`*` | 匹配0个或1个目录
`**` | 匹配0个或多个目录
样例 | 说明
--- | ---
`*.html` | 可以匹配 `content.html`, `user-ui.html`
`/api/*/book` | 可以匹配 `/api/user/book``/api/book`
`/**` | 可以匹配任何路径
`/**/foo` | 可以匹配 `/api/user/book/foo`
匹配优先级: 原始字符串 > `*` > `**`
匹配优先级: 原始字符串 > `str*str` > `*` > `**`
最长路径匹配原则:
eg: `requestUri``/app/book/foo`,若存在两个路径匹配模式`/app/**``/app/book/*`,则会匹配到`/app/book/*`
\ No newline at end of file
......@@ -2,21 +2,23 @@
We treat restful requests as a resource, resource format like `requestUri===httpMethod`.
That is the request uri + request method(`post,get,put,delete...`) is considered as a resource as a whole.
`eg: /api/v2/book===get`
The `requestUri` here support url path match: `*`, `**`
The `requestUri` here support url path match: `str*str`, `*`, `**`
Wildcard | Describe
--- | ---
`str*str` | the * in string match 0 or more characters
`*` | Match 0 or 1 directories
`**` | Match 0 or more directories
Sample | Note
--- | ---
`*.html` | can match `content.html`, `user-ui.html` etc
`/api/*/book` | can match `/api/user/book` or `/api/book` etc
`/**` | can match any path
`/**/foo` | can match `/api/user/book/foo` etc
Match priority: Raw string > `*` > `**`
Match priority: Raw string > `*.html` > `*` > `**`
Longest path matching principle:
eg: when `requestUri` is `/app/book/foo`,If there are two matching patterns - `/app/**` and `/app/book/*`,will match`/app/book/*`
\ No newline at end of file
......@@ -24,6 +24,9 @@ excludedResource:
- /api/v3/host===get
- /api/v3/book===get
- /api/v1/account/auth===post
- /**/*.html===get
- /**/*.js===get
- /**/*.css===get
# account info
# there are three account: admin, root, tom
......
......@@ -13,6 +13,9 @@ insert into auth_resource (id, name, code, uri, type, method, status, descriptio
insert into auth_resource (id, name, code, uri, type, method, status, description) values (109, 'Delete role', 'DELETE_ROLE', '/role/*', 'role', 'DELETE', 1, null);
insert into auth_resource (id, name, code, uri, type, method, status, description) values (110, 'Get role', 'GET_ROLES', '/role/*/*', 'role', 'GET', 1, null);
insert into auth_resource (id, name, code, uri, type, method, status, description) values (111, 'User get custom token', 'ACCOUNT_CUSTOM_TOKEN', '/auth/custom/token', 'account', 'POST', 9, null);
insert into auth_resource (id, name, code, uri, type, method, status, description) values (112, 'Static Resource', 'Static Resource', '/**/*.html', 'static', 'GET', 9, null);
insert into auth_resource (id, name, code, uri, type, method, status, description) values (113, 'Static Resource', 'Static Resource', '/**/*.js', 'static', 'GET', 9, null);
insert into auth_resource (id, name, code, uri, type, method, status, description) values (114, 'Static Resource', 'Static Resource', '/**/*.css', 'static', 'GET', 9, null);
-- ----------------------------
-- Records of auth_role
......
......@@ -24,6 +24,9 @@ excludedResource:
- /api/v3/host===get
- /api/v3/book===get
- /api/v1/account/auth===post
- /**/*.html===get
- /**/*.js===get
- /**/*.css===get
# account info
# there are three account: admin, root, tom
......
......@@ -24,6 +24,9 @@ excludedResource:
- /api/v3/host===get
- /api/v3/book===get
- /api/v1/account/auth===post
- /**/*.html===get
- /**/*.js===get
- /**/*.css===get
# account info
# there are three account: admin, root, tom
......
......@@ -24,6 +24,9 @@ excludedResource:
- /api/v3/host===get
- /api/v3/book===get
- /api/v1/account/auth===post
- /**/*.html===get
- /**/*.js===get
- /**/*.css===get
# account info
# there are three account: admin, root, tom
......
......@@ -24,6 +24,9 @@ excludedResource:
- /api/v3/host===get
- /api/v3/book===get
- /api/v1/account/auth===post
- /**/*.html===get
- /**/*.js===get
- /**/*.css===get
# account info
# there are three account: admin, root, tom
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册