mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-04-19 03:01:48 +08:00
add body
This commit is contained in:
parent
3da83e0227
commit
d0fe78ae66
@ -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)
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
@ -267,7 +267,7 @@ public class BeanCopier<T> implements Copier<T>, Serializable {
|
||||
if (null == value && copyOptions.ignoreNullValue) {
|
||||
continue;// 当允许跳过空时,跳过
|
||||
}
|
||||
if (bean.equals(value)) {
|
||||
if (bean == value) {
|
||||
continue;// 值不能为bean本身,防止循环引用
|
||||
}
|
||||
|
||||
|
@ -65,7 +65,9 @@ public class BeanConverter<T> extends AbstractConverter<T> {
|
||||
|
||||
@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);
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
* 反序列化<br>
|
||||
* 对象必须实现Serializable接口
|
||||
*
|
||||
* <p>
|
||||
* 注意!!! 此方法不会检查反序列化安全,可能存在反序列化漏洞风险!!!
|
||||
* </p>
|
||||
*
|
||||
* @param <T> 对象类型
|
||||
* @param bytes 反序列化的字节码
|
||||
* @return 反序列化后的对象
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> 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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -43,6 +43,10 @@ public enum ContentType {
|
||||
|
||||
private final String value;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param value ContentType值
|
||||
*/
|
||||
ContentType(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
@ -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请求类<br>
|
||||
@ -46,12 +42,7 @@ import java.util.Map.Entry;
|
||||
*/
|
||||
public class HttpRequest extends HttpBase<HttpRequest> {
|
||||
|
||||
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<HttpRequest> {
|
||||
*/
|
||||
private Map<String, Object> form;
|
||||
/**
|
||||
* 文件表单对象,用于文件上传
|
||||
* 是否为Multipart表单
|
||||
*/
|
||||
private Map<String, Resource> fileForm;
|
||||
private boolean isMultiPart;
|
||||
/**
|
||||
* Cookie
|
||||
*/
|
||||
@ -492,17 +483,17 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
||||
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<HttpRequest> {
|
||||
strValue = Convert.toStr(value, null);
|
||||
}
|
||||
|
||||
form.put(name, strValue);
|
||||
return this;
|
||||
return putToForm(name, strValue);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -531,8 +521,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
||||
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<HttpRequest> {
|
||||
*/
|
||||
public HttpRequest form(Map<String, Object> formMap) {
|
||||
if (MapUtil.isNotEmpty(formMap)) {
|
||||
for (Map.Entry<String, Object> entry : formMap.entrySet()) {
|
||||
form(entry.getKey(), entry.getValue());
|
||||
}
|
||||
formMap.forEach(this::form);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@ -557,10 +544,13 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
||||
* 一旦有文件加入,表单变为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<HttpRequest> {
|
||||
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<HttpRequest> {
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public Map<String, Resource> fileForm() {
|
||||
return this.fileForm;
|
||||
final Map<String, Resource> 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<HttpRequest> {
|
||||
|| 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<HttpRequest> {
|
||||
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<String, Object> 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<String, Resource> 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<HttpRequest> {
|
||||
|| Method.OPTIONS == this.method //
|
||||
|| Method.TRACE == this.method;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为multipart/form-data表单,条件如下:
|
||||
*
|
||||
* <pre>
|
||||
* 1. 存在资源对象(fileForm非空)
|
||||
* 2. 用户自定义头为multipart/form-data开头
|
||||
* </pre>
|
||||
* @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
|
||||
|
||||
}
|
||||
|
154
hutool-http/src/main/java/cn/hutool/http/body/MultipartBody.java
Normal file
154
hutool-http/src/main/java/cn/hutool/http/body/MultipartBody.java
Normal file
@ -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<String, Object> form;
|
||||
/**
|
||||
* 编码
|
||||
*/
|
||||
private final Charset charset;
|
||||
|
||||
/**
|
||||
* 根据已有表单内容,构建MultipartBody
|
||||
* @param form 表单
|
||||
* @param charset 编码
|
||||
* @return MultipartBody
|
||||
*/
|
||||
public static MultipartBody create(Map<String, Object> 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<String, Object> 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<String, Object> 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);
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package cn.hutool.http.body;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* 定义请求体接口
|
||||
*/
|
||||
public interface RequestBody {
|
||||
|
||||
/**
|
||||
* 写出数据,不关闭流
|
||||
*
|
||||
* @param out out流
|
||||
*/
|
||||
void write(OutputStream out);
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* 请求体封装实现
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.http.body;
|
@ -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();
|
||||
|
@ -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
|
||||
|
40
hutool-json/src/test/java/cn/hutool/json/IssueI1H2VN.java
Normal file
40
hutool-json/src/test/java/cn/hutool/json/IssueI1H2VN.java
Normal file
@ -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<ConditionVo> conditionsVo;
|
||||
private QueryVo queryVo;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user