|
|
@@ -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,108 +1141,67 @@ 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 输出流
|
|
|
+ * 设置表单类型为Multipart(文件上传)
|
|
|
*/
|
|
|
- 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);
|
|
|
- }
|
|
|
+ private void setMultipart() {
|
|
|
+ this.httpConnection.header(Header.CONTENT_TYPE, MultipartBody.getContentType(), true);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 发送文件对象表单
|
|
|
+ * 是否忽略读取响应body部分<br>
|
|
|
+ * HEAD、CONNECT、OPTIONS、TRACE方法将不读取响应体
|
|
|
*
|
|
|
- * @param out 输出流
|
|
|
+ * @return 是否需要忽略响应body部分
|
|
|
+ * @since 3.1.2
|
|
|
*/
|
|
|
- private void writeFileForm(OutputStream out) {
|
|
|
- for (Entry<String, Resource> entry : this.fileForm.entrySet()) {
|
|
|
- appendPart(entry.getKey(), entry.getValue(), out);
|
|
|
- }
|
|
|
+ private boolean isIgnoreResponseBody() {
|
|
|
+ return Method.HEAD == this.method //
|
|
|
+ || Method.CONNECT == this.method //
|
|
|
+ || Method.OPTIONS == this.method //
|
|
|
+ || Method.TRACE == this.method;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 添加Multipart表单的数据项
|
|
|
+ * 判断是否为multipart/form-data表单,条件如下:
|
|
|
*
|
|
|
- * @param formFieldName 表单名
|
|
|
- * @param resource 资源,可以是文件等
|
|
|
- * @param out Http流
|
|
|
- * @since 4.1.0
|
|
|
+ * <pre>
|
|
|
+ * 1. 存在资源对象(fileForm非空)
|
|
|
+ * 2. 用户自定义头为multipart/form-data开头
|
|
|
+ * </pre>
|
|
|
+ * @return 是否为multipart/form-data表单
|
|
|
+ * @since 5.3.5
|
|
|
*/
|
|
|
- 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);
|
|
|
+ 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 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);
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 是否忽略读取响应body部分<br>
|
|
|
- * HEAD、CONNECT、OPTIONS、TRACE方法将不读取响应体
|
|
|
- *
|
|
|
- * @return 是否需要忽略响应body部分
|
|
|
- * @since 3.1.2
|
|
|
+ * @param name 表单属性名
|
|
|
+ * @param value 属性值
|
|
|
+ * @return this
|
|
|
*/
|
|
|
- private boolean isIgnoreResponseBody() {
|
|
|
- return Method.HEAD == this.method //
|
|
|
- || Method.CONNECT == this.method //
|
|
|
- || Method.OPTIONS == this.method //
|
|
|
- || Method.TRACE == this.method;
|
|
|
+ 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
|
|
|
|