diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/URL.java b/dubbo-common/src/main/java/org/apache/dubbo/common/URL.java index e82cd63978..e605643b6a 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/URL.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/URL.java @@ -225,6 +225,8 @@ class URL implements Serializable { } /** + * NOTICE: This method allocate too much objects, we can use {@link URLStrParser#parseDecodedStr(String)} instead. + *

* Parse url string * * @param url URL string @@ -326,13 +328,16 @@ class URL implements Serializable { String methodsString = parameters.get(METHODS_KEY); if (StringUtils.isNotEmpty(methodsString)) { - String[] methods = methodsString.split(","); + List methods = StringUtils.splitToList(methodsString, ','); for (Map.Entry entry : parameters.entrySet()) { String key = entry.getKey(); - for (String method : methods) { - String methodPrefix = method + '.'; - if (key.startsWith(methodPrefix)) { - String realKey = key.substring(methodPrefix.length()); + for (int i = 0; i < methods.size(); i++) { + String method = methods.get(i); + int methodLen = method.length(); + if (key.length() > methodLen + && key.startsWith(method) + && key.charAt(methodLen) == '.') {//equals to: key.startsWith(method + '.') + String realKey = key.substring(methodLen + 1); URL.putMethodParameter(method, realKey, entry.getValue(), methodParameters); } } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/URLStrParser.java b/dubbo-common/src/main/java/org/apache/dubbo/common/URLStrParser.java new file mode 100644 index 0000000000..37afb78143 --- /dev/null +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/URLStrParser.java @@ -0,0 +1,348 @@ +/* + * 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.common; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static org.apache.dubbo.common.utils.StringUtils.EMPTY_STRING; +import static org.apache.dubbo.common.utils.StringUtils.decodeHexByte; +import static org.apache.dubbo.common.utils.Utf8Utils.decodeUtf8; + +public final class URLStrParser { + + private static final char SPACE = 0x20; + + private static final ThreadLocal DECODE_TEMP_BUF = ThreadLocal.withInitial(() -> new TempBuf(1024)); + + private URLStrParser() { + //empty + } + + /** + * @param decodedURLStr : after {@link URL#decode} string + * decodedURLStr format: protocol://username:password@host:port/path?k1=v1&k2=v2 + * [protocol://][username:password@][host:port]/[path][?k1=v1&k2=v2] + */ + public static URL parseDecodedStr(String decodedURLStr) { + Map parameters = null; + int pathEndIdx = decodedURLStr.indexOf('?'); + if (pathEndIdx >= 0) { + parameters = parseDecodedParams(decodedURLStr, pathEndIdx + 1); + } else { + pathEndIdx = decodedURLStr.length(); + } + + String decodedBody = decodedURLStr.substring(0, pathEndIdx); + return parseURLBody(decodedURLStr, decodedBody, parameters); + } + + private static Map parseDecodedParams(String str, int from) { + int len = str.length(); + if (from >= len) { + return Collections.emptyMap(); + } + + TempBuf tempBuf = DECODE_TEMP_BUF.get(); + Map params = new HashMap<>(); + int nameStart = from; + int valueStart = -1; + int i; + for (i = from; i < len; i++) { + char ch = str.charAt(i); + switch (ch) { + case '=': + if (nameStart == i) { + nameStart = i + 1; + } else if (valueStart < nameStart) { + valueStart = i + 1; + } + break; + case ';': + case '&': + addParam(str, false, nameStart, valueStart, i, params, tempBuf); + nameStart = i + 1; + break; + default: + // continue + } + } + addParam(str, false, nameStart, valueStart, i, params, tempBuf); + return params; + } + + /** + * @param fullURLStr : fullURLString + * @param decodedBody : format: [protocol://][username:password@][host:port]/[path] + * @param parameters : + * @return URL + */ + private static URL parseURLBody(String fullURLStr, String decodedBody, Map parameters) { + int starIdx = 0, endIdx = decodedBody.length(); + String protocol = null; + int protoEndIdx = decodedBody.indexOf("://"); + if (protoEndIdx >= 0) { + if (protoEndIdx == 0) { + throw new IllegalStateException("url missing protocol: \"" + fullURLStr + "\""); + } + protocol = decodedBody.substring(0, protoEndIdx); + starIdx = protoEndIdx + 3; + } else { + // case: file:/path/to/file.txt + protoEndIdx = decodedBody.indexOf(":/"); + if (protoEndIdx >= 0) { + if (protoEndIdx == 0) { + throw new IllegalStateException("url missing protocol: \"" + fullURLStr + "\""); + } + protocol = decodedBody.substring(0, protoEndIdx); + starIdx = protoEndIdx + 1; + } + } + + String path = null; + int pathStartIdx = indexOf(decodedBody, '/', starIdx, endIdx); + if (pathStartIdx >= 0) { + path = decodedBody.substring(pathStartIdx + 1); + endIdx = pathStartIdx; + } + + String username = null; + String password = null; + int pwdEndIdx = lastIndexOf(decodedBody, '@', starIdx, endIdx); + if (pwdEndIdx > 0) { + int userNameEndIdx = indexOf(decodedBody, ':', starIdx, pwdEndIdx); + username = decodedBody.substring(starIdx, userNameEndIdx); + password = decodedBody.substring(userNameEndIdx + 1, pwdEndIdx); + starIdx = pwdEndIdx + 1; + } + + String host = null; + int port = 0; + int hostEndIdx = lastIndexOf(decodedBody, ':', starIdx, endIdx); + if (hostEndIdx > 0 && hostEndIdx < decodedBody.length() - 1) { + if (lastIndexOf(decodedBody, '%', starIdx, endIdx) > hostEndIdx) { + // ipv6 address with scope id + // e.g. fe80:0:0:0:894:aeec:f37d:23e1%en0 + // see https://howdoesinternetwork.com/2013/ipv6-zone-id + // ignore + } else { + port = Integer.parseInt(decodedBody.substring(hostEndIdx + 1, endIdx)); + endIdx = hostEndIdx; + } + } + + if (endIdx > starIdx) { + host = decodedBody.substring(starIdx, endIdx); + } + return new URL(protocol, username, password, host, port, path, parameters); + } + + /** + * @param encodedURLStr : after {@link URL#encode(String)} string + * encodedURLStr after decode format: protocol://username:password@host:port/path?k1=v1&k2=v2 + * [protocol://][username:password@][host:port]/[path][?k1=v1&k2=v2] + */ + public static URL parseEncodedStr(String encodedURLStr) { + Map parameters = null; + int pathEndIdx = encodedURLStr.indexOf("%3F");// '?' + if (pathEndIdx >= 0) { + parameters = parseEncodedParams(encodedURLStr, pathEndIdx + 3); + } else { + pathEndIdx = encodedURLStr.length(); + } + + //decodedBody format: [protocol://][username:password@][host:port]/[path] + String decodedBody = decodeComponent(encodedURLStr, 0, pathEndIdx, false, DECODE_TEMP_BUF.get()); + return parseURLBody(encodedURLStr, decodedBody, parameters); + } + + private static Map parseEncodedParams(String str, int from) { + int len = str.length(); + if (from >= len) { + return Collections.emptyMap(); + } + + TempBuf tempBuf = DECODE_TEMP_BUF.get(); + Map params = new HashMap<>(); + int nameStart = from; + int valueStart = -1; + int i; + for (i = from; i < len; i++) { + char ch = str.charAt(i); + if (ch == '%') { + if (i + 3 > len) { + throw new IllegalArgumentException("unterminated escape sequence at index " + i + " of: " + str); + } + ch = (char) decodeHexByte(str, i + 1); + i += 2; + } + + switch (ch) { + case '=': + if (nameStart == i) { + nameStart = i + 1; + } else if (valueStart < nameStart) { + valueStart = i + 1; + } + break; + case ';': + case '&': + addParam(str, true, nameStart, valueStart, i - 2, params, tempBuf); + nameStart = i + 1; + break; + default: + // continue + } + } + addParam(str, true, nameStart, valueStart, i, params, tempBuf); + return params; + } + + private static boolean addParam(String str, boolean isEncoded, int nameStart, int valueStart, int valueEnd, Map params, + TempBuf tempBuf) { + if (nameStart >= valueEnd) { + return false; + } + + if (valueStart <= nameStart) { + valueStart = valueEnd + 1; + } + + if (isEncoded) { + String name = decodeComponent(str, nameStart, valueStart - 3, false, tempBuf); + String value = decodeComponent(str, valueStart, valueEnd, false, tempBuf); + params.put(name, value); + } else { + String name = str.substring(nameStart, valueStart -1); + String value = str.substring(valueStart, valueEnd); + params.put(name, value); + } + return true; + } + + private static String decodeComponent(String s, int from, int toExcluded, boolean isPath, TempBuf tempBuf) { + int len = toExcluded - from; + if (len <= 0) { + return EMPTY_STRING; + } + + int firstEscaped = -1; + for (int i = from; i < toExcluded; i++) { + char c = s.charAt(i); + if (c == '%' || c == '+' && !isPath) { + firstEscaped = i; + break; + } + } + if (firstEscaped == -1) { + return s.substring(from, toExcluded); + } + + // Each encoded byte takes 3 characters (e.g. "%20") + int decodedCapacity = (toExcluded - firstEscaped) / 3; + byte[] buf = tempBuf.byteBuf(decodedCapacity); + char[] charBuf = tempBuf.charBuf(len); + s.getChars(from, firstEscaped, charBuf, 0); + + int charBufIdx = firstEscaped - from; + return decodeUtf8Component(s, firstEscaped, toExcluded, isPath, buf, charBuf, charBufIdx); + } + + private static String decodeUtf8Component(String str, int firstEscaped, int toExcluded, boolean isPath, byte[] buf, + char[] charBuf, int charBufIdx) { + int bufIdx; + for (int i = firstEscaped; i < toExcluded; i++) { + char c = str.charAt(i); + if (c != '%') { + charBuf[charBufIdx++] = c != '+' || isPath ? c : SPACE; + continue; + } + + bufIdx = 0; + do { + if (i + 3 > toExcluded) { + throw new IllegalArgumentException("unterminated escape sequence at index " + i + " of: " + str); + } + buf[bufIdx++] = decodeHexByte(str, i + 1); + i += 3; + } while (i < toExcluded && str.charAt(i) == '%'); + i--; + + charBufIdx += decodeUtf8(buf, 0, bufIdx, charBuf, charBufIdx); + } + return new String(charBuf, 0, charBufIdx); + } + + private static int indexOf(String str, char ch, int from, int toExclude) { + from = Math.max(from, 0); + toExclude = Math.min(toExclude, str.length()); + if (from > toExclude) { + return -1; + } + + for (int i = from; i < toExclude; i++) { + if (str.charAt(i) == ch) { + return i; + } + } + return -1; + } + + private static int lastIndexOf(String str, char ch, int from, int toExclude) { + from = Math.max(from, 0); + toExclude = Math.min(toExclude, str.length() - 1); + if (from > toExclude) { + return -1; + } + + for (int i = toExclude; i >= from; i--) { + if (str.charAt(i) == ch) { + return i; + } + } + return -1; + } + + private static final class TempBuf { + + private final char[] chars; + + private final byte[] bytes; + + TempBuf(int bufSize) { + this.chars = new char[bufSize]; + this.bytes = new byte[bufSize]; + } + + public char[] charBuf(int size) { + char[] chars = this.chars; + if (size <= chars.length) { + return chars; + } + return new char[size]; + } + + public byte[] byteBuf(int size) { + byte[] bytes = this.bytes; + if (size <= bytes.length) { + return bytes; + } + return new byte[size]; + } + } +} diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java index a941484dfb..fe410e2feb 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java @@ -17,6 +17,8 @@ package org.apache.dubbo.common.constants; +import org.apache.dubbo.common.URL; + import java.net.NetworkInterface; import java.util.concurrent.ExecutorService; import java.util.regex.Pattern; @@ -52,6 +54,8 @@ public interface CommonConstants { String PROTOCOL_SEPARATOR = "://"; + String PROTOCOL_SEPARATOR_ENCODED = URL.encode(PROTOCOL_SEPARATOR); + String REGISTRY_SEPARATOR = "|"; Pattern REGISTRY_SPLIT_PATTERN = Pattern.compile("\\s*[|;]+\\s*"); diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java index 9b910325bc..42ee05bbe5 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/StringUtils.java @@ -24,6 +24,7 @@ import com.alibaba.fastjson.JSON; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -60,6 +61,8 @@ public final class StringUtils { private static final Pattern PARAMETERS_PATTERN = Pattern.compile("^\\[((\\s*\\{\\s*[\\w_\\-\\.]+\\s*:\\s*.+?\\s*\\}\\s*,?\\s*)+)\\s*\\]$"); private static final Pattern PAIR_PARAMETERS_PATTERN = Pattern.compile("^\\{\\s*([\\w-_\\.]+)\\s*:\\s*(.+)\\s*\\}$"); private static final int PAD_LIMIT = 8192; + private static final byte[] HEX2B; + /** * @since 2.7.5 @@ -88,6 +91,33 @@ public final class StringUtils { public static final String HYPHEN = valueOf(HYPHEN_CHAR); + static { + HEX2B = new byte[128]; + Arrays.fill(HEX2B, (byte) -1); + HEX2B['0'] = (byte) 0; + HEX2B['1'] = (byte) 1; + HEX2B['2'] = (byte) 2; + HEX2B['3'] = (byte) 3; + HEX2B['4'] = (byte) 4; + HEX2B['5'] = (byte) 5; + HEX2B['6'] = (byte) 6; + HEX2B['7'] = (byte) 7; + HEX2B['8'] = (byte) 8; + HEX2B['9'] = (byte) 9; + HEX2B['A'] = (byte) 10; + HEX2B['B'] = (byte) 11; + HEX2B['C'] = (byte) 12; + HEX2B['D'] = (byte) 13; + HEX2B['E'] = (byte) 14; + HEX2B['F'] = (byte) 15; + HEX2B['a'] = (byte) 10; + HEX2B['b'] = (byte) 11; + HEX2B['c'] = (byte) 12; + HEX2B['d'] = (byte) 13; + HEX2B['e'] = (byte) 14; + HEX2B['f'] = (byte) 15; + } + private StringUtils() { } @@ -1000,4 +1030,25 @@ public final class StringUtils { } return parameters; } + + public static int decodeHexNibble(final char c) { + // Character.digit() is not used here, as it addresses a larger + // set of characters (both ASCII and full-width latin letters). + byte[] hex2b = HEX2B; + return c < hex2b.length ? hex2b[c] : -1; + } + + /** + * Decode a 2-digit hex byte from within a string. + */ + public static byte decodeHexByte(CharSequence s, int pos) { + int hi = decodeHexNibble(s.charAt(pos)); + int lo = decodeHexNibble(s.charAt(pos + 1)); + if (hi == -1 || lo == -1) { + throw new IllegalArgumentException(String.format( + "invalid hex byte '%s' at index %d of '%s'", s.subSequence(pos, pos + 2), pos, s)); + } + return (byte) ((hi << 4) + lo); + } + } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/Utf8Utils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/Utf8Utils.java new file mode 100644 index 0000000000..09d5508e22 --- /dev/null +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/Utf8Utils.java @@ -0,0 +1,229 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +package org.apache.dubbo.common.utils; + +import static java.lang.Character.MIN_HIGH_SURROGATE; +import static java.lang.Character.MIN_LOW_SURROGATE; +import static java.lang.Character.MIN_SUPPLEMENTARY_CODE_POINT; + +/** + * See original Utf8.java + */ +public final class Utf8Utils { + + private Utf8Utils() { + //empty + } + + public static int decodeUtf8(byte[] srcBytes, int srcIdx, int srcSize, char[] destChars, int destIdx) { + // Bitwise OR combines the sign bits so any negative value fails the check. + if ((srcIdx | srcSize | srcBytes.length - srcIdx - srcSize) < 0 + || (destIdx | destChars.length - destIdx - srcSize) < 0) { + String exMsg = String.format("buffer srcBytes.length=%d, srcIdx=%d, srcSize=%d, destChars.length=%d, " + + "destIdx=%d", srcBytes.length, srcIdx, srcSize, destChars.length, destIdx); + throw new ArrayIndexOutOfBoundsException( + exMsg); + } + + int offset = srcIdx; + final int limit = offset + srcSize; + final int destIdx0 = destIdx; + + // Optimize for 100% ASCII (Hotspot loves small simple top-level loops like this). + // This simple loop stops when we encounter a byte >= 0x80 (i.e. non-ASCII). + while (offset < limit) { + byte b = srcBytes[offset]; + if (!DecodeUtil.isOneByte(b)) { + break; + } + offset++; + DecodeUtil.handleOneByteSafe(b, destChars, destIdx++); + } + + while (offset < limit) { + byte byte1 = srcBytes[offset++]; + if (DecodeUtil.isOneByte(byte1)) { + DecodeUtil.handleOneByteSafe(byte1, destChars, destIdx++); + // It's common for there to be multiple ASCII characters in a run mixed in, so add an + // extra optimized loop to take care of these runs. + while (offset < limit) { + byte b = srcBytes[offset]; + if (!DecodeUtil.isOneByte(b)) { + break; + } + offset++; + DecodeUtil.handleOneByteSafe(b, destChars, destIdx++); + } + } else if (DecodeUtil.isTwoBytes(byte1)) { + if (offset >= limit) { + throw new IllegalArgumentException("invalid UTF-8."); + } + DecodeUtil.handleTwoBytesSafe(byte1, /* byte2 */ srcBytes[offset++], destChars, destIdx++); + } else if (DecodeUtil.isThreeBytes(byte1)) { + if (offset >= limit - 1) { + throw new IllegalArgumentException("invalid UTF-8."); + } + DecodeUtil.handleThreeBytesSafe( + byte1, + /* byte2 */ srcBytes[offset++], + /* byte3 */ srcBytes[offset++], + destChars, + destIdx++); + } else { + if (offset >= limit - 2) { + throw new IllegalArgumentException("invalid UTF-8."); + } + DecodeUtil.handleFourBytesSafe( + byte1, + /* byte2 */ srcBytes[offset++], + /* byte3 */ srcBytes[offset++], + /* byte4 */ srcBytes[offset++], + destChars, + destIdx); + destIdx += 2; + } + } + return destIdx - destIdx0; + } + + + private static class DecodeUtil { + + /** + * Returns whether this is a single-byte codepoint (i.e., ASCII) with the form '0XXXXXXX'. + */ + private static boolean isOneByte(byte b) { + return b >= 0; + } + + /** + * Returns whether this is a two-byte codepoint with the form '10XXXXXX'. + */ + private static boolean isTwoBytes(byte b) { + return b < (byte) 0xE0; + } + + /** + * Returns whether this is a three-byte codepoint with the form '110XXXXX'. + */ + private static boolean isThreeBytes(byte b) { + return b < (byte) 0xF0; + } + + private static void handleOneByteSafe(byte byte1, char[] resultArr, int resultPos) { + resultArr[resultPos] = (char) byte1; + } + + private static void handleTwoBytesSafe(byte byte1, byte byte2, char[] resultArr, int resultPos) { + checkUtf8(byte1, byte2); + resultArr[resultPos] = (char) (((byte1 & 0x1F) << 6) | trailingByteValue(byte2)); + } + + private static void checkUtf8(byte byte1, byte byte2) { + // Simultaneously checks for illegal trailing-byte in leading position (<= '11000000') and + // overlong 2-byte, '11000001'. + if (byte1 < (byte) 0xC2 || isNotTrailingByte(byte2)) { + throw new IllegalArgumentException("invalid UTF-8."); + } + } + + private static void handleThreeBytesSafe(byte byte1, byte byte2, byte byte3, char[] resultArr, int resultPos) { + checkUtf8(byte1, byte2, byte3); + resultArr[resultPos] = + (char) (((byte1 & 0x0F) << 12) | (trailingByteValue(byte2) << 6) | trailingByteValue(byte3)); + } + + private static void checkUtf8(byte byte1, byte byte2, byte byte3) { + if (isNotTrailingByte(byte2) + // overlong? 5 most significant bits must not all be zero + || (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0) + // check for illegal surrogate codepoints + || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0) + || isNotTrailingByte(byte3)) { + throw new IllegalArgumentException("invalid UTF-8."); + } + } + + private static void handleFourBytesSafe(byte byte1, byte byte2, byte byte3, byte byte4, char[] resultArr, + int resultPos) { + checkUtf8(byte1, byte2, byte3, byte4); + int codepoint = + ((byte1 & 0x07) << 18) + | (trailingByteValue(byte2) << 12) + | (trailingByteValue(byte3) << 6) + | trailingByteValue(byte4); + + resultArr[resultPos] = DecodeUtil.highSurrogate(codepoint); + resultArr[resultPos + 1] = DecodeUtil.lowSurrogate(codepoint); + } + + private static void checkUtf8(byte byte1, byte byte2, byte byte3, byte byte4) { + if (isNotTrailingByte(byte2) + // Check that 1 <= plane <= 16. Tricky optimized form of: + // valid 4-byte leading byte? + // if (byte1 > (byte) 0xF4 || + // overlong? 4 most significant bits must not all be zero + // byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 || + // codepoint larger than the highest code point (U+10FFFF)? + // byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F) + || (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0 + || isNotTrailingByte(byte3) + || isNotTrailingByte(byte4)) { + throw new IllegalArgumentException("invalid UTF-8."); + } + } + + /** + * Returns whether the byte is not a valid continuation of the form '10XXXXXX'. + */ + private static boolean isNotTrailingByte(byte b) { + return b > (byte) 0xBF; + } + + /** + * Returns the actual value of the trailing byte (removes the prefix '10') for composition. + */ + private static int trailingByteValue(byte b) { + return b & 0x3F; + } + + private static char highSurrogate(int codePoint) { + return (char) + ((MIN_HIGH_SURROGATE - (MIN_SUPPLEMENTARY_CODE_POINT >>> 10)) + (codePoint >>> 10)); + } + + private static char lowSurrogate(int codePoint) { + return (char) (MIN_LOW_SURROGATE + (codePoint & 0x3ff)); + } + } + +} diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/URLStrParserTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/URLStrParserTest.java new file mode 100644 index 0000000000..3ca62ce83c --- /dev/null +++ b/dubbo-common/src/test/java/org/apache/dubbo/common/URLStrParserTest.java @@ -0,0 +1,40 @@ +/* + * 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.common; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Created by LinShunkang on 2020/03/12 + */ +public class URLStrParserTest { + + @Test + public void test() { + String str = "dubbo%3A%2F%2Fadmin%3Aadmin123%40192.168.1.41%3A28113%2Forg.test.api.DemoService%24Iface%3Fanyhost%3Dtrue%26application%3Ddemo-service%26dubbo%3D2.6.1%26generic%3Dfalse%26interface%3Dorg.test.api.DemoService%24Iface%26methods%3DorbCompare%2CcheckText%2CcheckPicture%26pid%3D65557%26revision%3D1.4.17%26service.filter%3DbootMetrics%26side%3Dprovider%26status%3Dserver%26threads%3D200%26timestamp%3D1583136298859%26version%3D1.0.0"; + System.out.println(URLStrParser.parseEncodedStr(str)); + + String decodeStr = URL.decode(str); + URL originalUrl = URL.valueOf(decodeStr); + assertThat(URLStrParser.parseEncodedStr(str), equalTo(originalUrl)); + assertThat(URLStrParser.parseDecodedStr(decodeStr), equalTo(originalUrl)); + } + +} diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/URLTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/URLTest.java index 1ef4d1dc49..db5f57b1c5 100644 --- a/dubbo-common/src/test/java/org/apache/dubbo/common/URLTest.java +++ b/dubbo-common/src/test/java/org/apache/dubbo/common/URLTest.java @@ -40,6 +40,7 @@ public class URLTest { @Test public void test_valueOf_noProtocolAndHost() throws Exception { URL url = URL.valueOf("/context/path?version=1.0.0&application=morgan"); + assertURLStrDecoder(url); assertNull(url.getProtocol()); assertNull(url.getUsername()); assertNull(url.getPassword()); @@ -51,9 +52,9 @@ public class URLTest { assertEquals("1.0.0", url.getParameter("version")); assertEquals("morgan", url.getParameter("application")); - url = URL.valueOf("context/path?version=1.0.0&application=morgan"); // ^^^^^^^ Caution , parse as host + assertURLStrDecoder(url); assertNull(url.getProtocol()); assertNull(url.getUsername()); assertNull(url.getPassword()); @@ -65,9 +66,19 @@ public class URLTest { assertEquals("morgan", url.getParameter("application")); } + private void assertURLStrDecoder(URL url) { + String fullURLStr = url.toFullString(); + URL newUrl = URLStrParser.parseEncodedStr(URL.encode(fullURLStr)); + assertEquals(URL.valueOf(fullURLStr), newUrl); + + URL newUrl2 = URLStrParser.parseDecodedStr(fullURLStr); + assertEquals(URL.valueOf(fullURLStr), newUrl2); + } + @Test public void test_valueOf_noProtocol() throws Exception { URL url = URL.valueOf("10.20.130.230"); + assertURLStrDecoder(url); assertNull(url.getProtocol()); assertNull(url.getUsername()); assertNull(url.getPassword()); @@ -78,6 +89,7 @@ public class URLTest { assertEquals(0, url.getParameters().size()); url = URL.valueOf("10.20.130.230:20880"); + assertURLStrDecoder(url); assertNull(url.getProtocol()); assertNull(url.getUsername()); assertNull(url.getPassword()); @@ -88,6 +100,7 @@ public class URLTest { assertEquals(0, url.getParameters().size()); url = URL.valueOf("10.20.130.230/context/path"); + assertURLStrDecoder(url); assertNull(url.getProtocol()); assertNull(url.getUsername()); assertNull(url.getPassword()); @@ -98,6 +111,7 @@ public class URLTest { assertEquals(0, url.getParameters().size()); url = URL.valueOf("10.20.130.230:20880/context/path"); + assertURLStrDecoder(url); assertNull(url.getProtocol()); assertNull(url.getUsername()); assertNull(url.getPassword()); @@ -108,6 +122,7 @@ public class URLTest { assertEquals(0, url.getParameters().size()); url = URL.valueOf("admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan"); + assertURLStrDecoder(url); assertNull(url.getProtocol()); assertEquals("admin", url.getUsername()); assertEquals("hello1234", url.getPassword()); @@ -123,6 +138,7 @@ public class URLTest { @Test public void test_valueOf_noHost() throws Exception { URL url = URL.valueOf("file:///home/user1/router.js"); + assertURLStrDecoder(url); assertEquals("file", url.getProtocol()); assertNull(url.getUsername()); assertNull(url.getPassword()); @@ -135,6 +151,7 @@ public class URLTest { // Caution!! url = URL.valueOf("file://home/user1/router.js"); // ^^ only tow slash! + assertURLStrDecoder(url); assertEquals("file", url.getProtocol()); assertNull(url.getUsername()); assertNull(url.getPassword()); @@ -145,6 +162,7 @@ public class URLTest { url = URL.valueOf("file:/home/user1/router.js"); + assertURLStrDecoder(url); assertEquals("file", url.getProtocol()); assertNull(url.getUsername()); assertNull(url.getPassword()); @@ -155,6 +173,7 @@ public class URLTest { assertEquals(0, url.getParameters().size()); url = URL.valueOf("file:///d:/home/user1/router.js"); + assertURLStrDecoder(url); assertEquals("file", url.getProtocol()); assertNull(url.getUsername()); assertNull(url.getPassword()); @@ -165,6 +184,7 @@ public class URLTest { assertEquals(0, url.getParameters().size()); url = URL.valueOf("file:///home/user1/router.js?p1=v1&p2=v2"); + assertURLStrDecoder(url); assertEquals("file", url.getProtocol()); assertNull(url.getUsername()); assertNull(url.getPassword()); @@ -179,6 +199,7 @@ public class URLTest { assertEquals(params, url.getParameters()); url = URL.valueOf("file:/home/user1/router.js?p1=v1&p2=v2"); + assertURLStrDecoder(url); assertEquals("file", url.getProtocol()); assertNull(url.getUsername()); assertNull(url.getPassword()); @@ -196,6 +217,7 @@ public class URLTest { @Test public void test_valueOf_WithProtocolHost() throws Exception { URL url = URL.valueOf("dubbo://10.20.130.230"); + assertURLStrDecoder(url); assertEquals("dubbo", url.getProtocol()); assertNull(url.getUsername()); assertNull(url.getPassword()); @@ -206,6 +228,7 @@ public class URLTest { assertEquals(0, url.getParameters().size()); url = URL.valueOf("dubbo://10.20.130.230:20880/context/path"); + assertURLStrDecoder(url); assertEquals("dubbo", url.getProtocol()); assertNull(url.getUsername()); assertNull(url.getPassword()); @@ -216,6 +239,7 @@ public class URLTest { assertEquals(0, url.getParameters().size()); url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880"); + assertURLStrDecoder(url); assertEquals("dubbo", url.getProtocol()); assertEquals("admin", url.getUsername()); assertEquals("hello1234", url.getPassword()); @@ -226,6 +250,7 @@ public class URLTest { assertEquals(0, url.getParameters().size()); url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880?version=1.0.0"); + assertURLStrDecoder(url); assertEquals("dubbo", url.getProtocol()); assertEquals("admin", url.getUsername()); assertEquals("hello1234", url.getPassword()); @@ -237,6 +262,7 @@ public class URLTest { assertEquals("1.0.0", url.getParameter("version")); url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan"); + assertURLStrDecoder(url); assertEquals("dubbo", url.getProtocol()); assertEquals("admin", url.getUsername()); assertEquals("hello1234", url.getPassword()); @@ -249,6 +275,7 @@ public class URLTest { assertEquals("morgan", url.getParameter("application")); url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan&noValue"); + assertURLStrDecoder(url); assertEquals("dubbo", url.getProtocol()); assertEquals("admin", url.getUsername()); assertEquals("hello1234", url.getPassword()); @@ -266,6 +293,7 @@ public class URLTest { @Test public void test_valueOf_spaceSafe() throws Exception { URL url = URL.valueOf("http://1.2.3.4:8080/path?key=value1 value2"); + assertURLStrDecoder(url); assertEquals("http://1.2.3.4:8080/path?key=value1 value2", url.toString()); assertEquals("value1 value2", url.getParameter("key")); } @@ -274,6 +302,7 @@ public class URLTest { public void test_noValueKey() throws Exception { URL url = URL.valueOf("http://1.2.3.4:8080/path?k0&k1=v1"); + assertURLStrDecoder(url); assertTrue(url.hasParameter("k0")); // If a Key has no corresponding Value, then the Key also used as the Value. @@ -288,38 +317,59 @@ public class URLTest { } catch (IllegalStateException expected) { assertEquals("url missing protocol: \"://1.2.3.4:8080/path\"", expected.getMessage()); } + + try { + String encodedURLStr = URL.encode("://1.2.3.4:8080/path"); + URLStrParser.parseEncodedStr(encodedURLStr); + fail(); + } catch (IllegalStateException expected) { + assertEquals("url missing protocol: \"://1.2.3.4:8080/path\"", URL.decode(expected.getMessage())); + } + + try { + URLStrParser.parseDecodedStr("://1.2.3.4:8080/path"); + fail(); + } catch (IllegalStateException expected) { + assertEquals("url missing protocol: \"://1.2.3.4:8080/path\"", expected.getMessage()); + } } @Test public void test_getAddress() throws Exception { URL url1 = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan"); + assertURLStrDecoder(url1); assertEquals("10.20.130.230:20880", url1.getAddress()); } @Test public void test_getAbsolutePath() throws Exception { URL url = new URL("p1", "1.2.2.2", 33); + assertURLStrDecoder(url); assertNull(url.getAbsolutePath()); url = new URL("file", null, 90, "/home/user1/route.js"); + assertURLStrDecoder(url); assertEquals("/home/user1/route.js", url.getAbsolutePath()); } @Test public void test_equals() throws Exception { URL url1 = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan"); + assertURLStrDecoder(url1); Map params = new HashMap(); params.put("version", "1.0.0"); params.put("application", "morgan"); URL url2 = new URL("dubbo", "admin", "hello1234", "10.20.130.230", 20880, "context/path", params); + assertURLStrDecoder(url2); assertEquals(url1, url2); } @Test public void test_toString() throws Exception { URL url1 = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan"); + assertURLStrDecoder(url1); assertThat(url1.toString(), anyOf( equalTo("dubbo://10.20.130.230:20880/context/path?version=1.0.0&application=morgan"), equalTo("dubbo://10.20.130.230:20880/context/path?application=morgan&version=1.0.0")) @@ -329,6 +379,7 @@ public class URLTest { @Test public void test_toFullString() throws Exception { URL url1 = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan"); + assertURLStrDecoder(url1); assertThat(url1.toFullString(), anyOf( equalTo("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan"), equalTo("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan&version=1.0.0")) @@ -338,9 +389,11 @@ public class URLTest { @Test public void test_set_methods() throws Exception { URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan"); + assertURLStrDecoder(url); url = url.setHost("host"); + assertURLStrDecoder(url); assertEquals("dubbo", url.getProtocol()); assertEquals("admin", url.getUsername()); assertEquals("hello1234", url.getPassword()); @@ -354,6 +407,7 @@ public class URLTest { url = url.setPort(1); + assertURLStrDecoder(url); assertEquals("dubbo", url.getProtocol()); assertEquals("admin", url.getUsername()); assertEquals("hello1234", url.getPassword()); @@ -367,6 +421,7 @@ public class URLTest { url = url.setPath("path"); + assertURLStrDecoder(url); assertEquals("dubbo", url.getProtocol()); assertEquals("admin", url.getUsername()); assertEquals("hello1234", url.getPassword()); @@ -380,6 +435,7 @@ public class URLTest { url = url.setProtocol("protocol"); + assertURLStrDecoder(url); assertEquals("protocol", url.getProtocol()); assertEquals("admin", url.getUsername()); assertEquals("hello1234", url.getPassword()); @@ -393,6 +449,7 @@ public class URLTest { url = url.setUsername("username"); + assertURLStrDecoder(url); assertEquals("protocol", url.getProtocol()); assertEquals("username", url.getUsername()); assertEquals("hello1234", url.getPassword()); @@ -406,6 +463,7 @@ public class URLTest { url = url.setPassword("password"); + assertURLStrDecoder(url); assertEquals("protocol", url.getProtocol()); assertEquals("username", url.getUsername()); assertEquals("password", url.getPassword()); @@ -421,8 +479,10 @@ public class URLTest { @Test public void test_removeParameters() throws Exception { URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan&k1=v1&k2=v2"); + assertURLStrDecoder(url); url = url.removeParameter("version"); + assertURLStrDecoder(url); assertEquals("dubbo", url.getProtocol()); assertEquals("admin", url.getUsername()); assertEquals("hello1234", url.getPassword()); @@ -438,6 +498,7 @@ public class URLTest { url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan&k1=v1&k2=v2"); url = url.removeParameters("version", "application", "NotExistedKey"); + assertURLStrDecoder(url); assertEquals("dubbo", url.getProtocol()); assertEquals("admin", url.getUsername()); assertEquals("hello1234", url.getPassword()); @@ -453,6 +514,7 @@ public class URLTest { url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan&k1=v1&k2=v2"); url = url.removeParameters(Arrays.asList("version", "application")); + assertURLStrDecoder(url); assertEquals("dubbo", url.getProtocol()); assertEquals("admin", url.getUsername()); assertEquals("hello1234", url.getPassword()); @@ -472,6 +534,7 @@ public class URLTest { URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan"); url = url.addParameter("k1", "v1"); + assertURLStrDecoder(url); assertEquals("dubbo", url.getProtocol()); assertEquals("admin", url.getUsername()); assertEquals("hello1234", url.getPassword()); @@ -489,6 +552,7 @@ public class URLTest { URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan&k1=v1"); URL newUrl = url.addParameter("k1", "v1"); + assertURLStrDecoder(url); assertSame(newUrl, url); } @@ -498,6 +562,7 @@ public class URLTest { URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan"); url = url.addParameters(CollectionUtils.toStringMap("k1", "v1", "k2", "v2")); + assertURLStrDecoder(url); assertEquals("dubbo", url.getProtocol()); assertEquals("admin", url.getUsername()); assertEquals("hello1234", url.getPassword()); @@ -513,6 +578,7 @@ public class URLTest { url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan"); url = url.addParameters("k1", "v1", "k2", "v2", "application", "xxx"); + assertURLStrDecoder(url); assertEquals("dubbo", url.getProtocol()); assertEquals("admin", url.getUsername()); assertEquals("hello1234", url.getPassword()); @@ -528,6 +594,7 @@ public class URLTest { url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan"); url = url.addParametersIfAbsent(CollectionUtils.toStringMap("k1", "v1", "k2", "v2", "application", "xxx")); + assertURLStrDecoder(url); assertEquals("dubbo", url.getProtocol()); assertEquals("admin", url.getUsername()); assertEquals("hello1234", url.getPassword()); @@ -543,6 +610,7 @@ public class URLTest { url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan"); url = url.addParameter("k1", "v1"); + assertURLStrDecoder(url); assertEquals("dubbo", url.getProtocol()); assertEquals("admin", url.getUsername()); assertEquals("hello1234", url.getPassword()); @@ -557,6 +625,7 @@ public class URLTest { url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan"); url = url.addParameter("application", "xxx"); + assertURLStrDecoder(url); assertEquals("dubbo", url.getProtocol()); assertEquals("admin", url.getUsername()); assertEquals("hello1234", url.getPassword()); @@ -574,12 +643,14 @@ public class URLTest { URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan&k1=v1"); URL newUrl = url.addParameters(CollectionUtils.toStringMap("k1", "v1")); + assertURLStrDecoder(url); assertSame(url, newUrl); } { URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan&k1=v1&k2=v2"); URL newUrl = url.addParameters(CollectionUtils.toStringMap("k1", "v1", "k2", "v2")); + assertURLStrDecoder(url); assertSame(newUrl, url); } } @@ -589,6 +660,7 @@ public class URLTest { URL url = URL.valueOf("dubbo://admin:hello1234@10.20.130.230:20880/context/path?application=morgan"); url = url.addParameterIfAbsent("application", "xxx"); + assertURLStrDecoder(url); assertEquals("dubbo", url.getProtocol()); assertEquals("admin", url.getUsername()); assertEquals("hello1234", url.getPassword()); @@ -639,6 +711,7 @@ public class URLTest { @Test public void test_Anyhost() throws Exception { URL url = URL.valueOf("dubbo://0.0.0.0:20880"); + assertURLStrDecoder(url); assertEquals("0.0.0.0", url.getHost()); assertTrue(url.isAnyHost()); } @@ -646,16 +719,19 @@ public class URLTest { @Test public void test_Localhost() throws Exception { URL url = URL.valueOf("dubbo://127.0.0.1:20880"); + assertURLStrDecoder(url); assertEquals("127.0.0.1", url.getHost()); assertEquals("127.0.0.1:20880", url.getAddress()); assertTrue(url.isLocalHost()); url = URL.valueOf("dubbo://127.0.1.1:20880"); + assertURLStrDecoder(url); assertEquals("127.0.1.1", url.getHost()); assertEquals("127.0.1.1:20880", url.getAddress()); assertTrue(url.isLocalHost()); url = URL.valueOf("dubbo://localhost:20880"); + assertURLStrDecoder(url); assertEquals("localhost", url.getHost()); assertEquals("localhost:20880", url.getAddress()); assertTrue(url.isLocalHost()); @@ -664,21 +740,26 @@ public class URLTest { @Test public void test_Path() throws Exception { URL url = new URL("dubbo", "localhost", 20880, "////path"); + assertURLStrDecoder(url); assertEquals("path", url.getPath()); } @Test public void testAddParameters() throws Exception { URL url = URL.valueOf("dubbo://127.0.0.1:20880"); + assertURLStrDecoder(url); + Map parameters = new HashMap(); parameters.put("version", null); url.addParameters(parameters); + assertURLStrDecoder(url); } @Test - public void testUserNamePasswordContainsAt(){ + public void testUserNamePasswordContainsAt() { // Test username or password contains "@" URL url = URL.valueOf("ad@min:hello@1234@10.20.130.230:20880/context/path?version=1.0.0&application=morgan"); + assertURLStrDecoder(url); assertNull(url.getProtocol()); assertEquals("ad@min", url.getUsername()); assertEquals("hello@1234", url.getPassword()); @@ -693,9 +774,10 @@ public class URLTest { @Test - public void testIpV6Address(){ + public void testIpV6Address() { // Test username or password contains "@" URL url = URL.valueOf("ad@min111:haha@1234@2001:0db8:85a3:08d3:1319:8a2e:0370:7344:20880/context/path?version=1.0.0&application=morgan"); + assertURLStrDecoder(url); assertNull(url.getProtocol()); assertEquals("ad@min111", url.getUsername()); assertEquals("haha@1234", url.getPassword()); @@ -709,8 +791,9 @@ public class URLTest { } @Test - public void testIpV6AddressWithScopeId(){ + public void testIpV6AddressWithScopeId() { URL url = URL.valueOf("2001:0db8:85a3:08d3:1319:8a2e:0370:7344%5/context/path?version=1.0.0&application=morgan"); + assertURLStrDecoder(url); assertNull(url.getProtocol()); assertEquals("2001:0db8:85a3:08d3:1319:8a2e:0370:7344%5", url.getHost()); assertEquals("2001:0db8:85a3:08d3:1319:8a2e:0370:7344%5", url.getAddress()); @@ -728,49 +811,67 @@ public class URLTest { } @Test - public void testGetServiceKey () { + public void testGetServiceKey() { URL url1 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName"); + assertURLStrDecoder(url1); Assertions.assertEquals("org.apache.dubbo.test.interfaceName", url1.getServiceKey()); URL url2 = URL.valueOf("10.20.130.230:20880/org.apache.dubbo.test.interfaceName?interface=org.apache.dubbo.test.interfaceName"); + assertURLStrDecoder(url2); Assertions.assertEquals("org.apache.dubbo.test.interfaceName", url2.getServiceKey()); URL url3 = URL.valueOf("10.20.130.230:20880/org.apache.dubbo.test.interfaceName?interface=org.apache.dubbo.test.interfaceName&group=group1&version=1.0.0"); + assertURLStrDecoder(url3); Assertions.assertEquals("group1/org.apache.dubbo.test.interfaceName:1.0.0", url3.getServiceKey()); URL url4 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName"); + assertURLStrDecoder(url4); Assertions.assertEquals("context/path", url4.getPathKey()); URL url5 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName&group=group1&version=1.0.0"); + assertURLStrDecoder(url5); Assertions.assertEquals("group1/context/path:1.0.0", url5.getPathKey()); } @Test public void testGetColonSeparatedKey() { URL url1 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName&group=group&version=1.0.0"); + assertURLStrDecoder(url1); Assertions.assertEquals("org.apache.dubbo.test.interfaceName:1.0.0:group", url1.getColonSeparatedKey()); URL url2 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName&version=1.0.0"); + assertURLStrDecoder(url2); Assertions.assertEquals("org.apache.dubbo.test.interfaceName:1.0.0:", url2.getColonSeparatedKey()); URL url3 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName&group=group"); + assertURLStrDecoder(url3); Assertions.assertEquals("org.apache.dubbo.test.interfaceName::group", url3.getColonSeparatedKey()); URL url4 = URL.valueOf("10.20.130.230:20880/context/path?interface=org.apache.dubbo.test.interfaceName"); + assertURLStrDecoder(url4); Assertions.assertEquals("org.apache.dubbo.test.interfaceName::", url4.getColonSeparatedKey()); URL url5 = URL.valueOf("10.20.130.230:20880/org.apache.dubbo.test.interfaceName"); + assertURLStrDecoder(url5); Assertions.assertEquals("org.apache.dubbo.test.interfaceName::", url5.getColonSeparatedKey()); URL url6 = URL.valueOf("10.20.130.230:20880/org.apache.dubbo.test.interfaceName?interface=org.apache.dubbo.test.interfaceName1"); + assertURLStrDecoder(url6); Assertions.assertEquals("org.apache.dubbo.test.interfaceName1::", url6.getColonSeparatedKey()); } @Test public void testValueOf() { - URL.valueOf("10.20.130.230"); - URL.valueOf("10.20.130.230:20880"); - URL.valueOf("dubbo://10.20.130.230:20880"); - URL.valueOf("dubbo://10.20.130.230:20880/path"); + URL url = URL.valueOf("10.20.130.230"); + assertURLStrDecoder(url); + + url = URL.valueOf("10.20.130.230:20880"); + assertURLStrDecoder(url); + + url = URL.valueOf("dubbo://10.20.130.230:20880"); + assertURLStrDecoder(url); + + url = URL.valueOf("dubbo://10.20.130.230:20880/path"); + assertURLStrDecoder(url); } } diff --git a/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistry.java b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistry.java index 38d4cafaa1..51fa4aa8a6 100644 --- a/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistry.java +++ b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistry.java @@ -18,6 +18,7 @@ package org.apache.dubbo.registry.zookeeper; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.URLBuilder; +import org.apache.dubbo.common.URLStrParser; import org.apache.dubbo.common.logger.Logger; import org.apache.dubbo.common.logger.LoggerFactory; import org.apache.dubbo.common.utils.CollectionUtils; @@ -44,7 +45,7 @@ import static org.apache.dubbo.common.constants.CommonConstants.ANY_VALUE; import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY; import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY; import static org.apache.dubbo.common.constants.CommonConstants.PATH_SEPARATOR; -import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_SEPARATOR; +import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_SEPARATOR_ENCODED; import static org.apache.dubbo.common.constants.RegistryConstants.CATEGORY_KEY; import static org.apache.dubbo.common.constants.RegistryConstants.CONFIGURATORS_CATEGORY; import static org.apache.dubbo.common.constants.RegistryConstants.CONSUMERS_CATEGORY; @@ -266,9 +267,8 @@ public class ZookeeperRegistry extends FailbackRegistry { List urls = new ArrayList<>(); if (CollectionUtils.isNotEmpty(providers)) { for (String provider : providers) { - provider = URL.decode(provider); - if (provider.contains(PROTOCOL_SEPARATOR)) { - URL url = URL.valueOf(provider); + if (provider.contains(PROTOCOL_SEPARATOR_ENCODED)) { + URL url = URLStrParser.parseEncodedStr(provider); if (UrlUtils.isMatch(consumer, url)) { urls.add(url); } diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture.java index 244625805d..a18d1169cd 100644 --- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture.java +++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture.java @@ -252,7 +252,7 @@ public class DefaultFuture extends CompletableFuture { return (sent > 0 ? "Waiting server-side response timeout" : "Sending request timeout in client-side") + (scan ? " by scan timer" : "") + ". start time: " + (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date(start))) + ", end time: " - + (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date())) + "," + + (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date(nowTimestamp))) + "," + (sent > 0 ? " client elapsed: " + (sent - start) + " ms, server elapsed: " + (nowTimestamp - sent) : " elapsed: " + (nowTimestamp - start)) + " ms, timeout: "