diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/net/url/UrlBuilder.java b/hutool-core/src/main/java/org/dromara/hutool/core/net/url/UrlBuilder.java index 4ecc15b02..b902be20e 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/net/url/UrlBuilder.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/net/url/UrlBuilder.java @@ -39,7 +39,7 @@ import java.nio.charset.Charset; */ public final class UrlBuilder implements Builder { private static final long serialVersionUID = 1L; - private static final String DEFAULT_SCHEME = "http"; + private static final String DEFAULT_SCHEME = "http" ; /** * 协议,例如http @@ -70,11 +70,6 @@ public final class UrlBuilder implements Builder { * 编码,用于URLEncode和URLDecode */ private Charset charset; - /** - * 是否需要编码`%`
- * 区别对待,如果是,则生成URL时需要重新全部编码,否则跳过所有`%` - */ - private final boolean needEncodePercent; // region ----- of @@ -124,10 +119,15 @@ public final class UrlBuilder implements Builder { } /** - * 使用URL字符串构建UrlBuilder,当传入的URL没有协议时,按照http协议对待。 + * 使用URL字符串构建UrlBuilder,当传入的URL没有协议时,按照http协议对待。
+ *
    + *
  • 如果url用户传入的URL没有做编码,则charset设置为{@code null},此时URL不会解码,在build时也不会编码。
  • + *
  • 如果url已经编码,或部分编码,则需要设置charset,此时URL会解码编码后的参数,在build时也会编码。
  • + *
  • 如果url未编码,且存在歧义字符串,则需要设置charset为{@code null},并调用{@link #setCharset(Charset)}在build时编码URL。
  • + *
* * @param httpUrl URL字符串 - * @param charset 编码,用于URLEncode和URLDecode + * @param charset 编码,用于URLEncode和URLDecode,如果为{@code null},则不对传入的URL解码 * @return UrlBuilder */ public static UrlBuilder ofHttp(String httpUrl, final Charset charset) { @@ -154,7 +154,12 @@ public final class UrlBuilder implements Builder { } /** - * 使用URL字符串构建UrlBuilder + * 使用URL字符串构建UrlBuilder,规则如下:
+ *
    + *
  • 如果url用户传入的URL没有做编码,则charset设置为{@code null},此时URL不会解码,在build时也不会编码。
  • + *
  • 如果url已经编码,或部分编码,则需要设置charset,此时URL会解码编码后的参数,在build时也会编码。
  • + *
  • 如果url未编码,且存在歧义字符串,则需要设置charset为{@code null},并调用{@link #setCharset(Charset)}在build时编码URL。
  • + *
* * @param url URL字符串 * @param charset 编码,用于URLEncode和URLDecode @@ -169,7 +174,7 @@ public final class UrlBuilder implements Builder { * 使用URL构建UrlBuilder * * @param url URL - * @param charset 编码,用于URLEncode和URLDecode + * @param charset 编码,用于URLEncode和URLDecode,{@code null}表示不解码 * @return UrlBuilder */ public static UrlBuilder of(final URL url, final Charset charset) { @@ -225,8 +230,6 @@ public final class UrlBuilder implements Builder { */ public UrlBuilder() { this.charset = CharsetUtil.UTF_8; - // 编码非空情况下做解码 - this.needEncodePercent = true; } /** @@ -248,8 +251,6 @@ public final class UrlBuilder implements Builder { this.path = path; this.query = query; this.setFragment(fragment); - // 编码非空情况下做解码 - this.needEncodePercent = null != charset; } /** @@ -360,7 +361,7 @@ public final class UrlBuilder implements Builder { * @return 路径,例如/aa/bb/cc */ public String getPathStr() { - return null == this.path ? StrUtil.SLASH : this.path.build(charset, this.needEncodePercent); + return null == this.path ? StrUtil.SLASH : this.path.build(charset); } /** @@ -435,7 +436,7 @@ public final class UrlBuilder implements Builder { * @return 查询语句,例如a=1&b=2 */ public String getQueryStr() { - return null == this.query ? null : this.query.build(this.charset, this.needEncodePercent); + return null == this.query ? null : this.query.build(this.charset); } /** @@ -483,8 +484,7 @@ public final class UrlBuilder implements Builder { * @return 标识符,例如#后边的部分 */ public String getFragmentEncoded() { - final char[] safeChars = this.needEncodePercent ? null : new char[]{'%'}; - return RFC3986.FRAGMENT.encode(this.fragment, this.charset, safeChars); + return RFC3986.FRAGMENT.encode(this.fragment, this.charset); } /** diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/net/url/UrlDecoder.java b/hutool-core/src/main/java/org/dromara/hutool/core/net/url/UrlDecoder.java index e03b8e746..726e461b0 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/net/url/UrlDecoder.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/net/url/UrlDecoder.java @@ -47,7 +47,7 @@ public class UrlDecoder implements Serializable { * * * @param str 包含URL编码后的字符串 - * @param charset 编码 + * @param charset 解码的编码,{@code null}表示不做解码 * @return 解码后的字符串 */ public static String decodeForPath(final String str, final Charset charset) { @@ -113,7 +113,7 @@ public class UrlDecoder implements Serializable { * * @param str 包含URL编码后的字符串 * @param isPlusToSpace 是否+转换为空格 - * @param charset 编码,{@code null}表示不做编码 + * @param charset 编码,{@code null}表示不做解码 * @return 解码后的字符串 */ public static String decode(final String str, final Charset charset, final boolean isPlusToSpace) { diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/net/url/UrlPath.java b/hutool-core/src/main/java/org/dromara/hutool/core/net/url/UrlPath.java index cab9ac0e6..cd8e3caa3 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/net/url/UrlPath.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/net/url/UrlPath.java @@ -137,19 +137,6 @@ public class UrlPath { return this; } - /** - * 构建path,前面带'/'
- *
-	 *     path = path-abempty / path-absolute / path-noscheme / path-rootless / path-empty
-	 * 
- * - * @param charset encode编码,null表示不做encode - * @return 如果没有任何内容,则返回空字符串"" - */ - public String build(final Charset charset) { - return build(charset, true); - } - /** * 构建path,前面带'/'
*
@@ -157,22 +144,20 @@ public class UrlPath {
 	 * 
* * @param charset encode编码,null表示不做encode - * @param encodePercent 是否编码`%` * @return 如果没有任何内容,则返回空字符串"" * @since 5.8.0 */ - public String build(final Charset charset, final boolean encodePercent) { + public String build(final Charset charset) { if (CollUtil.isEmpty(this.segments)) { // 没有节点的path取决于是否末尾追加/,如果不追加返回空串,否则返回/ return withEngTag ? StrUtil.SLASH : StrUtil.EMPTY; } - final char[] safeChars = encodePercent ? null : new char[]{'%'}; final StringBuilder builder = new StringBuilder(); for (final String segment : segments) { // https://www.ietf.org/rfc/rfc3986.html#section-3.3 // 此处Path中是允许有`:`的,之前理解有误,应该是相对URI的第一个segment中不允许有`:` - builder.append(CharUtil.SLASH).append(RFC3986.SEGMENT.encode(segment, charset, safeChars)); + builder.append(CharUtil.SLASH).append(RFC3986.SEGMENT.encode(segment, charset)); } if (withEngTag) { diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/net/url/UrlQuery.java b/hutool-core/src/main/java/org/dromara/hutool/core/net/url/UrlQuery.java index b5dd7d7f1..b7cfb466f 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/net/url/UrlQuery.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/net/url/UrlQuery.java @@ -247,54 +247,20 @@ public class UrlQuery { *
  • 如果value为{@code null},只保留key,如key1对应value为{@code null}生成类似于{@code key1&key2=v2}形式
  • * * - * @param charset encode编码,null表示不做encode编码 + * @param charset encode编码,null表示不做encode编码 * @return URL查询字符串 */ public String build(final Charset charset) { - return build(charset, null != charset); - } - - /** - * 构建URL查询字符串,即将key-value键值对转换为{@code key1=v1&key2=v2&key3=v3}形式。
    - * 对于{@code null}处理规则如下: - *
      - *
    • 如果key为{@code null},则这个键值对忽略
    • - *
    • 如果value为{@code null},只保留key,如key1对应value为{@code null}生成类似于{@code key1&key2=v2}形式
    • - *
    - * - * @param charset encode编码,null表示不做encode编码 - * @param encodePercent 是否编码`%` - * @return URL查询字符串 - */ - public String build(final Charset charset, final boolean encodePercent) { switch (this.encodeMode) { case FORM_URL_ENCODED: - return build(FormUrlencoded.ALL, FormUrlencoded.ALL, charset, encodePercent); + return build(FormUrlencoded.ALL, FormUrlencoded.ALL, charset); case STRICT: - return build(RFC3986.QUERY_PARAM_NAME_STRICT, RFC3986.QUERY_PARAM_VALUE_STRICT, charset, encodePercent); + return build(RFC3986.QUERY_PARAM_NAME_STRICT, RFC3986.QUERY_PARAM_VALUE_STRICT, charset); default: - return build(RFC3986.QUERY_PARAM_NAME, RFC3986.QUERY_PARAM_VALUE, charset, encodePercent); + return build(RFC3986.QUERY_PARAM_NAME, RFC3986.QUERY_PARAM_VALUE, charset); } } - /** - * 构建URL查询字符串,即将key-value键值对转换为{@code key1=v1&key2=v2&key3=v3}形式。
    - * 对于{@code null}处理规则如下: - *
      - *
    • 如果key为{@code null},则这个键值对忽略
    • - *
    • 如果value为{@code null},只保留key,如key1对应value为{@code null}生成类似于{@code key1&key2=v2}形式
    • - *
    - * - * @param keyCoder 键值对中键的编码器 - * @param valueCoder 键值对中值的编码器 - * @param charset encode编码,null表示不做encode编码 - * @return URL查询字符串 - * @since 5.7.16 - */ - public String build(final PercentCodec keyCoder, final PercentCodec valueCoder, final Charset charset) { - return build(keyCoder, valueCoder, charset, true); - } - /** * 构建URL查询字符串,即将key-value键值对转换为{@code key1=v1&key2=v2&key3=v3}形式。
    * 对于{@code null}处理规则如下: @@ -306,17 +272,15 @@ public class UrlQuery { * @param keyCoder 键值对中键的编码器 * @param valueCoder 键值对中值的编码器 * @param charset encode编码,null表示不做encode编码 - * @param encodePercent 是否编码`%` * @return URL查询字符串 * @since 5.8.0 */ public String build(final PercentCodec keyCoder, final PercentCodec valueCoder, - final Charset charset, final boolean encodePercent) { + final Charset charset) { if (MapUtil.isEmpty(this.query)) { return StrUtil.EMPTY; } - final char[] safeChars = encodePercent ? null : new char[]{'%'}; final StringBuilder sb = new StringBuilder(); CharSequence name; CharSequence value; @@ -326,10 +290,10 @@ public class UrlQuery { if (sb.length() > 0) { sb.append("&"); } - sb.append(keyCoder.encode(name, charset, safeChars)); + sb.append(keyCoder.encode(name, charset)); value = entry.getValue(); if (null != value) { - sb.append("=").append(valueCoder.encode(value, charset, safeChars)); + sb.append("=").append(valueCoder.encode(value, charset)); } } } diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/net/UrlBuilderTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/net/UrlBuilderTest.java index 31d181899..d62759f6f 100644 --- a/hutool-core/src/test/java/org/dromara/hutool/core/net/UrlBuilderTest.java +++ b/hutool-core/src/test/java/org/dromara/hutool/core/net/UrlBuilderTest.java @@ -449,7 +449,7 @@ public class UrlBuilderTest { // https://github.com/dromara/hutool/issues/2243 // 如果用户已经做了%编码,不应该重复编码 final String url = "https://hutool.cn/v1.0?privateNum=%2B8616512884988"; - final String s = UrlBuilder.of(url, null).setCharset(CharsetUtil.UTF_8).toString(); + final String s = UrlBuilder.of(url, null).toString(); assertEquals(url, s); } @@ -528,5 +528,26 @@ public class UrlBuilderTest { // 不指定编码,则保持原字符串 urlBuilder = UrlBuilder.ofHttp("http://localhost:9999/getReportDataList?goodsName=工业硫酸98%&conReportTypeId=1", null); assertEquals("http://localhost:9999/getReportDataList?goodsName=工业硫酸98%&conReportTypeId=1", urlBuilder.build()); + + // URL已经编码,则先解码,再重新编码 + // 此例子中98%的%未做编码,解析后编码为25% + urlBuilder = UrlBuilder.ofHttp("http://localhost:9999/getReportDataList?goodsName=%E5%B7%A5%E4%B8%9A%E7%A1%AB%E9%85%B898%&conReportTypeId=1", CharsetUtil.UTF_8); + assertEquals("http://localhost:9999/getReportDataList?goodsName=%E5%B7%A5%E4%B8%9A%E7%A1%AB%E9%85%B898%25&conReportTypeId=1", urlBuilder.build()); + } + + @Test + void percentTest() { + // 此处URL有歧义 + // 如果用户需要传的a的值为`%`,则这个URL表示已经编码过了,此时需要解码后再重新编码,保持不变 + UrlBuilder urlBuilder = UrlBuilder.ofHttp("http://localhost:9999/a?a=%25"); + assertEquals("http://localhost:9999/a?a=%25", urlBuilder.build()); + // 不传charset,则保留原样,不做任何处理 + urlBuilder = UrlBuilder.ofHttp("http://localhost:9999/a?a=%25", null); + assertEquals("http://localhost:9999/a?a=%25", urlBuilder.build()); + + // 如果用户需要传的a的值为`%25`,则这个URL表示未编码,不需要解码,需要对`%`再次编码 + urlBuilder = UrlBuilder.ofHttp("http://localhost:9999/a?a=%25", null); + urlBuilder.setCharset(CharsetUtil.UTF_8); + assertEquals("http://localhost:9999/a?a=%2525", urlBuilder.build()); } } diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/HttpGlobalConfig.java b/hutool-http/src/main/java/org/dromara/hutool/http/HttpGlobalConfig.java index 4da3d632c..d9dfbf808 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/HttpGlobalConfig.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/HttpGlobalConfig.java @@ -40,7 +40,6 @@ public class HttpGlobalConfig implements Serializable { private static String boundary = "--------------------Hutool_" + RandomUtil.randomStringLower(16); private static int maxRedirectCount = 0; private static boolean ignoreEOFError = true; - private static boolean decodeUrl = false; /** * 是否从响应正文中的meta标签获取编码信息 */ @@ -137,30 +136,6 @@ public class HttpGlobalConfig implements Serializable { ignoreEOFError = customIgnoreEOFError; } - /** - * 获取是否忽略解码URL,包括URL中的Path部分和Param部分。
    - * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数,
    - * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 - * - * @return 是否忽略解码URL - * @since 5.7.22 - */ - public static boolean isDecodeUrl() { - return decodeUrl; - } - - /** - * 设置是否忽略解码URL,包括URL中的Path部分和Param部分。
    - * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数,
    - * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 - * - * @param customDecodeUrl 是否忽略解码URL - * @since 5.7.22 - */ - synchronized public static void setDecodeUrl(final boolean customDecodeUrl) { - decodeUrl = customDecodeUrl; - } - /** * 获取Cookie管理器,用于自定义Cookie管理 * diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/client/Request.java b/hutool-http/src/main/java/org/dromara/hutool/http/client/Request.java index 4e67d83c0..6ecfc95d9 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/client/Request.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/client/Request.java @@ -21,7 +21,6 @@ import org.dromara.hutool.core.net.url.UrlBuilder; import org.dromara.hutool.core.net.url.UrlQuery; import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.core.util.CharsetUtil; -import org.dromara.hutool.core.util.ObjUtil; import org.dromara.hutool.http.GlobalHeaders; import org.dromara.hutool.http.HttpGlobalConfig; import org.dromara.hutool.http.client.body.*; @@ -49,34 +48,50 @@ import java.util.concurrent.atomic.AtomicBoolean; public class Request implements HeaderOperation { /** - * 构建一个HTTP请求
    - * 对于传入的URL,可以自定义是否解码已经编码的内容,设置见{@link HttpGlobalConfig#setDecodeUrl(boolean)}
    - * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果{@link HttpGlobalConfig#isDecodeUrl()}为{@code true},则会统一解码编码后的参数,
    - * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * 构建一个HTTP请求,不解码和编码,此时要求用户传入的URL必须是已经编码过的。 * - * @param url URL链接,默认自动编码URL中的参数等信息 + * @param url URL链接 + * @return HttpRequest + */ + public static Request ofWithoutEncode(final String url) { + return of(url, null); + } + + /** + * 构建一个HTTP请求,默认编解码规则,规则为: + *
      + *
    • 如果传入的URL已编码,则先解码,再按照RFC3986规范重新编码。
    • + *
    • 如果传入的URL未编码,则先解码(解码后不变),再按照RFC3986规范重新编码。
    • + *
    • 如果传入的URL部分编码,则先解码编码过的部分,再按照RFC3986规范重新编码。
    • + *
    + * 如果服务端要求的URL编码规则不符合RFC3986,则需要先自行编码,再调用{@link #of(String, Charset)}指定decodeAndEncodeCharset为{@code null} + * + * @param url URL链接,默认自动解码并重新编码URL中的参数等信息 * @return HttpRequest */ public static Request of(final String url) { - return of(url, HttpGlobalConfig.isDecodeUrl() ? DEFAULT_CHARSET : null); + return of(url, DEFAULT_CHARSET); } /** * 构建一个HTTP请求
    - * 对于传入的URL,可以自定义是否解码已经编码的内容。
    - * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果charset参数不为{@code null},则会统一解码编码后的参数,
    - * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * 对于传入的URL,可以自定义是否解码已经编码的内容,规则如下:
    + *
      + *
    • 如果url已编码,则decodeAndEncodeCharset设置为{@code null},此时URL不会解码,发送也不编码。
    • + *
    • 如果url未编码或部分编码,则需要设置decodeAndEncodeCharset,此时URL会解码编码后的参数,发送时按照RFC3986规范重新编码。
    • + *
    • 如果url未编码,且存在歧义字符串,则需要设置decodeAndEncodeCharset为{@code null},并调用{@link Request#setEncodeUrl(boolean)}为true编码URL。
    • + *
    * - * @param url URL链接 - * @param charset 编码,如果为{@code null}不自动解码编码URL + * @param url URL链接 + * @param decodeAndEncodeCharset 编码,如果为{@code null}不自动解码编码URL * @return HttpRequest */ - public static Request of(final String url, final Charset charset) { - return of(UrlBuilder.ofHttp(url, charset)); + public static Request of(final String url, final Charset decodeAndEncodeCharset) { + return of(UrlBuilder.ofHttp(url, decodeAndEncodeCharset)); } /** - * 构建一个HTTP请求
    + * 构建一个HTTP请求 * * @param url {@link UrlBuilder} * @return HttpRequest @@ -98,6 +113,10 @@ public class Request implements HeaderOperation { * 请求的URL */ private UrlBuilder url; + /** + * 请求编码 + */ + private Charset charset = DEFAULT_CHARSET; /** * 存储头信息 */ @@ -178,25 +197,43 @@ public class Request implements HeaderOperation { } /** - * 设置编码 + * 设置自定义编码,一般用于: + *
      + *
    • 编码请求体
    • + *
    • 服务端未返回编码时,使用此编码解码响应体
    • + *
    * * @param charset 编码 * @return this */ public Request charset(final Charset charset) { - Assert.notNull(this.url, "You must be set request url first."); - this.url.setCharset(charset); + this.charset = charset; return this; } /** - * 获取请求编码,如果用户未设置,返回{@link #DEFAULT_CHARSET} + * 获取请求编码,默认{@link #DEFAULT_CHARSET},一般用于: + *
      + *
    • 编码请求体
    • + *
    • 服务端未返回编码时,使用此编码解码响应体
    • + *
    * * @return 编码 */ public Charset charset() { - Assert.notNull(this.url, "You must be set request url first."); - return ObjUtil.defaultIfNull(this.url.getCharset(), DEFAULT_CHARSET); + return this.charset; + } + + /** + * 设置是否编码URL + * + * @param isEncodeUrl 如果为{@code true},则使用请求编码编码URL,{@code false}则不编码URL + * @return this + */ + public Request setEncodeUrl(final boolean isEncodeUrl) { + Assert.notNull(this.url, "Request URL must be not null!"); + this.url.setCharset(isEncodeUrl ? this.charset : null); + return this; } @Override @@ -261,17 +298,17 @@ public class Request implements HeaderOperation { */ public Request form(final Map formMap) { final AtomicBoolean isMultiPart = new AtomicBoolean(false); - formMap.forEach((key, value)->{ - if(value instanceof File || + formMap.forEach((key, value) -> { + if (value instanceof File || value instanceof Path || value instanceof Resource || value instanceof InputStream || - value instanceof Reader){ + value instanceof Reader) { isMultiPart.set(true); } }); - if(isMultiPart.get()){ + if (isMultiPart.get()) { return body(MultipartBody.of(formMap, charset())); } return body(new UrlEncodedFormBody(formMap, charset())); diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/jdk/JdkClientEngine.java b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/jdk/JdkClientEngine.java index 190388638..e0d78798a 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/jdk/JdkClientEngine.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/jdk/JdkClientEngine.java @@ -211,6 +211,7 @@ public class JdkClientEngine implements ClientEngine { redirectUrl = UrlBuilder.of(parentUrl.getScheme(), parentUrl.getHost(), parentUrl.getPort(), location, query, null, parentUrl.getCharset()); } else { + // location已经是编码过的URL redirectUrl = UrlBuilder.ofHttpWithoutEncode(location); } diff --git a/hutool-http/src/test/java/org/dromara/hutool/http/HttpUtilTest.java b/hutool-http/src/test/java/org/dromara/hutool/http/HttpUtilTest.java index 9dcee1071..4f517762d 100644 --- a/hutool-http/src/test/java/org/dromara/hutool/http/HttpUtilTest.java +++ b/hutool-http/src/test/java/org/dromara/hutool/http/HttpUtilTest.java @@ -185,10 +185,9 @@ public class HttpUtilTest { @Test @Disabled public void getPicTest(){ - HttpGlobalConfig.setDecodeUrl(false); final String url = "https://p3-sign.douyinpic.com/tos-cn-i-0813/f41afb2e79a94dcf80970affb9a69415~noop.webp?x-expires=1647738000&x-signature=%2Br1ekUCGjXiu50Y%2Bk0MO4ovulK8%3D&from=4257465056&s=PackSourceEnum_DOUYIN_REFLOW&se=false&sh=&sc=&l=2022021809224601020810013524310DD3&biz_tag=aweme_images"; - final String body = HttpUtil.send(Request.of(url)).bodyStr(); + final String body = HttpUtil.send(Request.of(url, null)).bodyStr(); Console.log(body); } diff --git a/hutool-http/src/test/java/org/dromara/hutool/http/HttpRequestTest.java b/hutool-http/src/test/java/org/dromara/hutool/http/RequestTest.java similarity index 88% rename from hutool-http/src/test/java/org/dromara/hutool/http/HttpRequestTest.java rename to hutool-http/src/test/java/org/dromara/hutool/http/RequestTest.java index 9cba00dba..08f5ae983 100644 --- a/hutool-http/src/test/java/org/dromara/hutool/http/HttpRequestTest.java +++ b/hutool-http/src/test/java/org/dromara/hutool/http/RequestTest.java @@ -31,13 +31,15 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import static org.junit.jupiter.api.Assertions.assertEquals; + /** * {@link Request}单元测试 * * @author Looly */ @SuppressWarnings("resource") -public class HttpRequestTest { +public class RequestTest { final String url = "http://photo.qzone.qq.com/fcgi-bin/fcg_list_album?uin=88888&outstyle=2"; @Test @@ -238,4 +240,21 @@ public class HttpRequestTest { final Response httpResponse = Request.of("http://82.157.17.173:8100/app/getAddress").send(); Console.log(httpResponse.body()); } + + @Test + void percentTest() { + // 此处URL有歧义 + // 如果用户需要传的a的值为`%`,则这个URL表示已经编码过了,此时需要解码后再重新编码,保持不变 + Request request = Request.of("http://localhost:9999/a?a=%25", CharsetUtil.UTF_8); + assertEquals("http://localhost:9999/a?a=%25", request.handledUrl().toURL().toString()); + + // 不传charset,则保留原样,不做任何处理 + request = Request.of("http://localhost:9999/a?a=%25", null); + assertEquals("http://localhost:9999/a?a=%25", request.handledUrl().toURL().toString()); + + // 如果用户需要传的a的值为`%25`,则这个URL表示未编码,不需要解码,需要对`%`再次编码 + request = Request.of("http://localhost:9999/a?a=%25", null); + request.setEncodeUrl(true); + assertEquals("http://localhost:9999/a?a=%2525", request.handledUrl().toURL().toString()); + } }