Skip to content

Commit 920affe

Browse files
committed
Rest filter support pattern & some bugfix
1 parent 98e96d9 commit 920affe

File tree

12 files changed

+271
-24
lines changed

12 files changed

+271
-24
lines changed

dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/RestConstants.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@
2323

2424
public final class RestConstants {
2525

26-
public static final String REST = "rest";
27-
2826
public static final String REST_FILTER_KEY = "rest.filter";
2927
public static final String EXTENSION_KEY = "extension";
3028
public static final String EXTENSIONS_ATTRIBUTE_KEY = "restExtensionsAttributeKey";

dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/AbstractRestFilter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717
package org.apache.dubbo.rpc.protocol.tri.rest.filter;
1818

19+
import org.apache.dubbo.common.utils.ArrayUtils;
1920
import org.apache.dubbo.rpc.protocol.tri.rest.util.RestUtils;
2021

2122
import java.util.Arrays;
@@ -47,7 +48,7 @@ public String toString() {
4748
sb.append(", priority=").append(priority);
4849
}
4950
String[] patterns = getPatterns();
50-
if (patterns != null) {
51+
if (ArrayUtils.isNotEmpty(patterns)) {
5152
sb.append(", patterns=").append(Arrays.toString(patterns));
5253
}
5354
return sb.append('}').toString();

dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/filter/RestExtensionExecutionFilter.java

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
import org.apache.dubbo.common.constants.CommonConstants;
2222
import org.apache.dubbo.common.extension.Activate;
2323
import org.apache.dubbo.common.extension.ExtensionAccessorAware;
24+
import org.apache.dubbo.common.logger.Logger;
25+
import org.apache.dubbo.common.logger.LoggerFactory;
26+
import org.apache.dubbo.common.utils.ArrayUtils;
27+
import org.apache.dubbo.common.utils.CollectionUtils;
2428
import org.apache.dubbo.common.utils.StringUtils;
2529
import org.apache.dubbo.remoting.http12.HttpRequest;
2630
import org.apache.dubbo.remoting.http12.HttpResponse;
@@ -35,20 +39,27 @@
3539
import org.apache.dubbo.rpc.protocol.tri.rest.Messages;
3640
import org.apache.dubbo.rpc.protocol.tri.rest.RestConstants;
3741
import org.apache.dubbo.rpc.protocol.tri.rest.RestInitializeException;
42+
import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RadixTree;
43+
import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RadixTree.Match;
3844
import org.apache.dubbo.rpc.protocol.tri.rest.util.RestUtils;
3945
import org.apache.dubbo.rpc.protocol.tri.rest.util.TypeUtils;
4046

4147
import java.util.ArrayList;
48+
import java.util.Arrays;
49+
import java.util.BitSet;
4250
import java.util.Comparator;
4351
import java.util.List;
52+
import java.util.Map;
4453
import java.util.concurrent.CompletableFuture;
4554
import java.util.function.Supplier;
4655

4756
@Activate(group = CommonConstants.PROVIDER, order = 1000)
4857
public class RestExtensionExecutionFilter extends RestFilterAdapter {
4958

59+
private static final Logger LOGGER = LoggerFactory.getLogger(RestExtensionExecutionFilter.class);
5060
private static final String KEY = RestExtensionExecutionFilter.class.getSimpleName();
5161

62+
private final Map<RestFilter, RadixTree<Boolean>> filterTreeCache = CollectionUtils.newConcurrentHashMap();
5263
private final ApplicationModel applicationModel;
5364
private final List<RestExtensionAdapter<Object>> extensionAdapters;
5465

@@ -61,7 +72,7 @@ public RestExtensionExecutionFilter(ApplicationModel applicationModel) {
6172
@Override
6273
protected Result invoke(Invoker<?> invoker, Invocation invocation, HttpRequest request, HttpResponse response)
6374
throws RpcException {
64-
RestFilter[] filters = getFilters(invoker);
75+
RestFilter[] filters = matchFilters(getFilters(invoker), request.path());
6576
DefaultFilterChain chain = new DefaultFilterChain(filters, invocation, () -> invoker.invoke(invocation));
6677
invocation.put(KEY, chain);
6778
try {
@@ -127,13 +138,65 @@ protected void onError(
127138
chain.onError(t, request, response);
128139
}
129140

141+
private RestFilter[] matchFilters(RestFilter[] filters, String path) {
142+
int len = filters.length;
143+
BitSet bitSet = new BitSet(len);
144+
out:
145+
for (int i = 0; i < len; i++) {
146+
RestFilter filter = filters[i];
147+
String[] patterns = filter.getPatterns();
148+
if (ArrayUtils.isEmpty(patterns)) {
149+
continue;
150+
}
151+
RadixTree<Boolean> filterTree = filterTreeCache.computeIfAbsent(filter, f -> {
152+
RadixTree<Boolean> tree = new RadixTree<>();
153+
for (String pattern : patterns) {
154+
if (StringUtils.isNotEmpty(pattern)) {
155+
if (pattern.charAt(0) == '!') {
156+
tree.addPath(pattern.substring(1), false);
157+
} else {
158+
tree.addPath(pattern, true);
159+
}
160+
}
161+
}
162+
return tree;
163+
});
164+
165+
List<Match<Boolean>> matches = filterTree.match(path);
166+
int size = matches.size();
167+
if (size == 0) {
168+
bitSet.set(i);
169+
continue;
170+
}
171+
for (int j = 0; j < size; j++) {
172+
if (!matches.get(j).getValue()) {
173+
bitSet.set(i);
174+
continue out;
175+
}
176+
}
177+
}
178+
if (bitSet.isEmpty()) {
179+
return filters;
180+
}
181+
RestFilter[] matched = new RestFilter[len - bitSet.cardinality()];
182+
for (int i = 0, j = 0; i < len; i++) {
183+
if (!bitSet.get(i)) {
184+
matched[j++] = filters[i];
185+
}
186+
}
187+
if (LOGGER.isDebugEnabled()) {
188+
LOGGER.debug("Matched filters for path '{}' is {}", path, Arrays.toString(matched));
189+
}
190+
return matched;
191+
}
192+
193+
@SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
130194
private RestFilter[] getFilters(Invoker<?> invoker) {
131195
URL url = invoker.getUrl();
132196
RestFilter[] filters = getFilters(url);
133197
if (filters != null) {
134198
return filters;
135199
}
136-
//noinspection SynchronizationOnLocalVariableOrMethodParameter
137200
synchronized (invoker) {
138201
filters = getFilters(url);
139202
if (filters != null) {
@@ -150,6 +213,7 @@ private RestFilter[] getFilters(URL url) {
150213
}
151214

152215
private RestFilter[] loadFilters(URL url) {
216+
LOGGER.info("Loading rest filters for {}", url);
153217
List<RestFilter> extensions = new ArrayList<>();
154218

155219
// 1. load from extension config
@@ -186,13 +250,29 @@ private void adaptExtension(Object extension, List<RestFilter> extensions) {
186250
extension = ((Supplier<?>) extension).get();
187251
}
188252
if (extension instanceof RestFilter) {
189-
extensions.add((RestFilter) extension);
253+
addRestFilter(extension, (RestFilter) extension, extensions);
190254
return;
191255
}
192256
for (RestExtensionAdapter<Object> adapter : extensionAdapters) {
193257
if (adapter.accept(extension)) {
194-
extensions.add(adapter.adapt(extension));
258+
addRestFilter(extension, adapter.adapt(extension), extensions);
195259
}
196260
}
197261
}
262+
263+
private void addRestFilter(Object extension, RestFilter filter, List<RestFilter> extensions) {
264+
extensions.add(filter);
265+
if (!LOGGER.isInfoEnabled()) {
266+
return;
267+
}
268+
StringBuilder sb = new StringBuilder(64);
269+
sb.append("Rest filter [").append(extension).append("] loaded");
270+
if (filter.getPriority() != 0) {
271+
sb.append(", priority=").append(filter.getPriority());
272+
}
273+
if (ArrayUtils.isNotEmpty(filter.getPatterns())) {
274+
sb.append(", patterns=").append(Arrays.toString(filter.getPatterns()));
275+
}
276+
LOGGER.info(sb.toString());
277+
}
198278
}

dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTree.java

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,6 @@ public void match(KeyString path, List<Match<T>> matches) {
145145
for (int i = 0, size = directMatches.size(); i < size; i++) {
146146
matches.add(directMatches.get(i));
147147
}
148-
return;
149148
}
150149

151150
matchRecursive(root, path, 1, new HashMap<>(), matches);
@@ -157,11 +156,12 @@ public void match(String path, List<Match<T>> matches) {
157156

158157
public List<Match<T>> match(KeyString path) {
159158
List<Match<T>> matches = directPathMap.get(path);
160-
if (matches != null) {
161-
return new ArrayList<>(matches);
159+
if (matches == null) {
160+
matches = new ArrayList<>();
161+
} else {
162+
matches = new ArrayList<>(matches);
162163
}
163164

164-
matches = new ArrayList<>();
165165
matchRecursive(root, path, 1, new HashMap<>(), matches);
166166
return matches;
167167
}
@@ -175,8 +175,10 @@ private void matchRecursive(
175175
int end = path.indexOf('/', start);
176176
Node<T> node = current.children.get(new KeyString(path, start, end));
177177
if (node != null) {
178-
if (node.isLeaf()) {
179-
addMatch(node, variableMap, matches);
178+
if (end == -1) {
179+
if (node.isLeaf()) {
180+
addMatch(node, variableMap, matches);
181+
}
180182
return;
181183
}
182184
matchRecursive(node, path, end + 1, variableMap, matches);
@@ -191,13 +193,19 @@ private void matchRecursive(
191193
if (segment.match(path, start, end, workVariableMap)) {
192194
workVariableMap.putAll(variableMap);
193195
Node<T> child = entry.getValue();
194-
if (segment.isTailMatching() || child.isLeaf()) {
196+
if (segment.isTailMatching()) {
195197
addMatch(child, workVariableMap, matches);
196198
} else {
197-
matchRecursive(child, path, end + 1, workVariableMap, matches);
199+
if (end == -1) {
200+
if (child.isLeaf()) {
201+
addMatch(child, workVariableMap, matches);
202+
}
203+
} else {
204+
matchRecursive(child, path, end + 1, workVariableMap, matches);
205+
}
198206
}
199207
if (!workVariableMap.isEmpty()) {
200-
workVariableMap = new HashMap<>();
208+
workVariableMap = new LinkedHashMap<>();
201209
}
202210
}
203211
}

dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathExpression.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,17 +61,21 @@ public Map<String, String> match(@Nonnull String path) {
6161
}
6262
Map<String, String> variableMap = new LinkedHashMap<>();
6363
int start, end = 0;
64-
for (PathSegment segment : segments) {
64+
for (int i = 0, len = segments.length; i < len; i++) {
65+
PathSegment segment = segments[i];
6566
if (end != -1) {
6667
start = end + 1;
6768
end = path.indexOf('/', start);
6869
if (segment.match(new KeyString(path), start, end, variableMap)) {
70+
if (i == len - 1 && segment.isTailMatching()) {
71+
return variableMap;
72+
}
6973
continue;
7074
}
7175
}
7276
return null;
7377
}
74-
return variableMap;
78+
return end == -1 ? variableMap : null;
7579
}
7680

7781
public int compareTo(PathExpression other, String lookupPath) {

dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/util/KeyString.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public String toString() {
101101

102102
public int indexOf(char ch, int start) {
103103
int index = value.indexOf(ch, offset + start);
104-
return index == -1 ? -1 : index - offset;
104+
return index == -1 || index >= offset + length ? -1 : index - offset;
105105
}
106106

107107
public boolean regionMatches(int start, String value, int i, int length) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.dubbo.rpc.protocol.tri.rest.filter
19+
20+
import org.apache.dubbo.common.URL
21+
import org.apache.dubbo.rpc.Invoker
22+
import org.apache.dubbo.rpc.model.ApplicationModel
23+
24+
import spock.lang.Specification
25+
26+
class RestFilterTest extends Specification {
27+
28+
@SuppressWarnings('GroovyAccessibility')
29+
def "test filter patterns"() {
30+
given:
31+
Invoker invoker = Mock(Invoker)
32+
invoker.getUrl() >> URL.valueOf("tri://127.0.0.1/test?extension=org.apache.dubbo.rpc.protocol.tri.rest.filter.TestRestFilter")
33+
34+
var filter = new RestExtensionExecutionFilter(ApplicationModel.defaultModel())
35+
expect:
36+
filter.matchFilters(filter.getFilters(invoker), path).length == len
37+
where:
38+
path | len
39+
'/filter/one' | 1
40+
'/filter/one/1' | 1
41+
'/one.filter' | 2
42+
'/filter/two' | 2
43+
}
44+
}

dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/mapping/RadixTreeTest.groovy

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import spock.lang.Specification
2121

2222
class RadixTreeTest extends Specification {
2323

24-
def "Match"() {
24+
def "match"() {
2525
given:
2626
def tree = new RadixTree<String>()
2727
tree.addPath('/a/*', 'abc')
@@ -33,7 +33,7 @@ class RadixTreeTest extends Specification {
3333
!match.empty
3434
}
3535

36-
def "Clear"() {
36+
def "clear"() {
3737
given:
3838
def tree = new RadixTree<String>()
3939
tree.addPath('/a/*', 'abc')
@@ -44,4 +44,18 @@ class RadixTreeTest extends Specification {
4444
then:
4545
tree.empty
4646
}
47+
48+
def "test end match"() {
49+
given:
50+
def tree = new RadixTree<Boolean>()
51+
tree.addPath('/a/*/*', true)
52+
expect:
53+
tree.match(path).size() == len
54+
where:
55+
path | len
56+
'/a' | 0
57+
'/a/b' | 0
58+
'/a/b/c' | 1
59+
'/a/b/c/d' | 0
60+
}
4761
}

dubbo-rpc/dubbo-rpc-triple/src/test/groovy/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathExpressionTest.groovy

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ class PathExpressionTest extends Specification {
173173
'/resources/**' | '/resources/a/b/c' | true
174174
'/resources/{*path}' | '/resources/a/b/c' | [path: 'a/b/c']
175175
'/resources/*' | '/resources/a' | true
176-
'/resources/*' | '/resources/a/b/c' | true
176+
'/resources/{*}' | '/resources/a/b/c' | true
177177
'/{id:\\d+}' | '/123' | [id: '123']
178178
'/{id:\\d+}' | '/one' | false
179179
'/a?cd/ef' | '/abcd/ef' | true
@@ -194,8 +194,8 @@ class PathExpressionTest extends Specification {
194194
expect:
195195
parse(path).match(value) != null
196196
where:
197-
path | value
198-
'/resources/*' | '/resources/a/b/c'
197+
path | value
198+
'/resources/{*}' | '/resources/a/b/c'
199199
}
200200

201201
def "CompareTo"() {

0 commit comments

Comments
 (0)