This commit is contained in:
Looly 2024-07-04 21:26:01 +08:00
parent 1a1884a04c
commit 34b322e3fc
10 changed files with 135 additions and 134 deletions

View File

@ -39,7 +39,7 @@ import java.nio.charset.Charset;
*/ */
public final class UrlBuilder implements Builder<String> { public final class UrlBuilder implements Builder<String> {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final String DEFAULT_SCHEME = "http"; private static final String DEFAULT_SCHEME = "http" ;
/** /**
* 协议例如http * 协议例如http
@ -70,11 +70,6 @@ public final class UrlBuilder implements Builder<String> {
* 编码用于URLEncode和URLDecode * 编码用于URLEncode和URLDecode
*/ */
private Charset charset; private Charset charset;
/**
* 是否需要编码`%`<br>
* 区别对待如果是则生成URL时需要重新全部编码否则跳过所有`%`
*/
private final boolean needEncodePercent;
// region ----- of // region ----- of
@ -124,10 +119,15 @@ public final class UrlBuilder implements Builder<String> {
} }
/** /**
* 使用URL字符串构建UrlBuilder当传入的URL没有协议时按照http协议对待 * 使用URL字符串构建UrlBuilder当传入的URL没有协议时按照http协议对待<br>
* <ul>
* <li>如果url用户传入的URL没有做编码则charset设置为{@code null}此时URL不会解码在build时也不会编码</li>
* <li>如果url已经编码或部分编码则需要设置charset此时URL会解码编码后的参数在build时也会编码</li>
* <li>如果url未编码且存在歧义字符串则需要设置charset为{@code null}并调用{@link #setCharset(Charset)}在build时编码URL</li>
* </ul>
* *
* @param httpUrl URL字符串 * @param httpUrl URL字符串
* @param charset 编码用于URLEncode和URLDecode * @param charset 编码用于URLEncode和URLDecode如果为{@code null}则不对传入的URL解码
* @return UrlBuilder * @return UrlBuilder
*/ */
public static UrlBuilder ofHttp(String httpUrl, final Charset charset) { public static UrlBuilder ofHttp(String httpUrl, final Charset charset) {
@ -154,7 +154,12 @@ public final class UrlBuilder implements Builder<String> {
} }
/** /**
* 使用URL字符串构建UrlBuilder * 使用URL字符串构建UrlBuilder规则如下<br>
* <ul>
* <li>如果url用户传入的URL没有做编码则charset设置为{@code null}此时URL不会解码在build时也不会编码</li>
* <li>如果url已经编码或部分编码则需要设置charset此时URL会解码编码后的参数在build时也会编码</li>
* <li>如果url未编码且存在歧义字符串则需要设置charset为{@code null}并调用{@link #setCharset(Charset)}在build时编码URL</li>
* </ul>
* *
* @param url URL字符串 * @param url URL字符串
* @param charset 编码用于URLEncode和URLDecode * @param charset 编码用于URLEncode和URLDecode
@ -169,7 +174,7 @@ public final class UrlBuilder implements Builder<String> {
* 使用URL构建UrlBuilder * 使用URL构建UrlBuilder
* *
* @param url URL * @param url URL
* @param charset 编码用于URLEncode和URLDecode * @param charset 编码用于URLEncode和URLDecode{@code null}表示不解码
* @return UrlBuilder * @return UrlBuilder
*/ */
public static UrlBuilder of(final URL url, final Charset charset) { public static UrlBuilder of(final URL url, final Charset charset) {
@ -225,8 +230,6 @@ public final class UrlBuilder implements Builder<String> {
*/ */
public UrlBuilder() { public UrlBuilder() {
this.charset = CharsetUtil.UTF_8; this.charset = CharsetUtil.UTF_8;
// 编码非空情况下做解码
this.needEncodePercent = true;
} }
/** /**
@ -248,8 +251,6 @@ public final class UrlBuilder implements Builder<String> {
this.path = path; this.path = path;
this.query = query; this.query = query;
this.setFragment(fragment); this.setFragment(fragment);
// 编码非空情况下做解码
this.needEncodePercent = null != charset;
} }
/** /**
@ -360,7 +361,7 @@ public final class UrlBuilder implements Builder<String> {
* @return 路径例如/aa/bb/cc * @return 路径例如/aa/bb/cc
*/ */
public String getPathStr() { 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<String> {
* @return 查询语句例如a=1&amp;b=2 * @return 查询语句例如a=1&amp;b=2
*/ */
public String getQueryStr() { 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<String> {
* @return 标识符例如#后边的部分 * @return 标识符例如#后边的部分
*/ */
public String getFragmentEncoded() { public String getFragmentEncoded() {
final char[] safeChars = this.needEncodePercent ? null : new char[]{'%'}; return RFC3986.FRAGMENT.encode(this.fragment, this.charset);
return RFC3986.FRAGMENT.encode(this.fragment, this.charset, safeChars);
} }
/** /**

View File

@ -47,7 +47,7 @@ public class UrlDecoder implements Serializable {
* </ol> * </ol>
* *
* @param str 包含URL编码后的字符串 * @param str 包含URL编码后的字符串
* @param charset 编码 * @param charset 解码的编码{@code null}表示不做解码
* @return 解码后的字符串 * @return 解码后的字符串
*/ */
public static String decodeForPath(final String str, final Charset charset) { public static String decodeForPath(final String str, final Charset charset) {
@ -113,7 +113,7 @@ public class UrlDecoder implements Serializable {
* *
* @param str 包含URL编码后的字符串 * @param str 包含URL编码后的字符串
* @param isPlusToSpace 是否+转换为空格 * @param isPlusToSpace 是否+转换为空格
* @param charset 编码{@code null}表示不做 * @param charset 编码{@code null}表示不做
* @return 解码后的字符串 * @return 解码后的字符串
*/ */
public static String decode(final String str, final Charset charset, final boolean isPlusToSpace) { public static String decode(final String str, final Charset charset, final boolean isPlusToSpace) {

View File

@ -137,19 +137,6 @@ public class UrlPath {
return this; return this;
} }
/**
* 构建path前面带'/'<br>
* <pre>
* path = path-abempty / path-absolute / path-noscheme / path-rootless / path-empty
* </pre>
*
* @param charset encode编码null表示不做encode
* @return 如果没有任何内容则返回空字符串""
*/
public String build(final Charset charset) {
return build(charset, true);
}
/** /**
* 构建path前面带'/'<br> * 构建path前面带'/'<br>
* <pre> * <pre>
@ -157,22 +144,20 @@ public class UrlPath {
* </pre> * </pre>
* *
* @param charset encode编码null表示不做encode * @param charset encode编码null表示不做encode
* @param encodePercent 是否编码`%`
* @return 如果没有任何内容则返回空字符串"" * @return 如果没有任何内容则返回空字符串""
* @since 5.8.0 * @since 5.8.0
*/ */
public String build(final Charset charset, final boolean encodePercent) { public String build(final Charset charset) {
if (CollUtil.isEmpty(this.segments)) { if (CollUtil.isEmpty(this.segments)) {
// 没有节点的path取决于是否末尾追加/如果不追加返回空串否则返回/ // 没有节点的path取决于是否末尾追加/如果不追加返回空串否则返回/
return withEngTag ? StrUtil.SLASH : StrUtil.EMPTY; return withEngTag ? StrUtil.SLASH : StrUtil.EMPTY;
} }
final char[] safeChars = encodePercent ? null : new char[]{'%'};
final StringBuilder builder = new StringBuilder(); final StringBuilder builder = new StringBuilder();
for (final String segment : segments) { for (final String segment : segments) {
// https://www.ietf.org/rfc/rfc3986.html#section-3.3 // https://www.ietf.org/rfc/rfc3986.html#section-3.3
// 此处Path中是允许有`:`之前理解有误应该是相对URI的第一个segment中不允许有`:` // 此处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) { if (withEngTag) {

View File

@ -247,54 +247,20 @@ public class UrlQuery {
* <li>如果value为{@code null}只保留key如key1对应value为{@code null}生成类似于{@code key1&key2=v2}形式</li> * <li>如果value为{@code null}只保留key如key1对应value为{@code null}生成类似于{@code key1&key2=v2}形式</li>
* </ul> * </ul>
* *
* @param charset encode编码null表示不做encode编码 * @param charset encode编码null表示不做encode编码
* @return URL查询字符串 * @return URL查询字符串
*/ */
public String build(final Charset charset) { public String build(final Charset charset) {
return build(charset, null != charset);
}
/**
* 构建URL查询字符串即将key-value键值对转换为{@code key1=v1&key2=v2&key3=v3}形式<br>
* 对于{@code null}处理规则如下
* <ul>
* <li>如果key为{@code null}则这个键值对忽略</li>
* <li>如果value为{@code null}只保留key如key1对应value为{@code null}生成类似于{@code key1&key2=v2}形式</li>
* </ul>
*
* @param charset encode编码null表示不做encode编码
* @param encodePercent 是否编码`%`
* @return URL查询字符串
*/
public String build(final Charset charset, final boolean encodePercent) {
switch (this.encodeMode) { switch (this.encodeMode) {
case FORM_URL_ENCODED: case FORM_URL_ENCODED:
return build(FormUrlencoded.ALL, FormUrlencoded.ALL, charset, encodePercent); return build(FormUrlencoded.ALL, FormUrlencoded.ALL, charset);
case STRICT: 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: 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}形式<br>
* 对于{@code null}处理规则如下
* <ul>
* <li>如果key为{@code null}则这个键值对忽略</li>
* <li>如果value为{@code null}只保留key如key1对应value为{@code null}生成类似于{@code key1&key2=v2}形式</li>
* </ul>
*
* @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}形式<br> * 构建URL查询字符串即将key-value键值对转换为{@code key1=v1&key2=v2&key3=v3}形式<br>
* 对于{@code null}处理规则如下 * 对于{@code null}处理规则如下
@ -306,17 +272,15 @@ public class UrlQuery {
* @param keyCoder 键值对中键的编码器 * @param keyCoder 键值对中键的编码器
* @param valueCoder 键值对中值的编码器 * @param valueCoder 键值对中值的编码器
* @param charset encode编码null表示不做encode编码 * @param charset encode编码null表示不做encode编码
* @param encodePercent 是否编码`%`
* @return URL查询字符串 * @return URL查询字符串
* @since 5.8.0 * @since 5.8.0
*/ */
public String build(final PercentCodec keyCoder, final PercentCodec valueCoder, public String build(final PercentCodec keyCoder, final PercentCodec valueCoder,
final Charset charset, final boolean encodePercent) { final Charset charset) {
if (MapUtil.isEmpty(this.query)) { if (MapUtil.isEmpty(this.query)) {
return StrUtil.EMPTY; return StrUtil.EMPTY;
} }
final char[] safeChars = encodePercent ? null : new char[]{'%'};
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
CharSequence name; CharSequence name;
CharSequence value; CharSequence value;
@ -326,10 +290,10 @@ public class UrlQuery {
if (sb.length() > 0) { if (sb.length() > 0) {
sb.append("&"); sb.append("&");
} }
sb.append(keyCoder.encode(name, charset, safeChars)); sb.append(keyCoder.encode(name, charset));
value = entry.getValue(); value = entry.getValue();
if (null != value) { if (null != value) {
sb.append("=").append(valueCoder.encode(value, charset, safeChars)); sb.append("=").append(valueCoder.encode(value, charset));
} }
} }
} }

View File

@ -449,7 +449,7 @@ public class UrlBuilderTest {
// https://github.com/dromara/hutool/issues/2243 // https://github.com/dromara/hutool/issues/2243
// 如果用户已经做了%编码不应该重复编码 // 如果用户已经做了%编码不应该重复编码
final String url = "https://hutool.cn/v1.0?privateNum=%2B8616512884988"; 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); assertEquals(url, s);
} }
@ -528,5 +528,26 @@ public class UrlBuilderTest {
// 不指定编码则保持原字符串 // 不指定编码则保持原字符串
urlBuilder = UrlBuilder.ofHttp("http://localhost:9999/getReportDataList?goodsName=工业硫酸98%&conReportTypeId=1", null); urlBuilder = UrlBuilder.ofHttp("http://localhost:9999/getReportDataList?goodsName=工业硫酸98%&conReportTypeId=1", null);
assertEquals("http://localhost:9999/getReportDataList?goodsName=工业硫酸98%&conReportTypeId=1", urlBuilder.build()); 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());
} }
} }

View File

@ -40,7 +40,6 @@ public class HttpGlobalConfig implements Serializable {
private static String boundary = "--------------------Hutool_" + RandomUtil.randomStringLower(16); private static String boundary = "--------------------Hutool_" + RandomUtil.randomStringLower(16);
private static int maxRedirectCount = 0; private static int maxRedirectCount = 0;
private static boolean ignoreEOFError = true; private static boolean ignoreEOFError = true;
private static boolean decodeUrl = false;
/** /**
* 是否从响应正文中的meta标签获取编码信息 * 是否从响应正文中的meta标签获取编码信息
*/ */
@ -137,30 +136,6 @@ public class HttpGlobalConfig implements Serializable {
ignoreEOFError = customIgnoreEOFError; ignoreEOFError = customIgnoreEOFError;
} }
/**
* 获取是否忽略解码URL包括URL中的Path部分和Param部分<br>
* 在构建Http请求时用户传入的URL可能有编码后和未编码的内容混合在一起如果此参数为{@code true}则会统一解码编码后的参数<br>
* 按照RFC3986规范在发送请求时全部编码之如果为{@code false}则不会解码已经编码的内容在请求时只编码需要编码的部分
*
* @return 是否忽略解码URL
* @since 5.7.22
*/
public static boolean isDecodeUrl() {
return decodeUrl;
}
/**
* 设置是否忽略解码URL包括URL中的Path部分和Param部分<br>
* 在构建Http请求时用户传入的URL可能有编码后和未编码的内容混合在一起如果此参数为{@code true}则会统一解码编码后的参数<br>
* 按照RFC3986规范在发送请求时全部编码之如果为{@code false}则不会解码已经编码的内容在请求时只编码需要编码的部分
*
* @param customDecodeUrl 是否忽略解码URL
* @since 5.7.22
*/
synchronized public static void setDecodeUrl(final boolean customDecodeUrl) {
decodeUrl = customDecodeUrl;
}
/** /**
* 获取Cookie管理器用于自定义Cookie管理 * 获取Cookie管理器用于自定义Cookie管理
* *

View File

@ -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.net.url.UrlQuery;
import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.util.CharsetUtil; 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.GlobalHeaders;
import org.dromara.hutool.http.HttpGlobalConfig; import org.dromara.hutool.http.HttpGlobalConfig;
import org.dromara.hutool.http.client.body.*; import org.dromara.hutool.http.client.body.*;
@ -49,34 +48,50 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class Request implements HeaderOperation<Request> { public class Request implements HeaderOperation<Request> {
/** /**
* 构建一个HTTP请求<br> * 构建一个HTTP请求不解码和编码此时要求用户传入的URL必须是已经编码过的
* 对于传入的URL可以自定义是否解码已经编码的内容设置见{@link HttpGlobalConfig#setDecodeUrl(boolean)}<br>
* 在构建Http请求时用户传入的URL可能有编码后和未编码的内容混合在一起如果{@link HttpGlobalConfig#isDecodeUrl()}{@code true}则会统一解码编码后的参数<br>
* 按照RFC3986规范在发送请求时全部编码之如果为{@code false}则不会解码已经编码的内容在请求时只编码需要编码的部分
* *
* @param url URL链接默认自动编码URL中的参数等信息 * @param url URL链接
* @return HttpRequest
*/
public static Request ofWithoutEncode(final String url) {
return of(url, null);
}
/**
* 构建一个HTTP请求默认编解码规则规则为
* <ul>
* <li>如果传入的URL已编码则先解码再按照RFC3986规范重新编码</li>
* <li>如果传入的URL未编码则先解码解码后不变再按照RFC3986规范重新编码</li>
* <li>如果传入的URL部分编码则先解码编码过的部分再按照RFC3986规范重新编码</li>
* </ul>
* 如果服务端要求的URL编码规则不符合RFC3986则需要先自行编码再调用{@link #of(String, Charset)}指定decodeAndEncodeCharset为{@code null}
*
* @param url URL链接默认自动解码并重新编码URL中的参数等信息
* @return HttpRequest * @return HttpRequest
*/ */
public static Request of(final String url) { public static Request of(final String url) {
return of(url, HttpGlobalConfig.isDecodeUrl() ? DEFAULT_CHARSET : null); return of(url, DEFAULT_CHARSET);
} }
/** /**
* 构建一个HTTP请求<br> * 构建一个HTTP请求<br>
* 对于传入的URL可以自定义是否解码已经编码的内容<br> * 对于传入的URL可以自定义是否解码已经编码的内容规则如下<br>
* 在构建Http请求时用户传入的URL可能有编码后和未编码的内容混合在一起如果charset参数不为{@code null}则会统一解码编码后的参数<br> * <ul>
* 按照RFC3986规范在发送请求时全部编码之如果为{@code false}则不会解码已经编码的内容在请求时只编码需要编码的部分 * <li>如果url已编码则decodeAndEncodeCharset设置为{@code null}此时URL不会解码发送也不编码</li>
* <li>如果url未编码或部分编码则需要设置decodeAndEncodeCharset此时URL会解码编码后的参数发送时按照RFC3986规范重新编码</li>
* <li>如果url未编码且存在歧义字符串则需要设置decodeAndEncodeCharset为{@code null}并调用{@link Request#setEncodeUrl(boolean)}为true编码URL</li>
* </ul>
* *
* @param url URL链接 * @param url URL链接
* @param charset 编码如果为{@code null}不自动解码编码URL * @param decodeAndEncodeCharset 编码如果为{@code null}不自动解码编码URL
* @return HttpRequest * @return HttpRequest
*/ */
public static Request of(final String url, final Charset charset) { public static Request of(final String url, final Charset decodeAndEncodeCharset) {
return of(UrlBuilder.ofHttp(url, charset)); return of(UrlBuilder.ofHttp(url, decodeAndEncodeCharset));
} }
/** /**
* 构建一个HTTP请求<br> * 构建一个HTTP请求
* *
* @param url {@link UrlBuilder} * @param url {@link UrlBuilder}
* @return HttpRequest * @return HttpRequest
@ -98,6 +113,10 @@ public class Request implements HeaderOperation<Request> {
* 请求的URL * 请求的URL
*/ */
private UrlBuilder url; private UrlBuilder url;
/**
* 请求编码
*/
private Charset charset = DEFAULT_CHARSET;
/** /**
* 存储头信息 * 存储头信息
*/ */
@ -178,25 +197,43 @@ public class Request implements HeaderOperation<Request> {
} }
/** /**
* 设置编码 * 设置自定义编码一般用于
* <ul>
* <li>编码请求体</li>
* <li>服务端未返回编码时使用此编码解码响应体</li>
* </ul>
* *
* @param charset 编码 * @param charset 编码
* @return this * @return this
*/ */
public Request charset(final Charset charset) { public Request charset(final Charset charset) {
Assert.notNull(this.url, "You must be set request url first."); this.charset = charset;
this.url.setCharset(charset);
return this; return this;
} }
/** /**
* 获取请求编码如果用户未设置返回{@link #DEFAULT_CHARSET} * 获取请求编码默认{@link #DEFAULT_CHARSET}一般用于
* <ul>
* <li>编码请求体</li>
* <li>服务端未返回编码时使用此编码解码响应体</li>
* </ul>
* *
* @return 编码 * @return 编码
*/ */
public Charset charset() { public Charset charset() {
Assert.notNull(this.url, "You must be set request url first."); return this.charset;
return ObjUtil.defaultIfNull(this.url.getCharset(), DEFAULT_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 @Override
@ -261,17 +298,17 @@ public class Request implements HeaderOperation<Request> {
*/ */
public Request form(final Map<String, Object> formMap) { public Request form(final Map<String, Object> formMap) {
final AtomicBoolean isMultiPart = new AtomicBoolean(false); final AtomicBoolean isMultiPart = new AtomicBoolean(false);
formMap.forEach((key, value)->{ formMap.forEach((key, value) -> {
if(value instanceof File || if (value instanceof File ||
value instanceof Path || value instanceof Path ||
value instanceof Resource || value instanceof Resource ||
value instanceof InputStream || value instanceof InputStream ||
value instanceof Reader){ value instanceof Reader) {
isMultiPart.set(true); isMultiPart.set(true);
} }
}); });
if(isMultiPart.get()){ if (isMultiPart.get()) {
return body(MultipartBody.of(formMap, charset())); return body(MultipartBody.of(formMap, charset()));
} }
return body(new UrlEncodedFormBody(formMap, charset())); return body(new UrlEncodedFormBody(formMap, charset()));

View File

@ -211,6 +211,7 @@ public class JdkClientEngine implements ClientEngine {
redirectUrl = UrlBuilder.of(parentUrl.getScheme(), parentUrl.getHost(), parentUrl.getPort(), redirectUrl = UrlBuilder.of(parentUrl.getScheme(), parentUrl.getHost(), parentUrl.getPort(),
location, query, null, parentUrl.getCharset()); location, query, null, parentUrl.getCharset());
} else { } else {
// location已经是编码过的URL
redirectUrl = UrlBuilder.ofHttpWithoutEncode(location); redirectUrl = UrlBuilder.ofHttpWithoutEncode(location);
} }

View File

@ -185,10 +185,9 @@ public class HttpUtilTest {
@Test @Test
@Disabled @Disabled
public void getPicTest(){ 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 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); Console.log(body);
} }

View File

@ -31,13 +31,15 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
/** /**
* {@link Request}单元测试 * {@link Request}单元测试
* *
* @author Looly * @author Looly
*/ */
@SuppressWarnings("resource") @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"; final String url = "http://photo.qzone.qq.com/fcgi-bin/fcg_list_album?uin=88888&outstyle=2";
@Test @Test
@ -238,4 +240,21 @@ public class HttpRequestTest {
final Response httpResponse = Request.of("http://82.157.17.173:8100/app/getAddress").send(); final Response httpResponse = Request.of("http://82.157.17.173:8100/app/getAddress").send();
Console.log(httpResponse.body()); 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());
}
} }