mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
add HttpResource
This commit is contained in:
parent
8fda1b9ac5
commit
f9e5625744
@ -3,10 +3,11 @@
|
|||||||
|
|
||||||
-------------------------------------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
# 5.7.17 (2021-11-10)
|
# 5.7.17 (2021-11-11)
|
||||||
|
|
||||||
### 🐣新特性
|
### 🐣新特性
|
||||||
* 【core 】 增加AsyncUtil(pr#457@Gitee)
|
* 【core 】 增加AsyncUtil(pr#457@Gitee)
|
||||||
|
* 【http 】 增加HttpResource(issue#1943@Github)
|
||||||
### 🐞Bug修复
|
### 🐞Bug修复
|
||||||
* 【core 】 修复FileResource构造fileName参数无效问题(issue#1942@Github)
|
* 【core 】 修复FileResource构造fileName参数无效问题(issue#1942@Github)
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ import java.io.PushbackInputStream;
|
|||||||
import java.io.PushbackReader;
|
import java.io.PushbackReader;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
import java.nio.CharBuffer;
|
import java.nio.CharBuffer;
|
||||||
import java.nio.channels.FileChannel;
|
import java.nio.channels.FileChannel;
|
||||||
@ -622,6 +623,9 @@ public class IoUtil extends NioUtil {
|
|||||||
if (in == null) {
|
if (in == null) {
|
||||||
throw new IllegalArgumentException("The InputStream must not be null");
|
throw new IllegalArgumentException("The InputStream must not be null");
|
||||||
}
|
}
|
||||||
|
if(null != clazz){
|
||||||
|
in.accept(clazz);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
return (T) in.readObject();
|
return (T) in.readObject();
|
||||||
@ -1331,4 +1335,19 @@ public class IoUtil extends NioUtil {
|
|||||||
public static LineIter lineIter(InputStream in, Charset charset) {
|
public static LineIter lineIter(InputStream in, Charset charset) {
|
||||||
return new LineIter(in, charset);
|
return new LineIter(in, charset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ByteArrayOutputStream} 转换为String
|
||||||
|
* @param out {@link ByteArrayOutputStream}
|
||||||
|
* @param charset 编码
|
||||||
|
* @return 字符串
|
||||||
|
* @since 5.7.17
|
||||||
|
*/
|
||||||
|
public static String toStr(ByteArrayOutputStream out, Charset charset){
|
||||||
|
try {
|
||||||
|
return out.toString(charset.name());
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new IORuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package cn.hutool.core.io.resource;
|
|||||||
import cn.hutool.core.io.IORuntimeException;
|
import cn.hutool.core.io.IORuntimeException;
|
||||||
import cn.hutool.core.io.IoUtil;
|
import cn.hutool.core.io.IoUtil;
|
||||||
import cn.hutool.core.util.CharsetUtil;
|
import cn.hutool.core.util.CharsetUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
@ -59,7 +60,7 @@ public class CharSequenceResource implements Resource, Serializable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return this.name.toString();
|
return StrUtil.str(this.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -39,7 +39,11 @@ public enum ContentType {
|
|||||||
/**
|
/**
|
||||||
* text/html编码
|
* text/html编码
|
||||||
*/
|
*/
|
||||||
TEXT_HTML("text/html");
|
TEXT_HTML("text/html"),
|
||||||
|
/**
|
||||||
|
* application/octet-stream编码
|
||||||
|
*/
|
||||||
|
OCTET_STREAM("application/octet-stream");
|
||||||
|
|
||||||
private final String value;
|
private final String value;
|
||||||
|
|
||||||
|
56
hutool-http/src/main/java/cn/hutool/http/HttpResource.java
Normal file
56
hutool-http/src/main/java/cn/hutool/http/HttpResource.java
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package cn.hutool.http;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.resource.Resource;
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP资源,可自定义Content-Type
|
||||||
|
*
|
||||||
|
* @author looly
|
||||||
|
* @since 5.7.17
|
||||||
|
*/
|
||||||
|
public class HttpResource implements Resource, Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final Resource resource;
|
||||||
|
private final String contentType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*
|
||||||
|
* @param resource 资源,非空
|
||||||
|
* @param contentType Content-Type类型,{@code null}表示不设置
|
||||||
|
*/
|
||||||
|
public HttpResource(Resource resource, String contentType) {
|
||||||
|
this.resource = Assert.notNull(resource, "Resource must be not null !");
|
||||||
|
this.contentType = contentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return resource.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URL getUrl() {
|
||||||
|
return resource.getUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getStream() {
|
||||||
|
return resource.getStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取自定义Content-Type类型
|
||||||
|
*
|
||||||
|
* @return Content-Type类型
|
||||||
|
*/
|
||||||
|
public String getContentType() {
|
||||||
|
return this.contentType;
|
||||||
|
}
|
||||||
|
}
|
@ -5,31 +5,33 @@ import cn.hutool.core.io.IoUtil;
|
|||||||
import cn.hutool.core.io.resource.MultiResource;
|
import cn.hutool.core.io.resource.MultiResource;
|
||||||
import cn.hutool.core.io.resource.Resource;
|
import cn.hutool.core.io.resource.Resource;
|
||||||
import cn.hutool.core.map.MapUtil;
|
import cn.hutool.core.map.MapUtil;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
|
||||||
import cn.hutool.core.util.RandomUtil;
|
import cn.hutool.core.util.RandomUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.http.ContentType;
|
import cn.hutool.http.ContentType;
|
||||||
|
import cn.hutool.http.HttpResource;
|
||||||
import cn.hutool.http.HttpUtil;
|
import cn.hutool.http.HttpUtil;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Multipart/form-data数据的请求体封装
|
* Multipart/form-data数据的请求体封装<br>
|
||||||
|
* 遵循RFC2388规范
|
||||||
*
|
*
|
||||||
* @author looly
|
* @author looly
|
||||||
* @since 5.3.5
|
* @since 5.3.5
|
||||||
*/
|
*/
|
||||||
public class MultipartBody implements RequestBody{
|
public class MultipartBody implements RequestBody {
|
||||||
|
|
||||||
private static final String BOUNDARY = "--------------------Hutool_" + RandomUtil.randomString(16);
|
private static final String BOUNDARY = "--------------------Hutool_" + RandomUtil.randomString(16);
|
||||||
private static final String BOUNDARY_END = StrUtil.format("--{}--\r\n", BOUNDARY);
|
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_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"\r\n";
|
||||||
private static final String CONTENT_DISPOSITION_FILE_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"\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_MULTIPART_PREFIX = ContentType.MULTIPART.getValue() + "; boundary=";
|
||||||
private static final String CONTENT_TYPE_FILE_TEMPLATE = "Content-Type: {}\r\n\r\n";
|
private static final String CONTENT_TYPE_FILE_TEMPLATE = "Content-Type: {}\r\n";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 存储表单数据
|
* 存储表单数据
|
||||||
@ -42,11 +44,12 @@ public class MultipartBody implements RequestBody{
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据已有表单内容,构建MultipartBody
|
* 根据已有表单内容,构建MultipartBody
|
||||||
* @param form 表单
|
*
|
||||||
|
* @param form 表单
|
||||||
* @param charset 编码
|
* @param charset 编码
|
||||||
* @return MultipartBody
|
* @return MultipartBody
|
||||||
*/
|
*/
|
||||||
public static MultipartBody create(Map<String, Object> form, Charset charset){
|
public static MultipartBody create(Map<String, Object> form, Charset charset) {
|
||||||
return new MultipartBody(form, charset);
|
return new MultipartBody(form, charset);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,15 +58,15 @@ public class MultipartBody implements RequestBody{
|
|||||||
*
|
*
|
||||||
* @return Multipart的Content-Type类型
|
* @return Multipart的Content-Type类型
|
||||||
*/
|
*/
|
||||||
public static String getContentType(){
|
public static String getContentType() {
|
||||||
return CONTENT_TYPE_MULTIPART_PREFIX + BOUNDARY;
|
return CONTENT_TYPE_MULTIPART_PREFIX + BOUNDARY;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造
|
* 构造
|
||||||
*
|
*
|
||||||
* @param form 表单
|
* @param form 表单
|
||||||
* @param charset 编码
|
* @param charset 编码
|
||||||
*/
|
*/
|
||||||
public MultipartBody(Map<String, Object> form, Charset charset) {
|
public MultipartBody(Map<String, Object> form, Charset charset) {
|
||||||
this.form = form;
|
this.form = form;
|
||||||
@ -81,6 +84,13 @@ public class MultipartBody implements RequestBody{
|
|||||||
formEnd(out);
|
formEnd(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
write(out);
|
||||||
|
return IoUtil.toStr(out, this.charset);
|
||||||
|
}
|
||||||
|
|
||||||
// 普通字符串数据
|
// 普通字符串数据
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -97,10 +107,26 @@ public class MultipartBody implements RequestBody{
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 添加Multipart表单的数据项
|
* 添加Multipart表单的数据项<br>
|
||||||
|
* <pre>
|
||||||
|
* --分隔符(boundary)[换行]
|
||||||
|
* Content-Disposition: form-data; name="参数名"[换行]
|
||||||
|
* [换行]
|
||||||
|
* 参数值[换行]
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* 或者:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* --分隔符(boundary)[换行]
|
||||||
|
* Content-Disposition: form-data; name="表单名"; filename="文件名"[换行]
|
||||||
|
* Content-Type: MIME类型[换行]
|
||||||
|
* [换行]
|
||||||
|
* 文件的二进制内容[换行]
|
||||||
|
* </pre>
|
||||||
*
|
*
|
||||||
* @param formFieldName 表单名
|
* @param formFieldName 表单名
|
||||||
* @param value 值,可以是普通值、资源(如文件等)
|
* @param value 值,可以是普通值、资源(如文件等)
|
||||||
* @param out Http流
|
* @param out Http流
|
||||||
* @throws IORuntimeException IO异常
|
* @throws IORuntimeException IO异常
|
||||||
*/
|
*/
|
||||||
@ -113,25 +139,63 @@ public class MultipartBody implements RequestBody{
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --分隔符(boundary)[换行]
|
||||||
write(out, "--", BOUNDARY, StrUtil.CRLF);
|
write(out, "--", BOUNDARY, StrUtil.CRLF);
|
||||||
|
|
||||||
if(value instanceof Resource){
|
if (value instanceof Resource) {
|
||||||
// 文件资源(二进制资源)
|
appendResource(formFieldName, (Resource) value, out);
|
||||||
final Resource resource = (Resource)value;
|
} else {
|
||||||
final String fileName = resource.getName();
|
/*
|
||||||
write(out, StrUtil.format(CONTENT_DISPOSITION_FILE_TEMPLATE, formFieldName, ObjectUtil.defaultIfNull(fileName, formFieldName)));
|
* Content-Disposition: form-data; name="参数名"[换行]
|
||||||
// 根据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, StrUtil.format(CONTENT_DISPOSITION_TEMPLATE, formFieldName));
|
||||||
|
write(out, StrUtil.CRLF);
|
||||||
write(out, value);
|
write(out, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
write(out, StrUtil.CRLF);
|
write(out, StrUtil.CRLF);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加Multipart表单的Resource数据项,支持包括{@link HttpResource}资源格式
|
||||||
|
*
|
||||||
|
* @param formFieldName 表单名
|
||||||
|
* @param resource 资源
|
||||||
|
* @param out Http流
|
||||||
|
* @throws IORuntimeException IO异常
|
||||||
|
*/
|
||||||
|
private void appendResource(String formFieldName, Resource resource, OutputStream out) throws IORuntimeException {
|
||||||
|
final String fileName = resource.getName();
|
||||||
|
|
||||||
|
// Content-Disposition
|
||||||
|
if (null == fileName) {
|
||||||
|
// Content-Disposition: form-data; name="参数名"[换行]
|
||||||
|
write(out, StrUtil.format(CONTENT_DISPOSITION_TEMPLATE, formFieldName));
|
||||||
|
} else {
|
||||||
|
// Content-Disposition: form-data; name="参数名"; filename="文件名"[换行]
|
||||||
|
write(out, StrUtil.format(CONTENT_DISPOSITION_FILE_TEMPLATE, formFieldName, fileName));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content-Type
|
||||||
|
if (resource instanceof HttpResource) {
|
||||||
|
final String contentType = ((HttpResource) resource).getContentType();
|
||||||
|
if (StrUtil.isNotBlank(contentType)) {
|
||||||
|
// Content-Type: 类型[换行]
|
||||||
|
write(out, StrUtil.format(CONTENT_TYPE_FILE_TEMPLATE, contentType));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 根据name的扩展名指定互联网媒体类型,默认二进制流数据
|
||||||
|
write(out, StrUtil.format(CONTENT_TYPE_FILE_TEMPLATE,
|
||||||
|
HttpUtil.getMimeType(fileName, ContentType.OCTET_STREAM.getValue())));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内容
|
||||||
|
write(out, "\r\n");
|
||||||
|
resource.writeTo(out);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上传表单结束
|
* 上传表单结束
|
||||||
*
|
*
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
package cn.hutool.http.body;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.resource.StringResource;
|
||||||
|
import cn.hutool.core.util.CharsetUtil;
|
||||||
|
import cn.hutool.http.HttpResource;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class MultipartBodyTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void buildTest(){
|
||||||
|
Map<String, Object> form = new HashMap<>();
|
||||||
|
form.put("pic1", "pic1 content");
|
||||||
|
form.put("pic2", new HttpResource(
|
||||||
|
new StringResource("pic2 content"), "text/plain"));
|
||||||
|
form.put("pic3", new HttpResource(
|
||||||
|
new StringResource("pic3 content", "pic3.jpg"), "image/jpeg"));
|
||||||
|
|
||||||
|
final MultipartBody body = MultipartBody.create(form, CharsetUtil.CHARSET_UTF_8);
|
||||||
|
|
||||||
|
Assert.assertNotNull(body.toString());
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user