add HttpResource

This commit is contained in:
Looly 2021-11-11 01:19:53 +08:00
parent 8fda1b9ac5
commit f9e5625744
7 changed files with 197 additions and 25 deletions

View File

@ -3,10 +3,11 @@
-------------------------------------------------------------------------------------------------------------
# 5.7.17 (2021-11-10)
# 5.7.17 (2021-11-11)
### 🐣新特性
* 【core 】 增加AsyncUtilpr#457@Gitee
* 【http 】 增加HttpResourceissue#1943@Github
### 🐞Bug修复
* 【core 】 修复FileResource构造fileName参数无效问题issue#1942@Github

View File

@ -32,6 +32,7 @@ import java.io.PushbackInputStream;
import java.io.PushbackReader;
import java.io.Reader;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
@ -622,6 +623,9 @@ public class IoUtil extends NioUtil {
if (in == null) {
throw new IllegalArgumentException("The InputStream must not be null");
}
if(null != clazz){
in.accept(clazz);
}
try {
//noinspection unchecked
return (T) in.readObject();
@ -1331,4 +1335,19 @@ public class IoUtil extends NioUtil {
public static LineIter lineIter(InputStream in, Charset 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);
}
}
}

View File

@ -3,6 +3,7 @@ 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 cn.hutool.core.util.StrUtil;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
@ -59,7 +60,7 @@ public class CharSequenceResource implements Resource, Serializable {
@Override
public String getName() {
return this.name.toString();
return StrUtil.str(this.name);
}
@Override

View File

@ -39,7 +39,11 @@ public enum ContentType {
/**
* text/html编码
*/
TEXT_HTML("text/html");
TEXT_HTML("text/html"),
/**
* application/octet-stream编码
*/
OCTET_STREAM("application/octet-stream");
private final String value;

View 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;
}
}

View File

@ -5,31 +5,33 @@ 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.HttpResource;
import cn.hutool.http.HttpUtil;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Map;
/**
* Multipart/form-data数据的请求体封装
* Multipart/form-data数据的请求体封装<br>
* 遵循RFC2388规范
*
* @author looly
* @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_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_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
* @param form 表单
*
* @param form 表单
* @param charset 编码
* @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);
}
@ -55,15 +58,15 @@ public class MultipartBody implements RequestBody{
*
* @return Multipart的Content-Type类型
*/
public static String getContentType(){
public static String getContentType() {
return CONTENT_TYPE_MULTIPART_PREFIX + BOUNDARY;
}
/**
* 构造
*
* @param form 表单
* @param charset 编码
* @param form 表单
* @param charset 编码
*/
public MultipartBody(Map<String, Object> form, Charset charset) {
this.form = form;
@ -81,6 +84,13 @@ public class MultipartBody implements RequestBody{
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 value 可以是普通值资源如文件等
* @param value 可以是普通值资源如文件等
* @param out Http流
* @throws IORuntimeException IO异常
*/
@ -113,25 +139,63 @@ public class MultipartBody implements RequestBody{
return;
}
// --分隔符(boundary)[换行]
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{
// 普通数据
if (value instanceof Resource) {
appendResource(formFieldName, (Resource) value, out);
} else {
/*
* Content-Disposition: form-data; name="参数名"[换行]
* [换行]
* 参数值
*/
write(out, StrUtil.format(CONTENT_DISPOSITION_TEMPLATE, formFieldName));
write(out, StrUtil.CRLF);
write(out, value);
}
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);
}
/**
* 上传表单结束
*

View File

@ -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());
}
}