add UrlDecoder

This commit is contained in:
Looly
2020-04-16 01:13:58 +08:00
parent 8102b31373
commit ca7c407a1c
18 changed files with 1402 additions and 229 deletions

View File

@@ -12,11 +12,11 @@ import cn.hutool.core.io.resource.MultiResource;
import cn.hutool.core.io.resource.Resource;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.http.cookie.GlobalCookieManager;
import cn.hutool.http.ssl.SSLSocketFactoryBuilder;
@@ -96,7 +96,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
GlobalCookieManager.setCookieManager(null);
}
private String url;
private UrlBuilder url;
private URLStreamHandler urlHandler;
private Method method = Method.GET;
/**
@@ -128,10 +128,6 @@ public class HttpRequest extends HttpBase<HttpRequest> {
* 是否禁用缓存
*/
private boolean isDisableCache;
/**
* 是否对url中的参数进行编码
*/
private boolean encodeUrlParams;
/**
* 是否是REST请求模式
*/
@@ -168,8 +164,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
* @param url URL
*/
public HttpRequest(String url) {
Assert.notBlank(url, "Param [url] can not be blank !");
this.url = URLUtil.normalize(url, true);
setUrl(url);
// 给定一个默认头信息
this.header(GlobalHeaders.INSTANCE.headers);
}
@@ -265,7 +260,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
* @since 4.1.8
*/
public String getUrl() {
return url;
return url.toString();
}
/**
@@ -276,7 +271,19 @@ public class HttpRequest extends HttpBase<HttpRequest> {
* @since 4.1.8
*/
public HttpRequest setUrl(String url) {
this.url = url;
this.url = UrlBuilder.ofHttp(url, this.charset);
return this;
}
/**
* 设置URL
*
* @param urlBuilder url字符串
* @return this
* @since 5.3.1
*/
public HttpRequest setUrl(UrlBuilder urlBuilder) {
this.url = urlBuilder;
return this;
}
@@ -774,9 +781,10 @@ public class HttpRequest extends HttpBase<HttpRequest> {
* @param isEncodeUrlParams 是否对URL中的参数进行编码
* @return this
* @since 4.4.1
* @deprecated 编码自动完成,无需设置
*/
@Deprecated
public HttpRequest setEncodeUrlParams(boolean isEncodeUrlParams) {
this.encodeUrlParams = isEncodeUrlParams;
return this;
}
@@ -925,10 +933,6 @@ public class HttpRequest extends HttpBase<HttpRequest> {
public HttpResponse execute(boolean isAsync) {
// 初始化URL
urlWithParamIfGet();
// 编码URL
if (this.encodeUrlParams) {
this.url = HttpUtil.encodeParams(this.url, this.charset);
}
// 初始化 connection
initConnection();
@@ -982,7 +986,8 @@ public class HttpRequest extends HttpBase<HttpRequest> {
this.httpConnection.disconnectQuietly();
}
this.httpConnection = HttpConnection.create(URLUtil.toUrlForHttp(this.url, this.urlHandler), this.proxy)//
this.httpConnection = HttpConnection
.create(this.url.toURL(this.urlHandler), this.proxy)//
.setMethod(this.method)//
.setHttpsInfo(this.hostnameVerifier, this.ssf)//
.setConnectTimeout(this.connectionTimeout)//
@@ -1016,9 +1021,9 @@ public class HttpRequest extends HttpBase<HttpRequest> {
if (Method.GET.equals(method) && false == this.isRest) {
// 优先使用body形式的参数不存在使用form
if (ArrayUtil.isNotEmpty(this.bodyBytes)) {
this.url = HttpUtil.urlWithForm(this.url, StrUtil.str(this.bodyBytes, this.charset), this.charset, false);
this.url.getQuery().parse(StrUtil.str(this.bodyBytes, this.charset), this.charset);
} else {
this.url = HttpUtil.urlWithForm(this.url, this.form, this.charset, false);
this.url.getQuery().addAll(this.form);
}
}
}
@@ -1047,7 +1052,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
if (responseCode != HttpURLConnection.HTTP_OK) {
if (responseCode == HttpURLConnection.HTTP_MOVED_TEMP || responseCode == HttpURLConnection.HTTP_MOVED_PERM || responseCode == HttpURLConnection.HTTP_SEE_OTHER) {
this.url = httpConnection.header(Header.LOCATION);
setUrl(httpConnection.header(Header.LOCATION));
if (redirectCount < this.maxRedirectCount) {
redirectCount++;
return execute();

View File

@@ -471,11 +471,11 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
*/
private String getFileNameFromDisposition() {
String fileName = null;
final String desposition = header(Header.CONTENT_DISPOSITION);
if (StrUtil.isNotBlank(desposition)) {
fileName = ReUtil.get("filename=\"(.*?)\"", desposition, 1);
final String disposition = header(Header.CONTENT_DISPOSITION);
if (StrUtil.isNotBlank(disposition)) {
fileName = ReUtil.get("filename=\"(.*?)\"", disposition, 1);
if (StrUtil.isBlank(fileName)) {
fileName = StrUtil.subAfter(desposition, "filename=", true);
fileName = StrUtil.subAfter(disposition, "filename=", true);
}
}
return fileName;

View File

@@ -1,14 +1,12 @@
package cn.hutool.http;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FastByteArrayOutputStream;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.StreamProgress;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.net.url.UrlQuery;
import cn.hutool.core.text.StrBuilder;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjectUtil;
@@ -23,13 +21,9 @@ import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;
/**
@@ -390,44 +384,11 @@ public class HttpUtil {
* </pre>
*
* @param paramMap 表单数据
* @param charset 编码
* @param charset 编码null表示不encode键值对
* @return url参数
*/
public static String toParams(Map<String, ?> paramMap, Charset charset) {
if (CollectionUtil.isEmpty(paramMap)) {
return StrUtil.EMPTY;
}
if (null == charset) {// 默认编码为系统编码
charset = CharsetUtil.CHARSET_UTF_8;
}
final StringBuilder sb = new StringBuilder();
boolean isFirst = true;
String key;
Object value;
String valueStr;
for (Entry<String, ?> item : paramMap.entrySet()) {
if (isFirst) {
isFirst = false;
} else {
sb.append("&");
}
key = item.getKey();
value = item.getValue();
if (value instanceof Iterable) {
value = CollUtil.join((Iterable<?>) value, ",");
} else if (value instanceof Iterator) {
value = IterUtil.join((Iterator<?>) value, ",");
}
valueStr = Convert.toStr(value);
if (StrUtil.isNotEmpty(key)) {
sb.append(URLUtil.encodeAll(key, charset)).append("=");
if (StrUtil.isNotEmpty(valueStr)) {
sb.append(URLUtil.encodeAll(valueStr, charset));
}
}
}
return sb.toString();
return URLUtil.buildQuery(paramMap, charset);
}
/**
@@ -437,7 +398,7 @@ public class HttpUtil {
* <p>注意此方法只能标准化整个URL并不适合于单独编码参数值</p>
*
* @param urlWithParams url和参数可以包含url本身也可以单独参数
* @param charset 编码
* @param charset 编码
* @return 编码后的url和参数
* @since 4.0.1
*/
@@ -457,10 +418,10 @@ public class HttpUtil {
// 无参数返回url
return urlPart;
}
} else if(false == StrUtil.contains(urlWithParams, '=')){
} else if (false == StrUtil.contains(urlWithParams, '=')) {
// 无参数的URL
return urlWithParams;
}else {
} else {
// 无URL的参数
paramPart = urlWithParams;
}
@@ -536,8 +497,10 @@ public class HttpUtil {
* @param charset 字符集
* @return 参数Map
* @since 4.0.2
* @deprecated 请使用 {@link #decodeParamMap(String, Charset)}
*/
public static HashMap<String, String> decodeParamMap(String paramsStr, String charset) {
@Deprecated
public static Map<String, String> decodeParamMap(String paramsStr, String charset) {
return decodeParamMap(paramsStr, CharsetUtil.charset(charset));
}
@@ -549,15 +512,12 @@ public class HttpUtil {
* @return 参数Map
* @since 5.2.6
*/
public static HashMap<String, String> decodeParamMap(String paramsStr, Charset charset) {
final Map<String, List<String>> paramsMap = decodeParams(paramsStr, charset);
final HashMap<String, String> result = MapUtil.newHashMap(paramsMap.size());
List<String> valueList;
for (Entry<String, List<String>> entry : paramsMap.entrySet()) {
valueList = entry.getValue();
result.put(entry.getKey(), CollUtil.isEmpty(valueList) ? null : valueList.get(0));
public static Map<String, String> decodeParamMap(String paramsStr, Charset charset) {
final Map<CharSequence, CharSequence> queryMap = UrlQuery.of(paramsStr, charset).getQueryMap();
if (MapUtil.isEmpty(queryMap)) {
return MapUtil.empty();
}
return result;
return Convert.toMap(String.class, String.class, queryMap);
}
/**
@@ -580,56 +540,17 @@ public class HttpUtil {
* @since 5.2.6
*/
public static Map<String, List<String>> decodeParams(String paramsStr, Charset charset) {
if (StrUtil.isBlank(paramsStr)) {
return Collections.emptyMap();
final Map<CharSequence, CharSequence> queryMap = UrlQuery.of(paramsStr, charset).getQueryMap();
if (MapUtil.isEmpty(queryMap)) {
return MapUtil.empty();
}
// 去掉Path部分
int pathEndPos = paramsStr.indexOf('?');
if (pathEndPos > -1) {
paramsStr = StrUtil.subSuf(paramsStr, pathEndPos + 1);
if (StrUtil.isBlank(paramsStr)) {
return Collections.emptyMap();
}
}
final int len = paramsStr.length();
final Map<String, List<String>> params = new LinkedHashMap<>();
String name = null;
int pos = 0; // 未处理字符开始位置
int i; // 未处理字符结束位置
char c; // 当前字符
for (i = 0; i < len; i++) {
c = paramsStr.charAt(i);
if (c == '=') { // 键值对的分界点
if (null == name) {
// name可以是""
name = paramsStr.substring(pos, i);
}
pos = i + 1;
} else if (c == '&') { // 参数对的分界点
if (null == name && pos != i) {
// 对于像&a&这类无参数值的字符串我们将name为a的值设为""
addParam(params, paramsStr.substring(pos, i), StrUtil.EMPTY, charset);
} else if (name != null) {
addParam(params, name, paramsStr.substring(pos, i), charset);
name = null;
}
pos = i + 1;
}
}
// 处理结尾
if (pos != i) {
if (name == null) {
addParam(params, paramsStr.substring(pos, i), StrUtil.EMPTY, charset);
} else {
addParam(params, name, paramsStr.substring(pos, i), charset);
}
} else if (name != null) {
addParam(params, name, StrUtil.EMPTY, charset);
}
queryMap.forEach((key, value) -> {
final List<String> values = params.computeIfAbsent(StrUtil.str(key), k -> new ArrayList<>(1));
// 一般是一个参数
values.add(StrUtil.str(value));
});
return params;
}
@@ -826,26 +747,7 @@ public class HttpUtil {
* @return {@link SimpleServer}
* @since 5.2.6
*/
public static SimpleServer createServer(int port){
public static SimpleServer createServer(int port) {
return new SimpleServer(port);
}
// ----------------------------------------------------------------------------------------- Private method start
/**
* 将键值对加入到值为List类型的Map中
*
* @param params 参数
* @param name key
* @param value value
* @param charset 编码
*/
private static void addParam(Map<String, List<String>> params, String name, String value, Charset charset) {
name = URLUtil.decode(name, charset);
value = URLUtil.decode(value, charset);
final List<String> values = params.computeIfAbsent(name, k -> new ArrayList<>(1));
// 一般是一个参数
values.add(value);
}
// ----------------------------------------------------------------------------------------- Private method start end
}

View File

@@ -11,6 +11,7 @@ import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -18,7 +19,7 @@ import java.util.Map;
public class HttpUtilTest {
@Test
@Ignore
// @Ignore
public void postTest() {
String result = HttpUtil.createPost("api.uhaozu.com/goods/description/1120448506").charset(CharsetUtil.UTF_8).execute().body();
Console.log(result);
@@ -71,6 +72,17 @@ public class HttpUtilTest {
Console.log(str);
}
@Test
@Ignore
public void getTest5() {
String url2 = "http://storage.chancecloud.com.cn/20200413_%E7%B2%A4B12313_386.pdf";
ByteArrayOutputStream os2 = new ByteArrayOutputStream();
HttpUtil.download(url2, os2, false);
url2 = "http://storage.chancecloud.com.cn/20200413_粤B12313_386.pdf";
HttpUtil.download(url2, os2, false);
}
@Test
@Ignore
public void get12306Test() {