From dd05ec45193b0f99768cfa9d9c3ec08ca4cc297c Mon Sep 17 00:00:00 2001 From: Sean Yang Date: Tue, 26 Dec 2023 21:43:11 +0800 Subject: [PATCH] v3 --- .../org/apache/dubbo/common/utils/Pair.java | 4 + .../dubbo/remoting/http12/HttpRequest.java | 2 +- .../dubbo/remoting/http12/HttpUtils.java | 25 - .../http12/message/DefaultHttpRequest.java | 7 +- .../mapping/condition/AbstractCondition.java | 22 - .../mapping/condition/ConsumesCondition.java | 53 ++- .../rest/mapping/condition/Expression.java | 34 -- .../mapping/condition/HeadersCondition.java | 28 +- .../condition/MediaTypeExpression.java | 239 ++++++++-- .../condition/NameValueExpression.java | 68 +-- .../mapping/condition/ParamsCondition.java | 13 +- .../rest/mapping/condition/PathCondition.java | 52 +- .../mapping/condition/PathExpression.java | 443 ++++++++++++++++++ .../rest/mapping/condition/PathSegment.java | 106 +++++ .../mapping/condition/ProducesCondition.java | 174 ++++++- .../protocol/tri/rest/meta/MethodMeta.java | 14 +- .../protocol/tri/rest/meta/ServiceMeta.java | 15 +- 17 files changed, 1080 insertions(+), 219 deletions(-) delete mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/AbstractCondition.java delete mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/Expression.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathExpression.java create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathSegment.java diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/Pair.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/Pair.java index c3742067dac2..fb949f432187 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/Pair.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/Pair.java @@ -77,6 +77,10 @@ public R getRight() { return right; } + public boolean isNull() { + return this == NULL || left == null && right == null; + } + @Override public L getKey() { return left; diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpRequest.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpRequest.java index a9a6fe9f49e5..141f98447411 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpRequest.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpRequest.java @@ -67,7 +67,7 @@ public interface HttpRequest extends RequestMetadata { String charset(); - List accept(); + String accept(); Locale locale(); diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpUtils.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpUtils.java index 175e2b6d99b4..1d96ddcd2806 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpUtils.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpUtils.java @@ -118,31 +118,6 @@ public static List parseAcceptLanguage(String header) { return new ArrayList<>(locales.values()); } - public static Pair parseMimeType(String mimeType) { - if (StringUtils.isEmpty(mimeType)) { - throw new HttpStatusException(415, "mimeType must not be empty"); - } - - int index = mimeType.indexOf(';'); - String fullType = (index == -1 ? mimeType : mimeType.substring(0, index)).trim(); - if (WILDCARD_TYPE.equals(fullType)) { - fullType = "*/*"; - } - int subIndex = fullType.indexOf('/'); - if (subIndex == -1) { - throw new HttpStatusException(415, "mimeType '" + fullType + "' does not contain '/'"); - } - if (subIndex == fullType.length() - 1) { - throw new HttpStatusException(415, "mimeType '" + fullType + "' does not contain subtype after '/'"); - } - String type = fullType.substring(0, subIndex); - String subType = fullType.substring(subIndex + 1); - if (WILDCARD_TYPE.equals(type) && !WILDCARD_TYPE.equals(subType)) { - throw new HttpStatusException(415, "mimeType '" + fullType + "' is invalid"); - } - return new Pair<>(type, subType); - } - public static HttpPostRequestDecoder createPostRequestDecoder( HttpRequest request, InputStream inputStream, String charset) { ByteBuf data; diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpRequest.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpRequest.java index 21f7a81182b5..f63fe5f25324 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpRequest.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultHttpRequest.java @@ -241,11 +241,8 @@ private String getCharset0() { } @Override - public List accept() { - if (accept == null) { - accept = HttpUtils.parseAccept(headers.getFirst("accept-language")); - } - return accept; + public String accept() { + return headers.getFirst(HttpHeaderNames.ACCEPT.getName()); } @Override diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/AbstractCondition.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/AbstractCondition.java deleted file mode 100644 index 4a5757e3c313..000000000000 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/AbstractCondition.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition; - -import org.apache.dubbo.remoting.http12.HttpRequest; - -public abstract class AbstractCondition, R extends HttpRequest> implements Condition { -} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ConsumesCondition.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ConsumesCondition.java index b9d34131c9ba..7997041a9843 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ConsumesCondition.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ConsumesCondition.java @@ -16,11 +16,10 @@ */ package org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition; -import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.remoting.http12.HttpHeaderNames; import org.apache.dubbo.remoting.http12.HttpRequest; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; @@ -28,7 +27,7 @@ public class ConsumesCondition implements Condition { - public static final MediaTypeExpression DEFAULT = new MediaTypeExpression("application/octet-stream"); + public static final MediaTypeExpression DEFAULT = MediaTypeExpression.parse("application/octet-stream"); private final List expressions; @@ -37,32 +36,44 @@ public ConsumesCondition(String... consumes) { } public ConsumesCondition(String[] consumes, String[] headers) { - List expressions = new ArrayList<>(); + Set expressions = null; if (headers != null) { for (String header : headers) { - Expression expr = new NameValueExpression(header); - if ("content-type".equalsIgnoreCase(expr.getName())) { - String mimeTypes = expr.getValue(); - if (StringUtils.isEmpty(mimeTypes)) { + NameValueExpression expr = NameValueExpression.parse(header); + if (HttpHeaderNames.CONTENT_TYPE.getName().equalsIgnoreCase(expr.getName())) { + MediaTypeExpression expression = MediaTypeExpression.parse(expr.getValue()); + if (expression == null) { continue; } - for (String mimeType : StringUtils.tokenize(mimeTypes, ',')) { - expressions.add(new MediaTypeExpression(mimeType)); + if (expressions == null) { + expressions = new LinkedHashSet<>(); } + expressions.add(expression); } } } if (consumes != null) { for (String consume : consumes) { - expressions.add(new MediaTypeExpression(consume)); + MediaTypeExpression expression = MediaTypeExpression.parse(consume); + if (expression == null) { + continue; + } + if (expressions == null) { + expressions = new LinkedHashSet<>(); + } + expressions.add(expression); } } - this.expressions = expressions; + if (expressions == null) { + this.expressions = Collections.emptyList(); + } else { + this.expressions = new ArrayList<>(expressions); + Collections.sort(this.expressions); + } } - private ConsumesCondition(Collection expressions) { - this.expressions = new ArrayList<>(expressions); - Collections.sort(this.expressions); + private ConsumesCondition(List expressions) { + this.expressions = expressions; } @Override @@ -77,14 +88,18 @@ public ConsumesCondition match(HttpRequest request) { } String contentType = request.contentType(); - MediaTypeExpression mediaType = contentType == null ? DEFAULT : new MediaTypeExpression(contentType); - Set result = new LinkedHashSet<>(); - for (MediaTypeExpression expression : expressions) { + MediaTypeExpression mediaType = contentType == null ? DEFAULT : MediaTypeExpression.parse(contentType); + List result = null; + for (int i = 0, len = expressions.size(); i < len; i++) { + MediaTypeExpression expression = expressions.get(i); if (expression.match(mediaType)) { + if (result == null) { + result = new ArrayList<>(); + } result.add(expression); } } - return result.isEmpty() ? null : new ConsumesCondition(result); + return result == null ? null : new ConsumesCondition(result); } @Override diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/Expression.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/Expression.java deleted file mode 100644 index 12abb367ca95..000000000000 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/Expression.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition; - -import java.util.function.Function; - -public interface Expression { - - String getName(); - - T getValue(); - - boolean isNegated(); - - boolean match(Function nameFn, Function valueFn); - - boolean match(Function valueFn); - -} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/HeadersCondition.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/HeadersCondition.java index eb87cf415308..b34ffe7f5c32 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/HeadersCondition.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/HeadersCondition.java @@ -16,44 +16,54 @@ */ package org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition; +import org.apache.dubbo.remoting.http12.HttpHeaderNames; import org.apache.dubbo.remoting.http12.HttpRequest; import java.util.Collection; +import java.util.Collections; import java.util.LinkedHashSet; import java.util.Set; +import static org.apache.dubbo.common.utils.CollectionUtils.isEmpty; +import static org.apache.dubbo.remoting.http12.HttpHeaderNames.ACCEPT; +import static org.apache.dubbo.remoting.http12.HttpHeaderNames.CONTENT_TYPE; + public final class HeadersCondition implements Condition { - private final Set> expressions; + private final Set expressions; public HeadersCondition(String... headers) { - Set> expressions = new LinkedHashSet<>(); + Set expressions = null; if (headers != null) { for (String header : headers) { - Expression expr = new NameValueExpression(header); - if ("accept".equalsIgnoreCase(expr.getName()) || "content-type".equalsIgnoreCase(expr.getName())) { + NameValueExpression expr = NameValueExpression.parse(header); + String name = expr.getName(); + if (ACCEPT.getName().equalsIgnoreCase(name) || CONTENT_TYPE.getName().equalsIgnoreCase(name)) { continue; } + if (expressions == null) { + expressions = new LinkedHashSet<>(); + } expressions.add(expr); } } - this.expressions = expressions; + this.expressions = expressions == null ? Collections.emptySet() : expressions; } - private HeadersCondition(Collection> conditions) { - expressions = new LinkedHashSet<>(conditions); + private HeadersCondition(Collection expressions) { + this.expressions = isEmpty(expressions) ? Collections.emptySet() : new LinkedHashSet<>(expressions); } @Override public HeadersCondition combine(HeadersCondition other) { - Set> set = new LinkedHashSet<>(expressions); + Set set = new LinkedHashSet<>(expressions); set.addAll(other.expressions); return new HeadersCondition(set); } @Override public HeadersCondition match(HttpRequest request) { - for (Expression expression : expressions) { + for (NameValueExpression expression : expressions) { if (!expression.match(request::hasHeader, request::header)) { return null; } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/MediaTypeExpression.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/MediaTypeExpression.java index e240c5c3adf1..80626c9bb424 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/MediaTypeExpression.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/MediaTypeExpression.java @@ -17,29 +17,119 @@ package org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition; -import org.apache.dubbo.common.utils.Pair; -import org.apache.dubbo.remoting.http12.HttpUtils; +import org.apache.dubbo.common.utils.StringUtils; +import java.util.Collections; import java.util.Comparator; +import java.util.List; +import java.util.Objects; import static org.apache.dubbo.remoting.http12.HttpUtils.WILDCARD_TYPE; public final class MediaTypeExpression implements Comparable { + public static final MediaTypeExpression ALL = new MediaTypeExpression(WILDCARD_TYPE, WILDCARD_TYPE); + public static final List ALL_LIST = Collections.singletonList(ALL); + + public static final Comparator COMPARATOR = (m1, m2) -> { + int comparison = compareQuality(m1, m2); + if (comparison != 0) { + return comparison; + } + + comparison = compareType(m1.type, m2.type); + if (comparison != Integer.MIN_VALUE) { + return comparison; + } + + comparison = compareType(m1.subtype, m2.subtype); + return comparison == Integer.MIN_VALUE ? 0 : comparison; + }; + + public static final Comparator QUALITY_COMPARATOR = MediaTypeExpression::compareQuality; + private final String type; private final String subtype; private final boolean negated; + private final float quality; + + private MediaTypeExpression(String type, String subtype, float quality, boolean negated) { + this.type = type; + this.subtype = subtype; + this.quality = quality; + this.negated = negated; + } + + public MediaTypeExpression(String type, String subtype) { + this.type = type; + this.subtype = subtype; + quality = 1F; + negated = false; + } - public MediaTypeExpression(String expression) { - if (expression.indexOf('!') == 0) { + public static MediaTypeExpression parse(String expr) { + boolean negated; + if (expr.indexOf('!') == 0) { negated = true; - expression = expression.substring(1); + expr = expr.substring(1); } else { negated = false; } - Pair pair = HttpUtils.parseMimeType(expression); - type = pair.getKey(); - subtype = pair.getValue(); + if (StringUtils.isEmpty(expr)) { + return null; + } + + int index = expr.indexOf(';'); + String mimeType = (index == -1 ? expr : expr.substring(0, index)).trim(); + if (WILDCARD_TYPE.equals(mimeType)) { + mimeType = "*/*"; + } + int subIndex = mimeType.indexOf('/'); + if (subIndex == -1) { + return null; + } + if (subIndex == mimeType.length() - 1) { + return null; + } + String type = mimeType.substring(0, subIndex); + String subType = mimeType.substring(subIndex + 1); + if (WILDCARD_TYPE.equals(type) && !WILDCARD_TYPE.equals(subType)) { + return null; + } + + return new MediaTypeExpression(type, subType, parseQuality(expr, index), negated); + } + + private static float parseQuality(String expr, int index) { + float quality = 1F; + if (index != -1) { + int qStart = expr.indexOf("q=", index + 1); + if (qStart != -1) { + qStart += 2; + int qEnd = expr.indexOf(',', qStart); + String qString = qEnd == -1 ? expr.substring(qStart) : expr.substring(qStart, qEnd).trim(); + try { + quality = Float.parseFloat(qString); + } catch (NumberFormatException ignored) { + } + } + } + return quality; + } + + private static int compareType(String type1, String type2) { + boolean type1IsWildcard = WILDCARD_TYPE.equals(type1); + boolean type2IsWildcard = WILDCARD_TYPE.equals(type2); + if (type1IsWildcard && !type2IsWildcard) { + return 1; + } + if (type2IsWildcard && !type1IsWildcard) { + return -1; + } + if (!type1.equals(type2)) { + return 0; + } + return Integer.MIN_VALUE; } public String getType() { @@ -50,53 +140,23 @@ public String getSubtype() { return subtype; } - public boolean isNegated() { - return negated; + public float getQuality() { + return quality; } - public boolean isWildcardType() { - return WILDCARD_TYPE.equals(getType()); + private static int compareQuality(MediaTypeExpression m1, MediaTypeExpression m2) { + return Float.compare(m2.quality, m1.quality); } - public boolean isWildcardSubtype() { - return WILDCARD_TYPE.equals(subtype) || getSubtype().startsWith("*+"); + public boolean typesEquals(MediaTypeExpression other) { + return type.equalsIgnoreCase(other.type) && subtype.equalsIgnoreCase(other.subtype); } - @Override - public int compareTo(MediaTypeExpression other) { - return COMPARATOR.compare(this, other); + public boolean match(MediaTypeExpression other) { + return matchMediaType(other) != negated; } - private static final Comparator COMPARATOR = new Comparator() { - @Override - public int compare(MediaTypeExpression mimeType1, MediaTypeExpression mimeType2) { - int x = compareType(mimeType1.subtype, mimeType2.subtype); - if (x != Integer.MIN_VALUE) { - return x; - } - - x = compareType(mimeType1.subtype, mimeType2.subtype); - return x == Integer.MIN_VALUE ? 0 : x; - } - - private int compareType(String type1, String type2) { - boolean type1IsWildcard = WILDCARD_TYPE.equals(type1); - boolean type2IsWildcard = WILDCARD_TYPE.equals(type2); - if (type1IsWildcard && !type2IsWildcard) { - return 1; - } - if (type2IsWildcard && !type1IsWildcard) { - return -1; - } - if (!type1.equals(type2)) { // audio/basic == audio/wave - return 0; - } - return Integer.MIN_VALUE; - } - - }; - - public boolean match(MediaTypeExpression other) { + private boolean matchMediaType(MediaTypeExpression other) { if (other == null) { return false; } @@ -123,4 +183,89 @@ public boolean match(MediaTypeExpression other) { } return false; } + + public boolean compatibleWith(MediaTypeExpression other) { + return compatibleWithMediaType(other) != negated; + } + + private boolean compatibleWithMediaType(MediaTypeExpression other) { + if (other == null) { + return false; + } + if (isWildcardType() || other.isWildcardType()) { + return true; + } + if (type.equals(other.type)) { + if (subtype.equalsIgnoreCase(other.subtype)) { + return true; + } + if (isWildcardSubtype() || other.isWildcardSubtype()) { + if (subtype.equals(WILDCARD_TYPE) || other.subtype.equals(WILDCARD_TYPE)) { + return true; + } + String thisSuffix = getSubtypeSuffix(); + String otherSuffix = other.getSubtypeSuffix(); + if (isWildcardSubtype() && thisSuffix != null) { + return (thisSuffix.equals(other.subtype) || thisSuffix.equals(otherSuffix)); + } + if (other.isWildcardSubtype() && otherSuffix != null) { + return (subtype.equals(otherSuffix) || otherSuffix.equals(thisSuffix)); + } + } + } + return false; + } + + private boolean isWildcardType() { + return WILDCARD_TYPE.equals(type); + } + + private boolean isWildcardSubtype() { + return WILDCARD_TYPE.equals(subtype) || subtype.startsWith("*+"); + } + + private String getSubtypeSuffix() { + int suffixIndex = subtype.lastIndexOf('+'); + if (suffixIndex != -1) { + return subtype.substring(suffixIndex + 1); + } + return null; + } + + @Override + public int compareTo(MediaTypeExpression other) { + return COMPARATOR.compare(this, other); + } + + @Override + public int hashCode() { + return Objects.hash(type, subtype, negated, quality); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof MediaTypeExpression) { + MediaTypeExpression that = (MediaTypeExpression) obj; + return negated == that.negated && Float.compare(quality, that.quality) == 0 && Objects.equals( + type, that.type) && Objects.equals(subtype, that.subtype); + } + return false; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (negated) { + sb.append('!'); + } + sb.append(type).append('/').append(subtype); + if (quality != 1F) { + sb.append(";q=").append(quality); + } + return sb.toString(); + } + } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/NameValueExpression.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/NameValueExpression.java index 1487aa5b5dc7..4d0ee1e21f33 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/NameValueExpression.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/NameValueExpression.java @@ -24,68 +24,76 @@ import java.util.Objects; import java.util.Set; import java.util.function.Function; +import java.util.function.Predicate; -public final class NameValueExpression implements Expression { +public final class NameValueExpression { private final String name; private final String value; private final boolean negated; - public NameValueExpression(String expression) { - int separator = expression.indexOf('='); - if (separator == -1) { - negated = expression.startsWith("!"); - name = negated ? expression.substring(1) : expression; - value = null; - } else { - negated = separator > 0 && expression.charAt(separator - 1) == '!'; - name = negated ? expression.substring(0, separator - 1) : expression.substring(0, separator); - value = expression.substring(separator + 1); - } + private NameValueExpression(String name, String value, boolean negated) { + this.name = name; + this.value = value; + this.negated = negated; + } + + public NameValueExpression(String name, String value) { + this.name = name; + this.value = value; + negated = false; } - public static Set> parse(String... params) { + public static Set parse(String... params) { if (ArrayUtils.isEmpty(params)) { return Collections.emptySet(); } - Set> expressions = new LinkedHashSet<>(params.length); + Set expressions = new LinkedHashSet<>(params.length); for (String param : params) { - expressions.add(new NameValueExpression(param)); + expressions.add(parse(param)); } return expressions; } - @Override + public static NameValueExpression parse(String expr) { + int index = expr.indexOf('='); + if (index == -1) { + boolean negated = expr.indexOf('!') == 0; + return new NameValueExpression(negated ? expr.substring(1) : expr, null, negated); + } else { + boolean negated = index > 0 && expr.charAt(index - 1) == '!'; + return new NameValueExpression(negated ? expr.substring(0, index - 1) : expr.substring(0, index), + expr.substring(index + 1), negated); + } + } + public String getName() { return name; } - @Override public String getValue() { return value; } - @Override - public boolean isNegated() { - return negated; - } - - @Override - public boolean match(Function nameFn, Function valueFn) { + public boolean match(Predicate nameFn, Function valueFn) { boolean matched; if (value == null) { - matched = nameFn.apply(name); + matched = nameFn.test(name); } else { matched = Objects.equals(valueFn.apply(name), value); } return matched != negated; } - @Override public boolean match(Function valueFn) { return match(n -> valueFn.apply(n) != null, valueFn); } + @Override + public int hashCode() { + return Objects.hash(name, value, negated); + } + @Override public boolean equals(Object obj) { if (this == obj) { @@ -98,13 +106,9 @@ public boolean equals(Object obj) { return false; } - @Override - public int hashCode() { - return Objects.hash(name, value, negated); - } - @Override public String toString() { - return "{" + "name='" + name + '\'' + ", value='" + value + '\'' + ", negated=" + negated + '}'; + return name + (negated ? "!=" : "=") + value; } + } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ParamsCondition.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ParamsCondition.java index f61b40a4b115..50807c5c08d7 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ParamsCondition.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ParamsCondition.java @@ -19,31 +19,34 @@ import org.apache.dubbo.remoting.http12.HttpRequest; import java.util.Collection; +import java.util.Collections; import java.util.LinkedHashSet; import java.util.Set; +import static org.apache.dubbo.common.utils.CollectionUtils.isEmpty; + public final class ParamsCondition implements Condition { - private final Set> expressions; + private final Set expressions; public ParamsCondition(String... params) { this(NameValueExpression.parse(params)); } - private ParamsCondition(Collection> conditions) { - expressions = new LinkedHashSet<>(conditions); + private ParamsCondition(Collection expressions) { + this.expressions = isEmpty(expressions) ? Collections.emptySet() : new LinkedHashSet<>(expressions); } @Override public ParamsCondition combine(ParamsCondition other) { - Set> set = new LinkedHashSet<>(expressions); + Set set = new LinkedHashSet<>(expressions); set.addAll(other.expressions); return new ParamsCondition(set); } @Override public ParamsCondition match(HttpRequest request) { - for (Expression expression : expressions) { + for (NameValueExpression expression : expressions) { if (!expression.match(request::hasParameter, request::parameter)) { return null; } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathCondition.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathCondition.java index db74211fcfeb..cef23e7a6813 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathCondition.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathCondition.java @@ -16,11 +16,59 @@ */ package org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition; +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.remoting.http12.HttpRequest; + +import java.util.ArrayList; +import java.util.Collections; import java.util.List; -public class PathCondition { +public class PathCondition implements Condition { + + private final List expressions; + + public PathCondition(String... paths) { + List expressions = null; + if (paths != null) { + for (String path : paths) { + PathExpression expression = PathExpression.parse(path); + if (expression == null) { + continue; + } + if (expressions == null) { + expressions = new ArrayList<>(); + } + expressions.add(expression); + } + } + if (expressions == null) { + this.expressions = Collections.emptyList(); + } else { + this.expressions = new ArrayList<>(expressions); + } + } + + private PathCondition(List expressions) { + this.expressions = CollectionUtils.isEmpty(expressions) ? Collections.emptyList() : expressions; + } + + public List getPaths() { + return expressions; + } + + @Override + public ParamsCondition combine(ParamsCondition other) { + return null; + } - public List paths() { + @Override + public ParamsCondition match(HttpRequest request) { return null; } + + @Override + public int compareTo(ParamsCondition other, HttpRequest request) { + return 0; + } + } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathExpression.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathExpression.java new file mode 100644 index 000000000000..cd88823cc431 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathExpression.java @@ -0,0 +1,443 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition; + +import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.PathSegment.Type; + +import java.util.LinkedList; +import java.util.ListIterator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class PathExpression { + + private static final String ANY = "**"; + + private final PathSegment[] segments; + + private PathExpression(PathSegment[] segments) { + this.segments = segments; + } + + public static PathExpression parse(String path) { + if (path == null || path.isEmpty()) { + return null; + } + LinkedList segments = parseSegments(path); + return new PathExpression(segments.toArray(new PathSegment[0])); + } + + private static LinkedList parseSegments(String path) { + LinkedList segments = new LinkedList<>(); + StringBuilder buf = new StringBuilder(); + int state = State.INITIAL; + boolean regexBraceStart = false; + String variableName = null; + int variableStartIndex = 0; + for (int i = 0, len = path.length(); i < len; i++) { + char c = path.charAt(i); + switch (c) { + case ' ': + case '\t': + case '\n': + case '\r': + if (state == State.INITIAL) { + continue; + } + case '/': + switch (state) { + case State.INITIAL: + case State.SEGMENT_END: + continue; + case State.LITERAL_START: + if (buf.length() > 0) { + segments.add(new PathSegment(Type.LITERAL, buf.toString())); + buf.setLength(0); + } + break; + case State.WILDCARD_START: + segments.add(new PathSegment(Type.WILDCARD, null, buf.toString())); + buf.setLength(0); + break; + case State.VARIABLE_START: + case State.REGEX_VARIABLE_START: + case State.WILDCARD_VARIABLE_START: + segments.add(new PathSegment(Type.LITERAL, path.substring(variableStartIndex, i))); + buf.setLength(0); + break; + } + segments.add(PathSegment.SLASH); + state = State.SEGMENT_END; + continue; + case '?': + switch (state) { + case State.INITIAL: + case State.LITERAL_START: + case State.SEGMENT_END: + state = State.WILDCARD_START; + break; + } + break; + case '*': + switch (state) { + case State.INITIAL: + case State.LITERAL_START: + case State.SEGMENT_END: + state = State.WILDCARD_START; + break; + case State.VARIABLE_START: + if (path.charAt(i - 1) == '{') { + state = State.WILDCARD_VARIABLE_START; + continue; + } + break; + } + break; + case ':': + if (state == State.VARIABLE_START) { + state = State.REGEX_VARIABLE_START; + variableName = buf.toString(); + buf.setLength(0); + continue; + } + break; + case '{': + switch (state) { + case State.INITIAL: + case State.SEGMENT_END: + state = State.VARIABLE_START; + variableStartIndex = i; + continue; + case State.LITERAL_START: + if (buf.length() > 0) { + segments.add(new PathSegment(Type.LITERAL, buf.toString())); + buf.setLength(0); + } + state = State.VARIABLE_START; + variableStartIndex = i; + continue; + case State.VARIABLE_START: + case State.WILDCARD_VARIABLE_START: + state = State.LITERAL_START; + buf.append(path, variableStartIndex, i); + buf.setLength(0); + break; + case State.REGEX_VARIABLE_START: + if (path.charAt(i - 1) != '\\') { + regexBraceStart = true; + } + break; + } + break; + case '}': + switch (state) { + case State.VARIABLE_START: + segments.add(new PathSegment(Type.VARIABLE, buf.toString())); + buf.setLength(0); + state = State.LITERAL_START; + continue; + case State.REGEX_VARIABLE_START: + if (regexBraceStart) { + regexBraceStart = false; + } else { + segments.add(new PathSegment(Type.PATTERN, variableName, buf.toString())); + buf.setLength(0); + state = State.LITERAL_START; + continue; + } + break; + case State.WILDCARD_VARIABLE_START: + segments.add(new PathSegment(Type.WILDCARD, buf.toString())); + buf.setLength(0); + state = State.LITERAL_START; + continue; + } + break; + default: + if (state == State.INITIAL || state == State.SEGMENT_END) { + state = State.LITERAL_START; + } + break; + } + buf.append(c); + } + + if (buf.length() > 0) { + switch (state) { + case State.WILDCARD_START: + segments.add(new PathSegment(Type.WILDCARD, null, buf.toString())); + break; + case State.LITERAL_START: + segments.add(new PathSegment(Type.LITERAL, buf.toString())); + break; + } + } + return segments; + } + + private static void transformSegments(LinkedList segments) { + if (segments.isEmpty()) { + return; + } + ListIterator iterator = segments.listIterator(); + PathSegment p = null, c; + boolean clash = false; + while (iterator.hasNext()) { + c = iterator.next(); + String cValue = c.getValue(); + switch (c.getType()) { + case SLASH: + clash = true; + iterator.remove(); + continue; + case WILDCARD: + if (c.getPattern() == null) { + c.setType(Type.WILDCARD_END); + } else { + c.setType(Type.PATTERN); + c.setPattern(wildcardToRegex(c.getPattern())); + } + break; + case PATTERN: + if (cValue != null) { + c.setPattern("(?<" + cValue + '>' + c.getPattern() + ')'); + c.setValue(null); + } + break; + } + if (clash) { + clash = false; + continue; + } + if (p == null) { + p = c; + continue; + } + String pValue = p.getValue(); + switch (p.getType()) { + case LITERAL: + switch (c.getType()) { + case LITERAL: + p.setValue(pValue + cValue); + iterator.remove(); + continue; + case VARIABLE: + p.setType(Type.PATTERN); + p.setPattern(quoteRegex(pValue) + "(?<" + cValue + ">[^/]+)"); + p.setValue(null); + iterator.remove(); + continue; + case PATTERN: + p.setType(Type.PATTERN); + p.setPattern(quoteRegex(pValue) + "(?<" + cValue + '>' + c.getPattern() + ')'); + p.setValue(null); + iterator.remove(); + continue; + } + break; + case WILDCARD: + if (p.getPattern() == null) { + p.setType(Type.WILDCARD_END); + } else { + p.setType(Type.PATTERN); + p.setPattern(wildcardToRegex(pValue)); + } + break; + case VARIABLE: + switch (c.getType()) { + case LITERAL: + p.setType(Type.PATTERN); + p.setPattern("(?<" + pValue + ">[^/]+)" + quoteRegex(cValue)); + p.setValue(null); + iterator.remove(); + continue; + case VARIABLE: + iterator.remove(); + continue; + case PATTERN: + p.setType(Type.PATTERN); + p.setPattern("(?<" + pValue + ">[^/]+)" + c.getPattern()); + p.setValue(null); + iterator.remove(); + continue; + } + break; + case PATTERN: + switch (c.getType()) { + case LITERAL: + if (pValue == null) { + p.setPattern(p.getPattern() + quoteRegex(cValue)); + } else { + p.setPattern("(?<" + pValue + '>' + p.getPattern() + ')' + quoteRegex(cValue)); + } + iterator.remove(); + continue; + case VARIABLE: + p.setPattern(p.getPattern() + "(?<" + cValue + ">[^/]+)"); + iterator.remove(); + continue; + case PATTERN: + p.setPattern(p.getPattern() + c.getPattern()); + iterator.remove(); + continue; + } + break; + } + p = c; + } + } + + public static void main(String[] args) { + PathExpression expression = PathExpression.parse("asdas*d8asd?asd"); + System.out.println(expression.segments[0].getPattern()); + Matcher matcher = Pattern.compile(expression.segments[0].getPattern()).matcher("a-1.0.0sd.jar"); + if (matcher.matches()) { + System.out.println(matcher.group("version")); + System.out.println(matcher.group("ext")); + } + } + + private static boolean maybeMatchClash(String regex) { + int len = regex.length(); + for (int i = 0; i < len; i++) { + char c = regex.charAt(i); + switch (c) { + case '^': + if (i > 0 && regex.charAt(i - 1) == '[' && i < len - 1 && regex.charAt(i + 1) != '/') { + return true; + } + case '.': + case '/': + if (i == 0 || regex.charAt(i - 1) != '\\') { + return true; + } + case 'S': + case 'W': + if (i > 0 && regex.charAt(i - 1) == '\\') { + return true; + } + break; + } + } + return false; + } + + private static String quoteRegex(String regex) { + int len = regex.length(); + boolean needsQuote = false; + out: + for (int i = 0; i < len; i++) { + char c = regex.charAt(i); + switch (c) { + case '(': + case ')': + case '[': + case ']': + case '$': + case '^': + case '.': + case '{': + case '}': + case '|': + case '\\': + needsQuote = true; + break out; + } + } + if (!needsQuote) { + return regex; + } + StringBuilder sb = new StringBuilder(len + 8); + for (int i = 0; i < len; i++) { + char c = regex.charAt(i); + switch (c) { + case '(': + case ')': + case '[': + case ']': + case '$': + case '^': + case '.': + case '{': + case '}': + case '|': + case '\\': + sb.append('\\'); + sb.append(c); + break; + default: + sb.append(c); + break; + } + } + return sb.toString(); + } + + private static String wildcardToRegex(String wildcard) { + int len = wildcard.length(); + if (len == 2 && ANY.equals(wildcard)) { + return ".*"; + } + StringBuilder sb = new StringBuilder(len + 8); + for (int i = 0; i < len; i++) { + char c = wildcard.charAt(i); + switch (c) { + case '*': + sb.append("[^/]+"); + break; + case '?': + sb.append('.'); + break; + case '(': + case ')': + case '[': + case ']': + case '$': + case '^': + case '.': + case '{': + case '}': + case '|': + case '\\': + sb.append('\\'); + sb.append(c); + break; + default: + sb.append(c); + break; + } + } + return sb.toString(); + } + + public PathSegment[] getSegments() { + return segments; + } + + private interface State { + + int INITIAL = 0; + int LITERAL_START = 1; + int WILDCARD_START = 2; + int VARIABLE_START = 3; + int REGEX_VARIABLE_START = 4; + int WILDCARD_VARIABLE_START = 5; + int SEGMENT_END = 6; + } + +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathSegment.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathSegment.java new file mode 100644 index 000000000000..14f57a6f40fa --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/PathSegment.java @@ -0,0 +1,106 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition; + +public final class PathSegment { + + public static final PathSegment SLASH = new PathSegment(Type.SLASH, "/"); + + private Type type; + private String value; + private String pattern; + + public PathSegment(Type type, String value) { + this.type = type; + this.value = value; + } + + public PathSegment(Type type, String value, String pattern) { + this.type = type; + this.value = value; + this.pattern = pattern; + } + + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getPattern() { + return pattern; + } + + public void setPattern(String pattern) { + this.pattern = pattern; + } + + @Override + public String toString() { + return "{" + "type=" + type + ", value='" + value + '\'' + ", pattern='" + pattern + '\'' + '}'; + } + + public enum Type { + /** + * A slash segment. + * E.g.: '/' + */ + SLASH, + /** + * A literal segment. + * E.g.: 'name' + */ + LITERAL, + /** + * A wildcard segment. + * E.g.: 't?st.*' + */ + WILDCARD, + /** + * A template variable segment. + * E.g.: '{userId}' + */ + VARIABLE, + /** + * A regex variable matching segment. + * E.g.: '{size:\d+}' + */ + SEGMENT_PATTERN, + /** + * A regex variable matching path. + * E.g.: '{var:.*}' + */ + PATTERN, + /** + * A wildcard matching suffix. + * E.g.: 'p/{*path}', 'p/*', 'p/**' + */ + WILDCARD_END + } + +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ProducesCondition.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ProducesCondition.java index 1c4cc068f0d5..00212936970b 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ProducesCondition.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/mapping/condition/ProducesCondition.java @@ -16,4 +16,176 @@ */ package org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition; -public class ProducesCondition {} +import org.apache.dubbo.common.utils.CollectionUtils; +import org.apache.dubbo.common.utils.Pair; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.remoting.http12.HttpHeaderNames; +import org.apache.dubbo.remoting.http12.HttpRequest; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.function.BiPredicate; + +import static java.util.Collections.singletonList; + +public class ProducesCondition implements Condition { + + private static final ProducesCondition ALL = new ProducesCondition(MediaTypeExpression.ALL_LIST); + private static final List ALL_LIST = singletonList(ALL); + + private final List expressions; + + public ProducesCondition(String... produces) { + this(produces, null); + } + + public ProducesCondition(String[] produces, String[] headers) { + Set expressions = null; + if (headers != null) { + for (String header : headers) { + NameValueExpression expr = NameValueExpression.parse(header); + if (HttpHeaderNames.ACCEPT.getName().equalsIgnoreCase(expr.getName())) { + MediaTypeExpression expression = MediaTypeExpression.parse(expr.getValue()); + if (expression == null) { + continue; + } + if (expressions == null) { + expressions = new LinkedHashSet<>(); + } + expressions.add(expression); + } + } + } + if (produces != null) { + for (String consume : produces) { + MediaTypeExpression expression = MediaTypeExpression.parse(consume); + if (expression == null) { + continue; + } + if (expressions == null) { + expressions = new LinkedHashSet<>(); + } + expressions.add(expression); + } + } + if (expressions == null) { + this.expressions = Collections.emptyList(); + } else { + this.expressions = new ArrayList<>(expressions); + Collections.sort(this.expressions); + } + } + + private ProducesCondition(List expressions) { + this.expressions = expressions; + } + + @Override + public ProducesCondition combine(ProducesCondition other) { + return other.expressions.isEmpty() ? this : other; + } + + @Override + public ProducesCondition match(HttpRequest request) { + if (expressions.isEmpty()) { + return null; + } + + List acceptedMediaTypes = getAcceptedMediaTypes(request); + List result = null; + for (int i = 0, len = expressions.size(); i < len; i++) { + MediaTypeExpression expression = expressions.get(i); + for (int j = 0, aLen = acceptedMediaTypes.size(); j < aLen; j++) { + if (expression.compatibleWith(acceptedMediaTypes.get(j))) { + if (result == null) { + result = new ArrayList<>(); + } + result.add(expression); + break; + } + } + } + return result == null ? null : new ProducesCondition(result); + } + + private List getAcceptedMediaTypes(HttpRequest request) { + List values = request.headerValues(HttpHeaderNames.ACCEPT.getName()); + if (CollectionUtils.isEmpty(values)) { + return MediaTypeExpression.ALL_LIST; + } + List mediaTypes = null; + for (int i = 0, len = values.size(); i < len; i++) { + String value = values.get(i); + if (StringUtils.isEmpty(value)) { + continue; + } + for (String item : StringUtils.tokenize(value, ',')) { + MediaTypeExpression expression = MediaTypeExpression.parse(item); + if (expression == null) { + continue; + } + if (mediaTypes == null) { + mediaTypes = new ArrayList<>(); + } + mediaTypes.add(expression); + } + } + if (mediaTypes == null) { + return Collections.emptyList(); + } + mediaTypes.sort(MediaTypeExpression.QUALITY_COMPARATOR.thenComparing(MediaTypeExpression.COMPARATOR)); + return mediaTypes; + } + + @Override + public int compareTo(ProducesCondition other, HttpRequest request) { + if (expressions.isEmpty() && other.expressions.isEmpty()) { + return 0; + } + List mediaTypes = getAcceptedMediaTypes(request); + for (int i = 0, len = mediaTypes.size(); i < len; i++) { + MediaTypeExpression mediaType = mediaTypes.get(i); + Pair thisPair, otherPair; + + thisPair = findMediaType(mediaType, MediaTypeExpression::typesEquals); + otherPair = findMediaType(mediaType, MediaTypeExpression::typesEquals); + int result = compareMediaType(thisPair, otherPair); + if (result != 0) { + return result; + } + + thisPair = findMediaType(mediaType, MediaTypeExpression::compatibleWith); + otherPair = findMediaType(mediaType, MediaTypeExpression::compatibleWith); + result = compareMediaType(thisPair, otherPair); + if (result != 0) { + return result; + } + } + return 0; + } + + private Pair findMediaType( + MediaTypeExpression mediaType, BiPredicate tester) { + List toCompare = expressions.isEmpty() ? MediaTypeExpression.ALL_LIST : expressions; + for (int i = 0; i < toCompare.size(); i++) { + MediaTypeExpression currentMediaType = toCompare.get(i); + if (tester.test(mediaType, currentMediaType)) { + return Pair.of(i, currentMediaType); + } + } + return Pair.of(-1, null); + } + + private int compareMediaType(Pair p1, Pair p2) { + int index1 = p1.getLeft(); + int index2 = p2.getLeft(); + if (index1 != index2) { + return index2 - index1; + } + return index1 != -1 ? p1.getRight().compareTo(p2.getRight()) : 0; + } + +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/meta/MethodMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/meta/MethodMeta.java index a3e2caae159d..ebd59573374b 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/meta/MethodMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/meta/MethodMeta.java @@ -24,13 +24,13 @@ public class MethodMeta { - private final String name; - private final Class returnType; - private final Type genericReturnType; - private final List annotations; - private final Set> annotationTypes; - private final ParameterMeta[] parameters; - private final ServiceMeta service; + private String name; + private Class returnType; + private Type genericReturnType; + private List annotations; + private Set> annotationTypes; + private ParameterMeta[] parameters; + private ServiceMeta service; public MethodMeta( String name, Class returnType, Type genericReturnType, List annotations, diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/meta/ServiceMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/meta/ServiceMeta.java index 0f209e7fad29..8ca77dacf79b 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/meta/ServiceMeta.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/meta/ServiceMeta.java @@ -22,12 +22,12 @@ public class ServiceMeta { - private final Class type; - private final Set> interfaces; - private final List annotations; - private final Set> annotationTypes; + private Class type; + private Set> interfaces; + private List annotations; + private Set> annotationTypes; - private final T target; + private T target; public ServiceMeta( Class type, Set> interfaces, List annotations, Set clazz, RestToolKit toolKit) { - this.type = (Class) clazz; - this.interfaces = toolKit.getInterfaces(clazz); - this.annotations = toolKit.getAnnotations(clazz); - this.annotationTypes = toolKit.getAnnotationTypes(clazz); - this.target = (T) object; } public Class getType() {