From d0fe78ae662f7bad023fe22bb770989669b0d5c5 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 14 May 2020 01:56:03 +0800 Subject: [PATCH] add body --- CHANGELOG.md | 2 + .../hutool/core/bean/copier/BeanCopier.java | 2 +- .../core/convert/impl/BeanConverter.java | 4 +- .../main/java/cn/hutool/core/io/IoUtil.java | 2 +- .../cn/hutool/core/io/resource/Resource.java | 16 ++ .../java/cn/hutool/core/util/ObjectUtil.java | 28 +-- .../main/java/cn/hutool/http/ContentType.java | 4 + .../main/java/cn/hutool/http/HttpRequest.java | 188 +++++++----------- .../cn/hutool/http/body/MultipartBody.java | 154 ++++++++++++++ .../java/cn/hutool/http/body/RequestBody.java | 16 ++ .../cn/hutool/http/body/package-info.java | 7 + .../hutool/http/server/SimpleServerTest.java | 10 +- .../java/cn/hutool/http/test/UploadTest.java | 20 +- .../test/java/cn/hutool/json/IssueI1H2VN.java | 40 ++++ 14 files changed, 337 insertions(+), 156 deletions(-) create mode 100644 hutool-http/src/main/java/cn/hutool/http/body/MultipartBody.java create mode 100644 hutool-http/src/main/java/cn/hutool/http/body/RequestBody.java create mode 100644 hutool-http/src/main/java/cn/hutool/http/body/package-info.java create mode 100644 hutool-json/src/test/java/cn/hutool/json/IssueI1H2VN.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 87c7ec4c9..9cfccb9e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,10 +11,12 @@ * 【system 】 OshiUtil增加getNetworkIFs方法 * 【core 】 CollUtil增加unionDistinct、unionAll方法(pr#122@Gitee) * 【core 】 增加IoUtil.readObj重载,通过ValidateObjectInputStream由用户自定义安全检查。 +* 【http 】 改造HttpRequest中文件上传部分,增加MultipartBody类 ### Bug修复 * 【core 】 修复IoUtil.readObj中反序列化安全检查导致的一些问题,去掉安全检查。 * 【http 】 修复SimpleServer文件访问404问题(issue#I1GZI3@Gitee) +* 【core 】 修复BeanCopier中循环引用逻辑问题(issue#I1H2VN@Gitee) ------------------------------------------------------------------------------------------------------------- diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java index 27ed86fbb..10fb83b65 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java @@ -267,7 +267,7 @@ public class BeanCopier implements Copier, Serializable { if (null == value && copyOptions.ignoreNullValue) { continue;// 当允许跳过空时,跳过 } - if (bean.equals(value)) { + if (bean == value) { continue;// 值不能为bean本身,防止循环引用 } diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/BeanConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/BeanConverter.java index cb78ec6e4..72513ef59 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/BeanConverter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/BeanConverter.java @@ -65,7 +65,9 @@ public class BeanConverter extends AbstractConverter { @Override protected T convertInternal(Object value) { - if(value instanceof Map || value instanceof ValueProvider || BeanUtil.isBean(value.getClass())) { + if(value instanceof Map || + value instanceof ValueProvider || + BeanUtil.isBean(value.getClass())) { if(value instanceof Map && this.beanClass.isInterface()) { // 将Map动态代理为Bean return MapProxy.create((Map)value).toProxyBean(this.beanClass); diff --git a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java index 2896c0102..9d368316c 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/IoUtil.java @@ -1001,9 +1001,9 @@ public class IoUtil { for (Object content : contents) { if (content != null) { osw.write(Convert.toStr(content, StrUtil.EMPTY)); - osw.flush(); } } + osw.flush(); } catch (IOException e) { throw new IORuntimeException(e); } finally { diff --git a/hutool-core/src/main/java/cn/hutool/core/io/resource/Resource.java b/hutool-core/src/main/java/cn/hutool/core/io/resource/Resource.java index 22b2d702f..98989a29e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/resource/Resource.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/resource/Resource.java @@ -1,11 +1,13 @@ package cn.hutool.core.io.resource; import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.CharsetUtil; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.net.URL; import java.nio.charset.Charset; @@ -36,6 +38,20 @@ public interface Resource { * @return {@link InputStream} */ InputStream getStream(); + + /** + * 将资源内容写出到流,不关闭输出流,但是关闭资源流 + * @param out 输出流 + * @throws IORuntimeException IO异常 + * @since 5.3.5 + */ + default void writeTo(OutputStream out) throws IORuntimeException{ + try (InputStream in = getStream()) { + IoUtil.copy(in, out); + } catch (IOException e) { + throw new IORuntimeException(e); + } + } /** * 获得Reader diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java index 4ae7360ce..6b2a8261a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ObjectUtil.java @@ -397,18 +397,8 @@ public class ObjectUtil { if (false == (obj instanceof Serializable)) { return null; } - - FastByteArrayOutputStream byteOut = new FastByteArrayOutputStream(); - ObjectOutputStream oos = null; - try { - oos = new ObjectOutputStream(byteOut); - oos.writeObject(obj); - oos.flush(); - } catch (Exception e) { - throw new UtilException(e); - } finally { - IoUtil.close(oos); - } + final FastByteArrayOutputStream byteOut = new FastByteArrayOutputStream(); + IoUtil.writeObjects(byteOut, false, (Serializable) obj); return byteOut.toByteArray(); } @@ -416,20 +406,16 @@ public class ObjectUtil { * 反序列化
* 对象必须实现Serializable接口 * + *

+ * 注意!!! 此方法不会检查反序列化安全,可能存在反序列化漏洞风险!!! + *

+ * * @param 对象类型 * @param bytes 反序列化的字节码 * @return 反序列化后的对象 */ - @SuppressWarnings("unchecked") public static T deserialize(byte[] bytes) { - ObjectInputStream ois; - try { - ByteArrayInputStream bais = new ByteArrayInputStream(bytes); - ois = new ObjectInputStream(bais); - return (T) ois.readObject(); - } catch (Exception e) { - throw new UtilException(e); - } + return IoUtil.readObj(new ByteArrayInputStream(bytes)); } /** diff --git a/hutool-http/src/main/java/cn/hutool/http/ContentType.java b/hutool-http/src/main/java/cn/hutool/http/ContentType.java index 1eb1a719b..b60259b71 100644 --- a/hutool-http/src/main/java/cn/hutool/http/ContentType.java +++ b/hutool-http/src/main/java/cn/hutool/http/ContentType.java @@ -43,6 +43,10 @@ public enum ContentType { private final String value; + /** + * 构造 + * @param value ContentType值 + */ ContentType(String value) { this.value = value; } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index a23d367be..2637a30c5 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -1,14 +1,13 @@ package cn.hutool.http; import cn.hutool.core.codec.Base64; -import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Convert; import cn.hutool.core.io.IORuntimeException; import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.resource.BytesResource; import cn.hutool.core.io.resource.FileResource; import cn.hutool.core.io.resource.MultiFileResource; -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; @@ -16,8 +15,8 @@ import cn.hutool.core.net.url.UrlBuilder; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.http.body.MultipartBody; import cn.hutool.http.cookie.GlobalCookieManager; import cn.hutool.http.ssl.SSLSocketFactoryBuilder; @@ -25,18 +24,15 @@ import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLSocketFactory; import java.io.File; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; import java.net.CookieManager; import java.net.HttpCookie; import java.net.HttpURLConnection; import java.net.Proxy; import java.net.URLStreamHandler; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; /** * http请求类
@@ -46,12 +42,7 @@ import java.util.Map.Entry; */ public class HttpRequest extends HttpBase { - private static final String BOUNDARY = "--------------------Hutool_" + RandomUtil.randomString(16); - private static final byte[] BOUNDARY_END = StrUtil.format("--{}--\r\n", BOUNDARY).getBytes(); - private static final String CONTENT_DISPOSITION_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"\r\n\r\n"; - private static final String CONTENT_DISPOSITION_FILE_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"\r\n"; - - private static final String CONTENT_TYPE_MULTIPART_PREFIX = "multipart/form-data; boundary="; + private static final String CONTENT_TYPE_MULTIPART_PREFIX = ContentType.MULTIPART.getValue() + "; boundary="; private static final String CONTENT_TYPE_FILE_TEMPLATE = "Content-Type: {}\r\n\r\n"; /** @@ -113,9 +104,9 @@ public class HttpRequest extends HttpBase { */ private Map form; /** - * 文件表单对象,用于文件上传 + * 是否为Multipart表单 */ - private Map fileForm; + private boolean isMultiPart; /** * Cookie */ @@ -492,17 +483,17 @@ public class HttpRequest extends HttpBase { if (value instanceof File) { // 文件上传 return this.form(name, (File) value); - } else if (value instanceof Resource) { - // 自定义流上传 - return this.form(name, (Resource) value); - } else if (this.form == null) { - this.form = new LinkedHashMap<>(); } + if(value instanceof Resource){ + return form(name, (Resource)value); + } + + // 普通值 String strValue; if (value instanceof List) { // 列表对象 - strValue = CollectionUtil.join((List) value, ","); + strValue = CollUtil.join((List) value, ","); } else if (ArrayUtil.isArray(value)) { if (File.class == ArrayUtil.getComponentType(value)) { // 多文件 @@ -515,8 +506,7 @@ public class HttpRequest extends HttpBase { strValue = Convert.toStr(value, null); } - form.put(name, strValue); - return this; + return putToForm(name, strValue); } /** @@ -531,8 +521,7 @@ public class HttpRequest extends HttpBase { form(name, value); for (int i = 0; i < parameters.length; i += 2) { - name = parameters[i].toString(); - form(name, parameters[i + 1]); + form(parameters[i].toString(), parameters[i + 1]); } return this; } @@ -545,9 +534,7 @@ public class HttpRequest extends HttpBase { */ public HttpRequest form(Map formMap) { if (MapUtil.isNotEmpty(formMap)) { - for (Map.Entry entry : formMap.entrySet()) { - form(entry.getKey(), entry.getValue()); - } + formMap.forEach(this::form); } return this; } @@ -557,10 +544,13 @@ public class HttpRequest extends HttpBase { * 一旦有文件加入,表单变为multipart/form-data * * @param name 名 - * @param files 需要上传的文件 + * @param files 需要上传的文件,为空跳过 * @return this */ public HttpRequest form(String name, File... files) { + if(ArrayUtil.isEmpty(files)){ + return this; + } if (1 == files.length) { final File file = files[0]; return form(name, file, file.getName()); @@ -628,11 +618,8 @@ public class HttpRequest extends HttpBase { keepAlive(true); } - if (null == this.fileForm) { - fileForm = new HashMap<>(); - } - // 文件对象 - this.fileForm.put(name, resource); + this.isMultiPart = true; + return putToForm(name, resource); } return this; } @@ -653,7 +640,13 @@ public class HttpRequest extends HttpBase { * @since 3.3.0 */ public Map fileForm() { - return this.fileForm; + final Map result = MapUtil.newHashMap(); + this.form.forEach((key, value)->{ + if(value instanceof Resource){ + result.put(key, (Resource)value); + } + }); + return result; } // ---------------------------------------------------------------- Form end @@ -1091,10 +1084,10 @@ public class HttpRequest extends HttpBase { || Method.PUT.equals(this.method) // || Method.DELETE.equals(this.method) // || this.isRest) { - if (CollectionUtil.isEmpty(this.fileForm)) { - sendFormUrlEncoded();// 普通表单 - } else { + if (isMultipart()) { sendMultipart(); // 文件上传表单 + } else { + sendFormUrlEncoded();// 普通表单 } } else { this.httpConnection.connect(); @@ -1148,94 +1141,15 @@ public class HttpRequest extends HttpBase { setMultipart();// 设置表单类型为Multipart try (OutputStream out = this.httpConnection.getOutputStream()) { - writeFileForm(out); - writeForm(out); - formEnd(out); + MultipartBody.create(this.form, this.charset).write(out); } } - // 普通字符串数据 - - /** - * 发送普通表单内容 - * - * @param out 输出流 - */ - private void writeForm(OutputStream out) { - if (CollectionUtil.isNotEmpty(this.form)) { - StringBuilder builder = StrUtil.builder(); - for (Entry entry : this.form.entrySet()) { - builder.append("--").append(BOUNDARY).append(StrUtil.CRLF); - builder.append(StrUtil.format(CONTENT_DISPOSITION_TEMPLATE, entry.getKey())); - builder.append(entry.getValue()).append(StrUtil.CRLF); - } - IoUtil.write(out, this.charset, false, builder); - } - } - - /** - * 发送文件对象表单 - * - * @param out 输出流 - */ - private void writeFileForm(OutputStream out) { - for (Entry entry : this.fileForm.entrySet()) { - appendPart(entry.getKey(), entry.getValue(), out); - } - } - - /** - * 添加Multipart表单的数据项 - * - * @param formFieldName 表单名 - * @param resource 资源,可以是文件等 - * @param out Http流 - * @since 4.1.0 - */ - private void appendPart(String formFieldName, Resource resource, OutputStream out) { - if (resource instanceof MultiResource) { - // 多资源 - for (Resource subResource : (MultiResource) resource) { - appendPart(formFieldName, subResource, out); - } - } else { - // 普通资源 - final StringBuilder builder = StrUtil.builder().append("--").append(BOUNDARY).append(StrUtil.CRLF); - final String fileName = resource.getName(); - builder.append(StrUtil.format(CONTENT_DISPOSITION_FILE_TEMPLATE, formFieldName, ObjectUtil.defaultIfNull(fileName, formFieldName))); - // 根据name的扩展名指定互联网媒体类型,默认二进制流数据 - builder.append(StrUtil.format(CONTENT_TYPE_FILE_TEMPLATE, HttpUtil.getMimeType(fileName, "application/octet-stream"))); - IoUtil.write(out, this.charset, false, builder); - InputStream in = null; - try { - in = resource.getStream(); - IoUtil.copy(in, out); - } finally { - IoUtil.close(in); - } - IoUtil.write(out, this.charset, false, StrUtil.CRLF); - } - - } - - // 添加结尾数据 - - /** - * 上传表单结束 - * - * @param out 输出流 - * @throws IOException IO异常 - */ - private void formEnd(OutputStream out) throws IOException { - out.write(BOUNDARY_END); - out.flush(); - } - /** * 设置表单类型为Multipart(文件上传) */ private void setMultipart() { - this.httpConnection.header(Header.CONTENT_TYPE, CONTENT_TYPE_MULTIPART_PREFIX + BOUNDARY, true); + this.httpConnection.header(Header.CONTENT_TYPE, MultipartBody.getContentType(), true); } /** @@ -1251,6 +1165,44 @@ public class HttpRequest extends HttpBase { || Method.OPTIONS == this.method // || Method.TRACE == this.method; } + + /** + * 判断是否为multipart/form-data表单,条件如下: + * + *
+	 *     1. 存在资源对象(fileForm非空)
+	 *     2. 用户自定义头为multipart/form-data开头
+	 * 
+ * @return 是否为multipart/form-data表单 + * @since 5.3.5 + */ + private boolean isMultipart(){ + if(this.isMultiPart){ + return true; + } + + final String contentType = header(Header.CONTENT_TYPE); + return StrUtil.isNotEmpty(contentType) && + contentType.startsWith(ContentType.MULTIPART.getValue()); + } + + /** + * 将参数加入到form中,如果form为空,新建之。 + * + * @param name 表单属性名 + * @param value 属性值 + * @return this + */ + private HttpRequest putToForm(String name, Object value){ + if(null == name || null == value){ + return this; + } + if(null == this.form){ + this.form = new LinkedHashMap<>(); + } + this.form.put(name, value); + return this; + } // ---------------------------------------------------------------- Private method end } diff --git a/hutool-http/src/main/java/cn/hutool/http/body/MultipartBody.java b/hutool-http/src/main/java/cn/hutool/http/body/MultipartBody.java new file mode 100644 index 000000000..05374c044 --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/body/MultipartBody.java @@ -0,0 +1,154 @@ +package cn.hutool.http.body; + +import cn.hutool.core.io.IORuntimeException; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.resource.MultiResource; +import cn.hutool.core.io.resource.Resource; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.http.ContentType; +import cn.hutool.http.HttpUtil; + +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.util.Map; + +/** + * Multipart/form-data数据的请求体封装 + * + * @author looly + * @since 5.3.5 + */ +public class MultipartBody implements RequestBody{ + + private static final String BOUNDARY = "--------------------Hutool_" + RandomUtil.randomString(16); + private static final String BOUNDARY_END = StrUtil.format("--{}--\r\n", BOUNDARY); + private static final String CONTENT_DISPOSITION_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"\r\n\r\n"; + private static final String CONTENT_DISPOSITION_FILE_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"\r\n"; + + private static final String CONTENT_TYPE_MULTIPART_PREFIX = ContentType.MULTIPART.getValue() + "; boundary="; + private static final String CONTENT_TYPE_FILE_TEMPLATE = "Content-Type: {}\r\n\r\n"; + + /** + * 存储表单数据 + */ + private final Map form; + /** + * 编码 + */ + private final Charset charset; + + /** + * 根据已有表单内容,构建MultipartBody + * @param form 表单 + * @param charset 编码 + * @return MultipartBody + */ + public static MultipartBody create(Map form, Charset charset){ + return new MultipartBody(form, charset); + } + + /** + * 获取Multipart的Content-Type类型 + * + * @return Multipart的Content-Type类型 + */ + public static String getContentType(){ + return CONTENT_TYPE_MULTIPART_PREFIX + BOUNDARY; + } + + /** + * 构造 + * + * @param form 表单 + * @param charset 编码 + */ + public MultipartBody(Map form, Charset charset) { + this.form = form; + this.charset = charset; + } + + /** + * 写出Multiparty数据,不关闭流 + * + * @param out out流 + */ + @Override + public void write(OutputStream out) { + writeForm(out); + formEnd(out); + } + + // 普通字符串数据 + + /** + * 发送文件对象表单 + * + * @param out 输出流 + */ + private void writeForm(OutputStream out) { + if (MapUtil.isNotEmpty(this.form)) { + for (Map.Entry entry : this.form.entrySet()) { + appendPart(entry.getKey(), entry.getValue(), out); + } + } + } + + /** + * 添加Multipart表单的数据项 + * + * @param formFieldName 表单名 + * @param value 值,可以是普通值、资源(如文件等) + * @param out Http流 + * @throws IORuntimeException IO异常 + */ + private void appendPart(String formFieldName, Object value, OutputStream out) throws IORuntimeException { + // 多资源 + if (value instanceof MultiResource) { + for (Resource subResource : (MultiResource) value) { + appendPart(formFieldName, subResource, out); + } + return; + } + + write(out, "--", BOUNDARY, StrUtil.CRLF); + + if(value instanceof Resource){ + // 文件资源(二进制资源) + final Resource resource = (Resource)value; + final String fileName = resource.getName(); + write(out, StrUtil.format(CONTENT_DISPOSITION_FILE_TEMPLATE, formFieldName, ObjectUtil.defaultIfNull(fileName, formFieldName))); + // 根据name的扩展名指定互联网媒体类型,默认二进制流数据 + write(out, StrUtil.format(CONTENT_TYPE_FILE_TEMPLATE, HttpUtil.getMimeType(fileName, "application/octet-stream"))); + resource.writeTo(out); + } else{ + // 普通数据 + write(out, StrUtil.format(CONTENT_DISPOSITION_TEMPLATE, formFieldName)); + write(out, value); + } + + write(out, StrUtil.CRLF); + } + + /** + * 上传表单结束 + * + * @param out 输出流 + * @throws IORuntimeException IO异常 + */ + private void formEnd(OutputStream out) throws IORuntimeException { + write(out, BOUNDARY_END); + } + + /** + * 写出对象 + * + * @param out 输出流 + * @param objs 写出的对象(转换为字符串) + */ + private void write(OutputStream out, Object... objs) { + IoUtil.write(out, this.charset, false, objs); + } +} diff --git a/hutool-http/src/main/java/cn/hutool/http/body/RequestBody.java b/hutool-http/src/main/java/cn/hutool/http/body/RequestBody.java new file mode 100644 index 000000000..574a4ed6f --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/body/RequestBody.java @@ -0,0 +1,16 @@ +package cn.hutool.http.body; + +import java.io.OutputStream; + +/** + * 定义请求体接口 + */ +public interface RequestBody { + + /** + * 写出数据,不关闭流 + * + * @param out out流 + */ + void write(OutputStream out); +} diff --git a/hutool-http/src/main/java/cn/hutool/http/body/package-info.java b/hutool-http/src/main/java/cn/hutool/http/body/package-info.java new file mode 100644 index 000000000..3e15dbfa0 --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/body/package-info.java @@ -0,0 +1,7 @@ +/** + * 请求体封装实现 + * + * @author looly + * + */ +package cn.hutool.http.body; \ No newline at end of file diff --git a/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java b/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java index de61f7f70..f76021b18 100644 --- a/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/server/SimpleServerTest.java @@ -27,11 +27,13 @@ public class SimpleServerTest { // 文件上传测试 // http://localhost:8888/formTest?a=1&a=2&b=3 .addAction("/file", (request, response) -> { - final UploadFile file = request.getMultipart().getFile("file"); + final UploadFile[] files = request.getMultipart().getFiles("file"); // 传入目录,默认读取HTTP头中的文件名然后创建文件 - file.write("d:/test/"); - Console.log("Write file to: d:/test/"); - response.write(request.getParams().toString(), ContentType.TEXT_PLAIN.toString()); + for (UploadFile file : files) { + file.write("d:/test/"); + Console.log("Write file: d:/test/" + file.getFileName()); + } + response.write(request.getMultipart().getParamMap().toString(), ContentType.TEXT_PLAIN.toString()); } ) .start(); diff --git a/hutool-http/src/test/java/cn/hutool/http/test/UploadTest.java b/hutool-http/src/test/java/cn/hutool/http/test/UploadTest.java index 8fdd17c30..6a8e151bd 100644 --- a/hutool-http/src/test/java/cn/hutool/http/test/UploadTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/test/UploadTest.java @@ -1,15 +1,15 @@ package cn.hutool.http.test; -import java.io.File; -import java.util.HashMap; - -import org.junit.Ignore; -import org.junit.Test; - import cn.hutool.core.io.FileUtil; +import cn.hutool.core.lang.Console; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpUtil; +import org.junit.Ignore; +import org.junit.Test; + +import java.io.File; +import java.util.HashMap; /** * 文件上传单元测试 @@ -24,16 +24,16 @@ public class UploadTest { @Test @Ignore public void uploadFilesTest() { - File file = FileUtil.file("e:\\face.jpg"); - File file2 = FileUtil.file("e:\\face2.jpg"); + File file = FileUtil.file("d:\\图片1.JPG"); + File file2 = FileUtil.file("d:\\图片3.png"); // 方法一:自定义构建表单 HttpRequest request = HttpRequest// - .post("http://localhost:8090/file/upload")// + .post("http://localhost:8888/file")// .form("file", file2, file)// .form("fileType", "图片"); HttpResponse response = request.execute(); - System.out.println(response.body()); + Console.log(response.body()); } @Test diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueI1H2VN.java b/hutool-json/src/test/java/cn/hutool/json/IssueI1H2VN.java new file mode 100644 index 000000000..5004d2fac --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueI1H2VN.java @@ -0,0 +1,40 @@ +package cn.hutool.json; + +import lombok.Data; +import org.junit.Assert; +import org.junit.Test; + +import java.util.List; + +/** + * 测试同一对象作为对象的字段是否会有null的问题, + * 此问题原来出在BeanCopier中,判断循环引用使用了equals,并不严谨。 + * 修复后使用==判断循环引用。 + */ +public class IssueI1H2VN { + + @Test + public void toBeanTest() { + String jsonStr = "{'conditionsVo':[{'column':'StockNo','value':'abc','type':'='},{'column':'CheckIncoming','value':'1','type':'='}]," + + "'queryVo':{'conditionsVo':[{'column':'StockNo','value':'abc','type':'='},{'column':'CheckIncoming','value':'1','type':'='}],'queryVo':null}}"; + QueryVo vo = JSONUtil.toBean(jsonStr, QueryVo.class); + Assert.assertEquals(2, vo.getConditionsVo().size()); + final QueryVo subVo = vo.getQueryVo(); + Assert.assertNotNull(subVo); + Assert.assertEquals(2, subVo.getConditionsVo().size()); + Assert.assertNull(subVo.getQueryVo()); + } + + @Data + public static class ConditionVo { + private String column; + private String value; + private String type; + } + + @Data + public static class QueryVo { + private List conditionsVo; + private QueryVo queryVo; + } +}