mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-08-18 20:38:02 +08:00
Merge branch 'v5-dev' into v5-dev
This commit is contained in:
@@ -28,20 +28,42 @@ public enum ContentType {
|
||||
* Rest请求XML编码
|
||||
*/
|
||||
XML("application/xml"),
|
||||
/**
|
||||
* text/plain编码
|
||||
*/
|
||||
TEXT_PLAIN("text/plain"),
|
||||
/**
|
||||
* Rest请求text/xml编码
|
||||
*/
|
||||
TEXT_XML("text/xml");
|
||||
TEXT_XML("text/xml"),
|
||||
/**
|
||||
* text/html编码
|
||||
*/
|
||||
TEXT_HTML("text/html");
|
||||
|
||||
private String value;
|
||||
private final String value;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* @param value ContentType值
|
||||
*/
|
||||
ContentType(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取value值
|
||||
*
|
||||
* @return value值
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value;
|
||||
return getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,7 +84,7 @@ public enum ContentType {
|
||||
* @since 4.1.5
|
||||
*/
|
||||
public static boolean isDefault(String contentType) {
|
||||
return null == contentType || isFormUrlEncoed(contentType);
|
||||
return null == contentType || isFormUrlEncode(contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,7 +93,7 @@ public enum ContentType {
|
||||
* @param contentType 内容类型
|
||||
* @return 是否为application/x-www-form-urlencoded
|
||||
*/
|
||||
public static boolean isFormUrlEncoed(String contentType) {
|
||||
public static boolean isFormUrlEncode(String contentType) {
|
||||
return StrUtil.startWithIgnoreCase(contentType, FORM_URLENCODED.toString());
|
||||
}
|
||||
|
||||
|
@@ -121,14 +121,23 @@ public enum Header {
|
||||
*/
|
||||
LOCATION("Location");
|
||||
|
||||
private String value;
|
||||
private final String value;
|
||||
|
||||
Header(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取值
|
||||
*
|
||||
* @return 值
|
||||
*/
|
||||
public String getValue(){
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value;
|
||||
return getValue();
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,11 @@
|
||||
package cn.hutool.http;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.map.CaseInsensitiveMap;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -8,11 +14,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.map.CaseInsensitiveMap;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* http基类
|
||||
* @author Looly
|
||||
@@ -270,7 +271,7 @@ public abstract class HttpBase<T> {
|
||||
*/
|
||||
public T charset(String charset) {
|
||||
if(StrUtil.isNotBlank(charset)){
|
||||
this.charset = Charset.forName(charset);
|
||||
charset(Charset.forName(charset));
|
||||
}
|
||||
return (T) this;
|
||||
}
|
||||
@@ -293,7 +294,9 @@ public abstract class HttpBase<T> {
|
||||
StringBuilder sb = StrUtil.builder();
|
||||
sb.append("Request Headers: ").append(StrUtil.CRLF);
|
||||
for (Entry<String, List<String>> entry : this.headers.entrySet()) {
|
||||
sb.append(" ").append(entry).append(StrUtil.CRLF);
|
||||
sb.append(" ").append(
|
||||
entry.getKey()).append(": ").append(CollUtil.join(entry.getValue(), ","))
|
||||
.append(StrUtil.CRLF);
|
||||
}
|
||||
|
||||
sb.append("Request Body: ").append(StrUtil.CRLF);
|
||||
|
@@ -33,8 +33,8 @@ import java.util.Map.Entry;
|
||||
*/
|
||||
public class HttpConnection {
|
||||
|
||||
private URL url;
|
||||
private Proxy proxy;
|
||||
private final URL url;
|
||||
private final Proxy proxy;
|
||||
private HttpURLConnection conn;
|
||||
|
||||
/**
|
||||
|
@@ -1,5 +1,7 @@
|
||||
package cn.hutool.http;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
@@ -8,8 +10,6 @@ import java.util.zip.GZIPInputStream;
|
||||
import java.util.zip.Inflater;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
/**
|
||||
* HTTP输入流,此流用于包装Http请求响应内容的流,用于解析各种压缩、分段的响应流内容
|
||||
*
|
||||
@@ -79,11 +79,10 @@ public class HttpInputStream extends InputStream {
|
||||
try {
|
||||
this.in = (response.status < HttpStatus.HTTP_BAD_REQUEST) ? response.httpConnection.getInputStream() : response.httpConnection.getErrorStream();
|
||||
} catch (IOException e) {
|
||||
if (e instanceof FileNotFoundException) {
|
||||
// 服务器无返回内容,忽略之
|
||||
} else {
|
||||
if (false == (e instanceof FileNotFoundException)) {
|
||||
throw new HttpException(e);
|
||||
}
|
||||
// 服务器无返回内容,忽略之
|
||||
}
|
||||
|
||||
// 在一些情况下,返回的流为null,此时提供状态码说明
|
||||
|
@@ -1,22 +1,22 @@
|
||||
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;
|
||||
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.core.util.URLUtil;
|
||||
import cn.hutool.http.body.MultipartBody;
|
||||
import cn.hutool.http.cookie.GlobalCookieManager;
|
||||
import cn.hutool.http.ssl.SSLSocketFactoryBuilder;
|
||||
|
||||
@@ -24,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>
|
||||
@@ -45,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";
|
||||
|
||||
/**
|
||||
@@ -96,7 +88,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
||||
GlobalCookieManager.setCookieManager(null);
|
||||
}
|
||||
|
||||
private String url;
|
||||
private UrlBuilder url;
|
||||
private URLStreamHandler urlHandler;
|
||||
private Method method = Method.GET;
|
||||
/**
|
||||
@@ -112,9 +104,9 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
||||
*/
|
||||
private Map<String, Object> form;
|
||||
/**
|
||||
* 文件表单对象,用于文件上传
|
||||
* 是否为Multipart表单
|
||||
*/
|
||||
private Map<String, Resource> fileForm;
|
||||
private boolean isMultiPart;
|
||||
/**
|
||||
* Cookie
|
||||
*/
|
||||
@@ -128,10 +120,6 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
||||
* 是否禁用缓存
|
||||
*/
|
||||
private boolean isDisableCache;
|
||||
/**
|
||||
* 是否对url中的参数进行编码
|
||||
*/
|
||||
private boolean encodeUrlParams;
|
||||
/**
|
||||
* 是否是REST请求模式
|
||||
*/
|
||||
@@ -163,13 +151,21 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
||||
private SSLSocketFactory ssf;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
* 构造,URL编码默认使用UTF-8
|
||||
*
|
||||
* @param url URL
|
||||
*/
|
||||
public HttpRequest(String url) {
|
||||
Assert.notBlank(url, "Param [url] can not be blank !");
|
||||
this.url = URLUtil.normalize(url, true);
|
||||
this(UrlBuilder.ofHttp(url, CharsetUtil.CHARSET_UTF_8));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param url {@link UrlBuilder}
|
||||
*/
|
||||
public HttpRequest(UrlBuilder url) {
|
||||
this.url = url;
|
||||
// 给定一个默认头信息
|
||||
this.header(GlobalHeaders.INSTANCE.headers);
|
||||
}
|
||||
@@ -265,7 +261,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
||||
* @since 4.1.8
|
||||
*/
|
||||
public String getUrl() {
|
||||
return url;
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -276,7 +272,19 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
||||
* @since 4.1.8
|
||||
*/
|
||||
public HttpRequest setUrl(String url) {
|
||||
this.url = url;
|
||||
this.url = UrlBuilder.ofHttp(url, this.charset);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置URL
|
||||
*
|
||||
* @param urlBuilder url字符串
|
||||
* @return this
|
||||
* @since 5.3.1
|
||||
*/
|
||||
public HttpRequest setUrl(UrlBuilder urlBuilder) {
|
||||
this.url = urlBuilder;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -475,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)) {
|
||||
// 多文件
|
||||
@@ -498,8 +506,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
||||
strValue = Convert.toStr(value, null);
|
||||
}
|
||||
|
||||
form.put(name, strValue);
|
||||
return this;
|
||||
return putToForm(name, strValue);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -514,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;
|
||||
}
|
||||
@@ -528,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;
|
||||
}
|
||||
@@ -540,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());
|
||||
@@ -611,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;
|
||||
}
|
||||
@@ -636,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
|
||||
|
||||
@@ -774,9 +784,10 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
||||
* @param isEncodeUrlParams 是否对URL中的参数进行编码
|
||||
* @return this
|
||||
* @since 4.4.1
|
||||
* @deprecated 编码自动完成,无需设置
|
||||
*/
|
||||
@Deprecated
|
||||
public HttpRequest setEncodeUrlParams(boolean isEncodeUrlParams) {
|
||||
this.encodeUrlParams = isEncodeUrlParams;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -860,7 +871,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
||||
public HttpRequest setSSLProtocol(String protocol) {
|
||||
Assert.notBlank(protocol, "protocol must be not blank!");
|
||||
try {
|
||||
this.ssf = SSLSocketFactoryBuilder.create().setProtocol(protocol).build();
|
||||
setSSLSocketFactory(SSLSocketFactoryBuilder.create().setProtocol(protocol).build());
|
||||
} catch (Exception e) {
|
||||
throw new HttpException(e);
|
||||
}
|
||||
@@ -925,14 +936,8 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
||||
public HttpResponse execute(boolean isAsync) {
|
||||
// 初始化URL
|
||||
urlWithParamIfGet();
|
||||
// 编码URL
|
||||
if (this.encodeUrlParams) {
|
||||
this.url = HttpUtil.encodeParams(this.url, this.charset);
|
||||
}
|
||||
|
||||
// 初始化 connection
|
||||
initConnection();
|
||||
|
||||
// 发送请求
|
||||
send();
|
||||
|
||||
@@ -971,6 +976,15 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
||||
header(Header.AUTHORIZATION, content, true);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = StrUtil.builder();
|
||||
sb.append("Request Url: ").append(this.url).append(StrUtil.CRLF);
|
||||
sb.append(super.toString());
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- Private method start
|
||||
|
||||
/**
|
||||
@@ -982,7 +996,8 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
||||
this.httpConnection.disconnectQuietly();
|
||||
}
|
||||
|
||||
this.httpConnection = HttpConnection.create(URLUtil.toUrlForHttp(this.url, this.urlHandler), this.proxy)//
|
||||
this.httpConnection = HttpConnection
|
||||
.create(this.url.toURL(this.urlHandler), this.proxy)//
|
||||
.setMethod(this.method)//
|
||||
.setHttpsInfo(this.hostnameVerifier, this.ssf)//
|
||||
.setConnectTimeout(this.connectionTimeout)//
|
||||
@@ -1016,9 +1031,9 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
||||
if (Method.GET.equals(method) && false == this.isRest) {
|
||||
// 优先使用body形式的参数,不存在使用form
|
||||
if (ArrayUtil.isNotEmpty(this.bodyBytes)) {
|
||||
this.url = HttpUtil.urlWithForm(this.url, StrUtil.str(this.bodyBytes, this.charset), this.charset, false);
|
||||
this.url.getQuery().parse(StrUtil.str(this.bodyBytes, this.charset), this.charset);
|
||||
} else {
|
||||
this.url = HttpUtil.urlWithForm(this.url, this.form, this.charset, false);
|
||||
this.url.getQuery().addAll(this.form);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1047,7 +1062,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
||||
|
||||
if (responseCode != HttpURLConnection.HTTP_OK) {
|
||||
if (responseCode == HttpURLConnection.HTTP_MOVED_TEMP || responseCode == HttpURLConnection.HTTP_MOVED_PERM || responseCode == HttpURLConnection.HTTP_SEE_OTHER) {
|
||||
this.url = httpConnection.header(Header.LOCATION);
|
||||
setUrl(httpConnection.header(Header.LOCATION));
|
||||
if (redirectCount < this.maxRedirectCount) {
|
||||
redirectCount++;
|
||||
return execute();
|
||||
@@ -1069,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();
|
||||
@@ -1097,12 +1112,23 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
||||
}
|
||||
|
||||
// Write的时候会优先使用body中的内容,write时自动关闭OutputStream
|
||||
if (ArrayUtil.isNotEmpty(this.bodyBytes)) {
|
||||
IoUtil.write(this.httpConnection.getOutputStream(), true, this.bodyBytes);
|
||||
} else {
|
||||
final String content = HttpUtil.toParams(this.form, this.charset);
|
||||
IoUtil.write(this.httpConnection.getOutputStream(), this.charset, true, content);
|
||||
byte[] content;
|
||||
if(ArrayUtil.isNotEmpty(this.bodyBytes)){
|
||||
content = this.bodyBytes;
|
||||
} else{
|
||||
content = StrUtil.bytes(getFormUrlEncoded(), this.charset);
|
||||
}
|
||||
IoUtil.write(this.httpConnection.getOutputStream(), true, content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取编码后的表单数据,无表单数据返回""
|
||||
*
|
||||
* @return 编码后的表单数据,无表单数据返回""
|
||||
* @since 5.3.2
|
||||
*/
|
||||
private String getFormUrlEncoded() {
|
||||
return HttpUtil.toParams(this.form, this.charset);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1115,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1218,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
|
||||
|
||||
}
|
||||
|
@@ -43,7 +43,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
|
||||
/** 响应状态码 */
|
||||
protected int status;
|
||||
/** 是否忽略读取Http响应体 */
|
||||
private boolean ignoreBody;
|
||||
private final boolean ignoreBody;
|
||||
/** 从响应中获取的编码 */
|
||||
private Charset charsetFromResponse;
|
||||
|
||||
@@ -471,11 +471,11 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
|
||||
*/
|
||||
private String getFileNameFromDisposition() {
|
||||
String fileName = null;
|
||||
final String desposition = header(Header.CONTENT_DISPOSITION);
|
||||
if (StrUtil.isNotBlank(desposition)) {
|
||||
fileName = ReUtil.get("filename=\"(.*?)\"", desposition, 1);
|
||||
final String disposition = header(Header.CONTENT_DISPOSITION);
|
||||
if (StrUtil.isNotBlank(disposition)) {
|
||||
fileName = ReUtil.get("filename=\"(.*?)\"", disposition, 1);
|
||||
if (StrUtil.isBlank(fileName)) {
|
||||
fileName = StrUtil.subAfter(desposition, "filename=", true);
|
||||
fileName = StrUtil.subAfter(disposition, "filename=", true);
|
||||
}
|
||||
}
|
||||
return fileName;
|
||||
|
@@ -1,19 +1,19 @@
|
||||
package cn.hutool.http;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.io.FastByteArrayOutputStream;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.io.StreamProgress;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.net.url.UrlQuery;
|
||||
import cn.hutool.core.text.StrBuilder;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import cn.hutool.http.server.SimpleServer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
@@ -21,13 +21,9 @@ import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
@@ -357,6 +353,7 @@ public class HttpUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
* 下载远程文件数据
|
||||
*
|
||||
* @param url 请求的url
|
||||
@@ -377,7 +374,7 @@ public class HttpUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Map形式的Form表单数据转换为Url参数形式,不做编码
|
||||
* 将Map形式的Form表单数据转换为Url参数形式,会自动url编码键和值
|
||||
*
|
||||
* @param paramMap 表单数据
|
||||
* @return url参数
|
||||
@@ -408,44 +405,11 @@ public class HttpUtil {
|
||||
* </pre>
|
||||
*
|
||||
* @param paramMap 表单数据
|
||||
* @param charset 编码
|
||||
* @param charset 编码,null表示不encode键值对
|
||||
* @return url参数
|
||||
*/
|
||||
public static String toParams(Map<String, ?> paramMap, Charset charset) {
|
||||
if (CollectionUtil.isEmpty(paramMap)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
if (null == charset) {// 默认编码为系统编码
|
||||
charset = CharsetUtil.CHARSET_UTF_8;
|
||||
}
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
boolean isFirst = true;
|
||||
String key;
|
||||
Object value;
|
||||
String valueStr;
|
||||
for (Entry<String, ?> item : paramMap.entrySet()) {
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
} else {
|
||||
sb.append("&");
|
||||
}
|
||||
key = item.getKey();
|
||||
value = item.getValue();
|
||||
if (value instanceof Iterable) {
|
||||
value = CollUtil.join((Iterable<?>) value, ",");
|
||||
} else if (value instanceof Iterator) {
|
||||
value = CollUtil.join((Iterator<?>) value, ",");
|
||||
}
|
||||
valueStr = Convert.toStr(value);
|
||||
if (StrUtil.isNotEmpty(key)) {
|
||||
sb.append(URLUtil.encodeAll(key, charset)).append("=");
|
||||
if (StrUtil.isNotEmpty(valueStr)) {
|
||||
sb.append(URLUtil.encodeAll(valueStr, charset));
|
||||
}
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
return URLUtil.buildQuery(paramMap, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -454,30 +418,33 @@ public class HttpUtil {
|
||||
*
|
||||
* <p>注意,此方法只能标准化整个URL,并不适合于单独编码参数值</p>
|
||||
*
|
||||
* @param paramsStr url参数,可以包含url本身
|
||||
* @param charset 编码
|
||||
* @param urlWithParams url和参数,可以包含url本身,也可以单独参数
|
||||
* @param charset 编码
|
||||
* @return 编码后的url和参数
|
||||
* @since 4.0.1
|
||||
*/
|
||||
public static String encodeParams(String paramsStr, Charset charset) {
|
||||
if (StrUtil.isBlank(paramsStr)) {
|
||||
public static String encodeParams(String urlWithParams, Charset charset) {
|
||||
if (StrUtil.isBlank(urlWithParams)) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
|
||||
String urlPart = null; // url部分,不包括问号
|
||||
String paramPart; // 参数部分
|
||||
int pathEndPos = paramsStr.indexOf('?');
|
||||
final int pathEndPos = urlWithParams.indexOf('?');
|
||||
if (pathEndPos > -1) {
|
||||
// url + 参数
|
||||
urlPart = StrUtil.subPre(paramsStr, pathEndPos);
|
||||
paramPart = StrUtil.subSuf(paramsStr, pathEndPos + 1);
|
||||
urlPart = StrUtil.subPre(urlWithParams, pathEndPos);
|
||||
paramPart = StrUtil.subSuf(urlWithParams, pathEndPos + 1);
|
||||
if (StrUtil.isBlank(paramPart)) {
|
||||
// 无参数,返回url
|
||||
return urlPart;
|
||||
}
|
||||
} else if (false == StrUtil.contains(urlWithParams, '=')) {
|
||||
// 无参数的URL
|
||||
return urlWithParams;
|
||||
} else {
|
||||
// 无URL
|
||||
paramPart = paramsStr;
|
||||
// 无URL的参数
|
||||
paramPart = urlWithParams;
|
||||
}
|
||||
|
||||
paramPart = normalizeParams(paramPart, charset);
|
||||
@@ -551,16 +518,27 @@ public class HttpUtil {
|
||||
* @param charset 字符集
|
||||
* @return 参数Map
|
||||
* @since 4.0.2
|
||||
* @deprecated 请使用 {@link #decodeParamMap(String, Charset)}
|
||||
*/
|
||||
public static HashMap<String, String> decodeParamMap(String paramsStr, String charset) {
|
||||
final Map<String, List<String>> paramsMap = decodeParams(paramsStr, charset);
|
||||
final HashMap<String, String> result = MapUtil.newHashMap(paramsMap.size());
|
||||
List<String> valueList;
|
||||
for (Entry<String, List<String>> entry : paramsMap.entrySet()) {
|
||||
valueList = entry.getValue();
|
||||
result.put(entry.getKey(), CollUtil.isEmpty(valueList) ? null : valueList.get(0));
|
||||
@Deprecated
|
||||
public static Map<String, String> decodeParamMap(String paramsStr, String charset) {
|
||||
return decodeParamMap(paramsStr, CharsetUtil.charset(charset));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将URL参数解析为Map(也可以解析Post中的键值对参数)
|
||||
*
|
||||
* @param paramsStr 参数字符串(或者带参数的Path)
|
||||
* @param charset 字符集
|
||||
* @return 参数Map
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public static Map<String, String> decodeParamMap(String paramsStr, Charset charset) {
|
||||
final Map<CharSequence, CharSequence> queryMap = UrlQuery.of(paramsStr, charset).getQueryMap();
|
||||
if (MapUtil.isEmpty(queryMap)) {
|
||||
return MapUtil.empty();
|
||||
}
|
||||
return result;
|
||||
return Convert.toMap(String.class, String.class, queryMap);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -571,56 +549,29 @@ public class HttpUtil {
|
||||
* @return 参数Map
|
||||
*/
|
||||
public static Map<String, List<String>> decodeParams(String paramsStr, String charset) {
|
||||
if (StrUtil.isBlank(paramsStr)) {
|
||||
return Collections.emptyMap();
|
||||
return decodeParams(paramsStr, CharsetUtil.charset(charset));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将URL参数解析为Map(也可以解析Post中的键值对参数)
|
||||
*
|
||||
* @param paramsStr 参数字符串(或者带参数的Path)
|
||||
* @param charset 字符集
|
||||
* @return 参数Map
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public static Map<String, List<String>> decodeParams(String paramsStr, Charset charset) {
|
||||
final Map<CharSequence, CharSequence> queryMap = UrlQuery.of(paramsStr, charset).getQueryMap();
|
||||
if (MapUtil.isEmpty(queryMap)) {
|
||||
return MapUtil.empty();
|
||||
}
|
||||
|
||||
// 去掉Path部分
|
||||
int pathEndPos = paramsStr.indexOf('?');
|
||||
if (pathEndPos > -1) {
|
||||
paramsStr = StrUtil.subSuf(paramsStr, pathEndPos + 1);
|
||||
if (StrUtil.isBlank(paramsStr)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
|
||||
final int len = paramsStr.length();
|
||||
final Map<String, List<String>> params = new LinkedHashMap<>();
|
||||
String name = null;
|
||||
int pos = 0; // 未处理字符开始位置
|
||||
int i; // 未处理字符结束位置
|
||||
char c; // 当前字符
|
||||
for (i = 0; i < len; i++) {
|
||||
c = paramsStr.charAt(i);
|
||||
if (c == '=') { // 键值对的分界点
|
||||
if (null == name) {
|
||||
// name可以是""
|
||||
name = paramsStr.substring(pos, i);
|
||||
}
|
||||
pos = i + 1;
|
||||
} else if (c == '&') { // 参数对的分界点
|
||||
if (null == name && pos != i) {
|
||||
// 对于像&a&这类无参数值的字符串,我们将name为a的值设为""
|
||||
addParam(params, paramsStr.substring(pos, i), StrUtil.EMPTY, charset);
|
||||
} else if (name != null) {
|
||||
addParam(params, name, paramsStr.substring(pos, i), charset);
|
||||
name = null;
|
||||
}
|
||||
pos = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理结尾
|
||||
if (pos != i) {
|
||||
if (name == null) {
|
||||
addParam(params, paramsStr.substring(pos, i), StrUtil.EMPTY, charset);
|
||||
} else {
|
||||
addParam(params, name, paramsStr.substring(pos, i), charset);
|
||||
}
|
||||
} else if (name != null) {
|
||||
addParam(params, name, StrUtil.EMPTY, charset);
|
||||
}
|
||||
|
||||
queryMap.forEach((key, value) -> {
|
||||
final List<String> values = params.computeIfAbsent(StrUtil.str(key), k -> new ArrayList<>(1));
|
||||
// 一般是一个参数
|
||||
values.add(StrUtil.str(value));
|
||||
});
|
||||
return params;
|
||||
}
|
||||
|
||||
@@ -696,7 +647,22 @@ public class HttpUtil {
|
||||
if (conn == null) {
|
||||
return null;
|
||||
}
|
||||
return ReUtil.get(CHARSET_PATTERN, conn.getContentType(), 1);
|
||||
return getCharset(conn.getContentType());
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Http连接的头信息中获得字符集<br>
|
||||
* 从ContentType中获取
|
||||
*
|
||||
* @param contentType Content-Type
|
||||
* @return 字符集
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public static String getCharset(String contentType) {
|
||||
if (StrUtil.isBlank(contentType)) {
|
||||
return null;
|
||||
}
|
||||
return ReUtil.get(CHARSET_PATTERN, contentType, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -794,23 +760,15 @@ public class HttpUtil {
|
||||
final ContentType contentType = ContentType.get(body);
|
||||
return (null == contentType) ? null : contentType.toString();
|
||||
}
|
||||
// ----------------------------------------------------------------------------------------- Private method start
|
||||
|
||||
/**
|
||||
* 将键值对加入到值为List类型的Map中
|
||||
* 创建简易的Http服务器
|
||||
*
|
||||
* @param params 参数
|
||||
* @param name key
|
||||
* @param value value
|
||||
* @param charset 编码
|
||||
* @param port 端口
|
||||
* @return {@link SimpleServer}
|
||||
* @since 5.2.6
|
||||
*/
|
||||
private static void addParam(Map<String, List<String>> params, String name, String value, String charset) {
|
||||
name = URLUtil.decode(name, charset);
|
||||
value = URLUtil.decode(value, charset);
|
||||
final List<String> values = params.computeIfAbsent(name, k -> new ArrayList<>(1));
|
||||
// 一般是一个参数
|
||||
values.add(value);
|
||||
public static SimpleServer createServer(int port) {
|
||||
return new SimpleServer(port);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------- Private method start 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;
|
@@ -0,0 +1,37 @@
|
||||
package cn.hutool.http.server;
|
||||
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* HttpServer公用对象,提供HttpExchange包装和公用方法
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public class HttpServerBase {
|
||||
|
||||
final static Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8;
|
||||
|
||||
final HttpExchange httpExchange;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param httpExchange {@link HttpExchange}
|
||||
*/
|
||||
public HttpServerBase(HttpExchange httpExchange) {
|
||||
this.httpExchange = httpExchange;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取{@link HttpExchange}对象
|
||||
*
|
||||
* @return {@link HttpExchange}对象
|
||||
*/
|
||||
public HttpExchange getHttpExchange() {
|
||||
return this.httpExchange;
|
||||
}
|
||||
}
|
@@ -0,0 +1,414 @@
|
||||
package cn.hutool.http.server;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.map.CaseInsensitiveMap;
|
||||
import cn.hutool.core.map.multi.ListValueMap;
|
||||
import cn.hutool.core.net.NetUtil;
|
||||
import cn.hutool.core.net.multipart.MultipartFormData;
|
||||
import cn.hutool.core.net.multipart.UploadSetting;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.Header;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.hutool.http.Method;
|
||||
import cn.hutool.http.useragent.UserAgent;
|
||||
import cn.hutool.http.useragent.UserAgentUtil;
|
||||
import com.sun.net.httpserver.Headers;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpCookie;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Http请求对象,对{@link HttpExchange}封装
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public class HttpServerRequest extends HttpServerBase {
|
||||
|
||||
private Map<String, HttpCookie> cookieCache;
|
||||
private ListValueMap<String, String> paramsCache;
|
||||
private MultipartFormData multipartFormDataCache;
|
||||
private Charset charsetCache;
|
||||
private byte[] bodyCache;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param httpExchange {@link HttpExchange}
|
||||
*/
|
||||
public HttpServerRequest(HttpExchange httpExchange) {
|
||||
super(httpExchange);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得Http Method
|
||||
*
|
||||
* @return Http Method
|
||||
*/
|
||||
public String getMethod() {
|
||||
return this.httpExchange.getRequestMethod();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为GET请求
|
||||
*
|
||||
* @return 是否为GET请求
|
||||
*/
|
||||
public boolean isGetMethod() {
|
||||
return Method.GET.name().equalsIgnoreCase(getMethod());
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为POST请求
|
||||
*
|
||||
* @return 是否为POST请求
|
||||
*/
|
||||
public boolean isPostMethod() {
|
||||
return Method.POST.name().equalsIgnoreCase(getMethod());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得请求URI
|
||||
*
|
||||
* @return 请求URI
|
||||
*/
|
||||
public URI getURI() {
|
||||
return this.httpExchange.getRequestURI();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得请求路径Path
|
||||
*
|
||||
* @return 请求路径
|
||||
*/
|
||||
public String getPath() {
|
||||
return getURI().getPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求参数
|
||||
*
|
||||
* @return 参数字符串
|
||||
*/
|
||||
public String getQuery() {
|
||||
return getURI().getQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得请求header中的信息
|
||||
*
|
||||
* @return header值
|
||||
*/
|
||||
public Headers getHeaders() {
|
||||
return this.httpExchange.getRequestHeaders();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得请求header中的信息
|
||||
*
|
||||
* @param headerKey 头信息的KEY
|
||||
* @return header值
|
||||
*/
|
||||
public String getHeader(Header headerKey) {
|
||||
return getHeader(headerKey.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得请求header中的信息
|
||||
*
|
||||
* @param headerKey 头信息的KEY
|
||||
* @return header值
|
||||
*/
|
||||
public String getHeader(String headerKey) {
|
||||
return getHeaders().getFirst(headerKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得请求header中的信息
|
||||
*
|
||||
* @param headerKey 头信息的KEY
|
||||
* @param charset 字符集
|
||||
* @return header值
|
||||
*/
|
||||
public String getHeader(String headerKey, Charset charset) {
|
||||
final String header = getHeader(headerKey);
|
||||
if (null != header) {
|
||||
return CharsetUtil.convert(header, CharsetUtil.CHARSET_ISO_8859_1, charset);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Content-Type头信息
|
||||
*
|
||||
* @return Content-Type头信息
|
||||
*/
|
||||
public String getContentType() {
|
||||
return getHeader(Header.CONTENT_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取编码,获取失败默认使用UTF-8,获取规则如下:
|
||||
*
|
||||
* <pre>
|
||||
* 1、从Content-Type头中获取编码,类似于:text/html;charset=utf-8
|
||||
* </pre>
|
||||
*
|
||||
* @return 编码,默认UTF-8
|
||||
*/
|
||||
public Charset getCharset() {
|
||||
if(null == this.charsetCache){
|
||||
final String contentType = getContentType();
|
||||
final String charsetStr = HttpUtil.getCharset(contentType);
|
||||
this.charsetCache = CharsetUtil.parse(charsetStr, DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
return this.charsetCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得User-Agent
|
||||
*
|
||||
* @return User-Agent字符串
|
||||
*/
|
||||
public String getUserAgentStr() {
|
||||
return getHeader(Header.USER_AGENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得User-Agent,未识别返回null
|
||||
*
|
||||
* @return User-Agent字符串,未识别返回null
|
||||
*/
|
||||
public UserAgent getUserAgent() {
|
||||
return UserAgentUtil.parse(getUserAgentStr());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得Cookie信息字符串
|
||||
*
|
||||
* @return cookie字符串
|
||||
*/
|
||||
public String getCookiesStr() {
|
||||
return getHeader(Header.COOKIE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得Cookie信息列表
|
||||
*
|
||||
* @return Cookie信息列表
|
||||
*/
|
||||
public Collection<HttpCookie> getCookies() {
|
||||
return getCookieMap().values();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得Cookie信息Map,键为Cookie名,值为HttpCookie对象
|
||||
*
|
||||
* @return Cookie信息Map
|
||||
*/
|
||||
public Map<String, HttpCookie> getCookieMap() {
|
||||
if (null == this.cookieCache) {
|
||||
cookieCache = Collections.unmodifiableMap(CollUtil.toMap(
|
||||
NetUtil.parseCookies(getCookiesStr()),
|
||||
new CaseInsensitiveMap<>(),
|
||||
HttpCookie::getName));
|
||||
}
|
||||
return cookieCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得指定Cookie名对应的HttpCookie对象
|
||||
*
|
||||
* @param cookieName Cookie名
|
||||
* @return HttpCookie对象
|
||||
*/
|
||||
public HttpCookie getCookie(String cookieName) {
|
||||
return getCookieMap().get(cookieName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为Multipart类型表单,此类型表单用于文件上传
|
||||
*
|
||||
* @return 是否为Multipart类型表单,此类型表单用于文件上传
|
||||
*/
|
||||
public boolean isMultipart() {
|
||||
if (false == isPostMethod()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final String contentType = getContentType();
|
||||
if (StrUtil.isBlank(contentType)) {
|
||||
return false;
|
||||
}
|
||||
return contentType.toLowerCase().startsWith("multipart/");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求体文本,可以是form表单、json、xml等任意内容<br>
|
||||
* 使用{@link #getCharset()}判断编码,判断失败使用UTF-8编码
|
||||
*
|
||||
* @return 请求
|
||||
*/
|
||||
public String getBody() {
|
||||
return getBody(getCharset());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求体文本,可以是form表单、json、xml等任意内容
|
||||
*
|
||||
* @param charset 编码
|
||||
* @return 请求
|
||||
*/
|
||||
public String getBody(Charset charset) {
|
||||
return StrUtil.str(getBodyBytes(), charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取body的bytes数组
|
||||
*
|
||||
* @return body的bytes数组
|
||||
*/
|
||||
public byte[] getBodyBytes(){
|
||||
if(null == this.bodyCache){
|
||||
this.bodyCache = IoUtil.readBytes(getBodyStream(), true);
|
||||
}
|
||||
return this.bodyCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求体的流,流中可以读取请求内容,包括请求表单数据或文件上传数据
|
||||
*
|
||||
* @return 流
|
||||
*/
|
||||
public InputStream getBodyStream() {
|
||||
return this.httpExchange.getRequestBody();
|
||||
}
|
||||
|
||||
public ListValueMap<String, String> getParams() {
|
||||
if (null == this.paramsCache) {
|
||||
this.paramsCache = new ListValueMap<>();
|
||||
final Charset charset = getCharset();
|
||||
|
||||
//解析URL中的参数
|
||||
final String query = getQuery();
|
||||
if(StrUtil.isNotBlank(query)){
|
||||
this.paramsCache.putAll(HttpUtil.decodeParams(query, charset));
|
||||
}
|
||||
|
||||
// 解析multipart中的参数
|
||||
if(isMultipart()){
|
||||
this.paramsCache.putAll(getMultipart().getParamListMap());
|
||||
} else{
|
||||
// 解析body中的参数
|
||||
final String body = getBody();
|
||||
if(StrUtil.isNotBlank(body)){
|
||||
this.paramsCache.putAll(HttpUtil.decodeParams(body, charset));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.paramsCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端IP
|
||||
*
|
||||
* <p>
|
||||
* 默认检测的Header:
|
||||
*
|
||||
* <pre>
|
||||
* 1、X-Forwarded-For
|
||||
* 2、X-Real-IP
|
||||
* 3、Proxy-Client-IP
|
||||
* 4、WL-Proxy-Client-IP
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* otherHeaderNames参数用于自定义检测的Header<br>
|
||||
* 需要注意的是,使用此方法获取的客户IP地址必须在Http服务器(例如Nginx)中配置头信息,否则容易造成IP伪造。
|
||||
* </p>
|
||||
*
|
||||
* @param otherHeaderNames 其他自定义头文件,通常在Http服务器(例如Nginx)中配置
|
||||
* @return IP地址
|
||||
*/
|
||||
public String getClientIP(String... otherHeaderNames) {
|
||||
String[] headers = {"X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR"};
|
||||
if (ArrayUtil.isNotEmpty(otherHeaderNames)) {
|
||||
headers = ArrayUtil.addAll(headers, otherHeaderNames);
|
||||
}
|
||||
|
||||
return getClientIPByHeader(headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端IP
|
||||
*
|
||||
* <p>
|
||||
* headerNames参数用于自定义检测的Header<br>
|
||||
* 需要注意的是,使用此方法获取的客户IP地址必须在Http服务器(例如Nginx)中配置头信息,否则容易造成IP伪造。
|
||||
* </p>
|
||||
*
|
||||
* @param headerNames 自定义头,通常在Http服务器(例如Nginx)中配置
|
||||
* @return IP地址
|
||||
* @since 4.4.1
|
||||
*/
|
||||
public String getClientIPByHeader(String... headerNames) {
|
||||
String ip;
|
||||
for (String header : headerNames) {
|
||||
ip = getHeader(header);
|
||||
if (false == NetUtil.isUnknown(ip)) {
|
||||
return NetUtil.getMultistageReverseProxyIp(ip);
|
||||
}
|
||||
}
|
||||
|
||||
ip = this.httpExchange.getRemoteAddress().getHostName();
|
||||
return NetUtil.getMultistageReverseProxyIp(ip);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得MultiPart表单内容,多用于获得上传的文件
|
||||
*
|
||||
* @return MultipartFormData
|
||||
* @throws IORuntimeException IO异常
|
||||
* @since 5.3.0
|
||||
*/
|
||||
public MultipartFormData getMultipart() throws IORuntimeException {
|
||||
if(null == this.multipartFormDataCache){
|
||||
this.multipartFormDataCache = parseMultipart(new UploadSetting());
|
||||
}
|
||||
return this.multipartFormDataCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得multipart/form-data 表单内容<br>
|
||||
* 包括文件和普通表单数据<br>
|
||||
* 在同一次请求中,此方法只能被执行一次!
|
||||
*
|
||||
* @param uploadSetting 上传文件的设定,包括最大文件大小、保存在内存的边界大小、临时目录、扩展名限定等
|
||||
* @return MultiPart表单
|
||||
* @throws IORuntimeException IO异常
|
||||
* @since 5.3.0
|
||||
*/
|
||||
public MultipartFormData parseMultipart(UploadSetting uploadSetting) throws IORuntimeException {
|
||||
final MultipartFormData formData = new MultipartFormData(uploadSetting);
|
||||
try {
|
||||
formData.parseRequestStream(getBodyStream(), getCharset());
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
|
||||
return formData;
|
||||
}
|
||||
}
|
@@ -0,0 +1,365 @@
|
||||
package cn.hutool.http.server;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.URLUtil;
|
||||
import cn.hutool.http.ContentType;
|
||||
import cn.hutool.http.Header;
|
||||
import cn.hutool.http.HttpStatus;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
|
||||
import com.sun.net.httpserver.Headers;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Http响应对象,用于写出数据到客户端
|
||||
*/
|
||||
public class HttpServerResponse extends HttpServerBase {
|
||||
|
||||
private Charset charset;
|
||||
/**
|
||||
* 是否已经发送了Http状态码,如果没有,提前写出状态码
|
||||
*/
|
||||
private boolean isSendCode;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param httpExchange {@link HttpExchange}
|
||||
*/
|
||||
public HttpServerResponse(HttpExchange httpExchange) {
|
||||
super(httpExchange);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送HTTP状态码
|
||||
*
|
||||
* @param httpStatusCode HTTP状态码,见HttpStatus
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse send(int httpStatusCode) {
|
||||
return send(httpStatusCode, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送成功状态码
|
||||
*
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse sendOk() {
|
||||
return send(HttpStatus.HTTP_OK);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送404错误页
|
||||
*
|
||||
* @param content 错误页页面内容,默认text/html类型
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse send404(String content) {
|
||||
return sendError(HttpStatus.HTTP_NOT_FOUND, content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送错误页
|
||||
*
|
||||
* @param errorCode HTTP错误状态码,见HttpStatus
|
||||
* @param content 错误页页面内容,默认text/html类型
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse sendError(int errorCode, String content) {
|
||||
send(errorCode);
|
||||
setContentType(ContentType.TEXT_HTML.toString());
|
||||
return write(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送HTTP状态码
|
||||
*
|
||||
* @param httpStatusCode HTTP状态码,见HttpStatus
|
||||
* @param bodyLength 响应体长度,默认0
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse send(int httpStatusCode, long bodyLength) {
|
||||
if (this.isSendCode) {
|
||||
throw new IORuntimeException("Http status code has been send!");
|
||||
}
|
||||
|
||||
try {
|
||||
this.httpExchange.sendResponseHeaders(httpStatusCode, bodyLength);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
|
||||
this.isSendCode = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得所有响应头,获取后可以添加新的响应头
|
||||
*
|
||||
* @return 响应头
|
||||
*/
|
||||
public Headers getHeaders() {
|
||||
if (false == this.isSendCode) {
|
||||
sendOk();
|
||||
}
|
||||
return this.httpExchange.getResponseHeaders();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加响应头,如果已经存在,则追加
|
||||
*
|
||||
* @param header 头key
|
||||
* @param value 值
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse addHeader(String header, String value) {
|
||||
getHeaders().add(header, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置响应头,如果已经存在,则覆盖
|
||||
*
|
||||
* @param header 头key
|
||||
* @param value 值
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse setHeader(Header header, String value) {
|
||||
return setHeader(header.getValue(), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置响应头,如果已经存在,则覆盖
|
||||
*
|
||||
* @param header 头key
|
||||
* @param value 值
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse setHeader(String header, String value) {
|
||||
getHeaders().set(header, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置响应头,如果已经存在,则覆盖
|
||||
*
|
||||
* @param header 头key
|
||||
* @param value 值列表
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse setHeader(String header, List<String> value) {
|
||||
getHeaders().put(header, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置所有响应头,如果已经存在,则覆盖
|
||||
*
|
||||
* @param headers 响应头map
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse setHeaders(Map<String, List<String>> headers) {
|
||||
getHeaders().putAll(headers);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Content-Type头,类似于:text/html;charset=utf-8<br>
|
||||
* 如果用户传入的信息无charset信息,自动根据charset补充,charset设置见{@link #setCharset(Charset)}
|
||||
*
|
||||
* @param contentType Content-Type头内容
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse setContentType(String contentType) {
|
||||
if (null != contentType && null != this.charset) {
|
||||
if (false == contentType.contains(";charset=")) {
|
||||
contentType = ContentType.build(contentType, this.charset);
|
||||
}
|
||||
}
|
||||
|
||||
return setHeader(Header.CONTENT_TYPE, contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Content-Length头
|
||||
*
|
||||
* @param contentLength Content-Length头内容
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse setContentLength(long contentLength) {
|
||||
return setHeader(Header.CONTENT_LENGTH, String.valueOf(contentLength));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置响应的编码
|
||||
*
|
||||
* @param charset 编码
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse setCharset(Charset charset) {
|
||||
this.charset = charset;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置属性
|
||||
*
|
||||
* @param name 属性名
|
||||
* @param value 属性值
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse setAttr(String name, Object value) {
|
||||
this.httpExchange.setAttribute(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取响应数据流
|
||||
*
|
||||
* @return 响应数据流
|
||||
*/
|
||||
public OutputStream getOut() {
|
||||
if (false == this.isSendCode) {
|
||||
sendOk();
|
||||
}
|
||||
return this.httpExchange.getResponseBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取响应数据流
|
||||
*
|
||||
* @return 响应数据流
|
||||
*/
|
||||
public PrintWriter getWriter() {
|
||||
final Charset charset = ObjectUtil.defaultIfNull(this.charset, DEFAULT_CHARSET);
|
||||
return new PrintWriter(new OutputStreamWriter(getOut(), charset));
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出数据到客户端
|
||||
*
|
||||
* @param data 数据
|
||||
* @param contentType Content-Type类型
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse write(String data, String contentType) {
|
||||
setContentType(contentType);
|
||||
return write(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出数据到客户端
|
||||
*
|
||||
* @param data 数据
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse write(String data) {
|
||||
final Charset charset = ObjectUtil.defaultIfNull(this.charset, DEFAULT_CHARSET);
|
||||
return write(StrUtil.bytes(data, charset));
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出数据到客户端
|
||||
*
|
||||
* @param data 数据
|
||||
* @param contentType 返回的类型
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse write(byte[] data, String contentType) {
|
||||
setContentType(contentType);
|
||||
return write(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出数据到客户端
|
||||
*
|
||||
* @param data 数据
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse write(byte[] data) {
|
||||
return write(new ByteArrayInputStream(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回数据给客户端
|
||||
*
|
||||
* @param in 需要返回客户端的内容
|
||||
* @param contentType 返回的类型
|
||||
* @return this
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public HttpServerResponse write(InputStream in, String contentType) {
|
||||
setContentType(contentType);
|
||||
return write(in);
|
||||
}
|
||||
|
||||
/**
|
||||
* 写出数据到客户端
|
||||
*
|
||||
* @param in 数据流
|
||||
* @return this
|
||||
*/
|
||||
public HttpServerResponse write(InputStream in) {
|
||||
OutputStream out = null;
|
||||
try {
|
||||
out = getOut();
|
||||
IoUtil.copy(in, out);
|
||||
} finally {
|
||||
IoUtil.close(out);
|
||||
IoUtil.close(in);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回文件给客户端(文件下载)
|
||||
*
|
||||
* @param file 写出的文件对象
|
||||
* @return this
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public HttpServerResponse write(File file) {
|
||||
final String fileName = file.getName();
|
||||
final String contentType = ObjectUtil.defaultIfNull(HttpUtil.getMimeType(fileName), "application/octet-stream");
|
||||
BufferedInputStream in = null;
|
||||
try {
|
||||
in = FileUtil.getInputStream(file);
|
||||
write(in, contentType, fileName);
|
||||
} finally {
|
||||
IoUtil.close(in);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回文件数据给客户端(文件下载)
|
||||
*
|
||||
* @param in 需要返回客户端的内容
|
||||
* @param contentType 返回的类型
|
||||
* @param fileName 文件名
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public void write(InputStream in, String contentType, String fileName) {
|
||||
final Charset charset = ObjectUtil.defaultIfNull(this.charset, DEFAULT_CHARSET);
|
||||
setHeader("Content-Disposition", StrUtil.format("attachment;filename={}", URLUtil.encode(fileName, charset)));
|
||||
setContentType(contentType);
|
||||
write(in);
|
||||
}
|
||||
}
|
@@ -0,0 +1,127 @@
|
||||
package cn.hutool.http.server;
|
||||
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.http.server.action.Action;
|
||||
import cn.hutool.http.server.action.RootAction;
|
||||
import cn.hutool.http.server.handler.ActionHandler;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
import com.sun.net.httpserver.HttpServer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* 简易Http服务器,基于{@link HttpServer}
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.2.5
|
||||
*/
|
||||
public class SimpleServer {
|
||||
|
||||
HttpServer server;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param port 监听端口
|
||||
*/
|
||||
public SimpleServer(int port) {
|
||||
this(new InetSocketAddress(port));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param hostname 监听地址
|
||||
* @param port 监听端口
|
||||
*/
|
||||
public SimpleServer(String hostname, int port) {
|
||||
this(new InetSocketAddress(hostname, port));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param address 监听地址
|
||||
*/
|
||||
public SimpleServer(InetSocketAddress address) {
|
||||
try {
|
||||
this.server = HttpServer.create(address, 0);
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加请求处理规则
|
||||
*
|
||||
* @param path 路径
|
||||
* @param handler 处理器
|
||||
* @return this
|
||||
*/
|
||||
public SimpleServer addHandler(String path, HttpHandler handler) {
|
||||
this.server.createContext(path, handler);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置根目录,默认的页面从root目录中读取解析返回
|
||||
*
|
||||
* @param root 路径
|
||||
* @return this
|
||||
*/
|
||||
public SimpleServer setRoot(String root) {
|
||||
return addAction("/", new RootAction(root));
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加请求处理规则
|
||||
*
|
||||
* @param path 路径
|
||||
* @param action 处理器
|
||||
* @return this
|
||||
*/
|
||||
public SimpleServer addAction(String path, Action action) {
|
||||
return addHandler(path, new ActionHandler(action));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置自定义线程池
|
||||
*
|
||||
* @param executor {@link Executor}
|
||||
* @return this
|
||||
*/
|
||||
public SimpleServer setExecutor(Executor executor) {
|
||||
this.server.setExecutor(executor);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得原始HttpServer对象
|
||||
*
|
||||
* @return {@link HttpServer}
|
||||
*/
|
||||
public HttpServer getRawServer() {
|
||||
return this.server;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取服务器地址信息
|
||||
*
|
||||
* @return {@link InetSocketAddress}
|
||||
*/
|
||||
public InetSocketAddress getAddress() {
|
||||
return this.server.getAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动Http服务器,启动后会阻塞当前线程
|
||||
*/
|
||||
public void start() {
|
||||
final InetSocketAddress address = getAddress();
|
||||
Console.log("Hutool Simple Http Server listen on 【{}:{}】", address.getHostName(), address.getPort());
|
||||
this.server.start();
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
package cn.hutool.http.server.action;
|
||||
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 请求处理接口<br>
|
||||
* 当用户请求某个Path,则调用相应Action的doAction方法
|
||||
*
|
||||
* @author Looly
|
||||
* @since 5.2.6
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Action {
|
||||
|
||||
/**
|
||||
* 处理请求
|
||||
*
|
||||
* @param request 请求对象
|
||||
* @param response 响应对象
|
||||
* @throws IOException IO异常
|
||||
*/
|
||||
void doAction(HttpServerRequest request, HttpServerResponse response) throws IOException;
|
||||
}
|
@@ -0,0 +1,64 @@
|
||||
package cn.hutool.http.server.action;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 默认的处理器,通过解析用户传入的path,找到网页根目录下对应文件后返回
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public class RootAction implements Action {
|
||||
|
||||
public static final String DEFAULT_INDEX_FILE_NAME = "index.html";
|
||||
|
||||
private final String rootDir;
|
||||
private final List<String> indexFileNames;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param rootDir 网页根目录
|
||||
*/
|
||||
public RootAction(String rootDir) {
|
||||
this(rootDir, DEFAULT_INDEX_FILE_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param rootDir 网页根目录
|
||||
* @param indexFileNames 主页文件名列表
|
||||
*/
|
||||
public RootAction(String rootDir, String... indexFileNames) {
|
||||
this.rootDir = rootDir;
|
||||
this.indexFileNames = CollUtil.toList(indexFileNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doAction(HttpServerRequest request, HttpServerResponse response) {
|
||||
final String path = request.getPath();
|
||||
File file = FileUtil.file(rootDir, path);
|
||||
if (file.exists()) {
|
||||
if (file.isDirectory()) {
|
||||
for (String indexFileName : indexFileNames) {
|
||||
//默认读取主页
|
||||
file = FileUtil.file(file, indexFileName);
|
||||
if (file.exists() && file.isFile()) {
|
||||
response.write(file);
|
||||
}
|
||||
}
|
||||
} else{
|
||||
response.write(file);
|
||||
}
|
||||
}
|
||||
|
||||
response.send404("404 Not Found !");
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
package cn.hutool.http.server.handler;
|
||||
|
||||
import cn.hutool.http.server.HttpServerRequest;
|
||||
import cn.hutool.http.server.HttpServerResponse;
|
||||
import cn.hutool.http.server.action.Action;
|
||||
import com.sun.net.httpserver.HttpExchange;
|
||||
import com.sun.net.httpserver.HttpHandler;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Action处理器,用于将HttpHandler转换为Action形式
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.2.6
|
||||
*/
|
||||
public class ActionHandler implements HttpHandler {
|
||||
|
||||
private final Action action;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param action Action
|
||||
*/
|
||||
public ActionHandler(Action action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(HttpExchange httpExchange) throws IOException {
|
||||
action.doAction(
|
||||
new HttpServerRequest(httpExchange),
|
||||
new HttpServerResponse(httpExchange)
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Http服务器封装
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
package cn.hutool.http.server;
|
@@ -2,15 +2,14 @@ package cn.hutool.http.ssl;
|
||||
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
/**
|
||||
* 自定义支持协议类型的SSLSocketFactory
|
||||
*
|
||||
@@ -18,8 +17,8 @@ import javax.net.ssl.SSLSocketFactory;
|
||||
*/
|
||||
public class CustomProtocolsSSLFactory extends SSLSocketFactory {
|
||||
|
||||
private String[] protocols;
|
||||
private SSLSocketFactory base;
|
||||
private final String[] protocols;
|
||||
private final SSLSocketFactory base;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
|
@@ -1,9 +1,7 @@
|
||||
package cn.hutool.http.ssl;
|
||||
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
/**
|
||||
* 证书管理
|
||||
|
@@ -1,9 +1,9 @@
|
||||
package cn.hutool.http.useragent;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* User-agent信息
|
||||
*
|
||||
@@ -15,9 +15,9 @@ public class UserAgentInfo {
|
||||
public static final String NameUnknown = "Unknown";
|
||||
|
||||
/** 信息名称 */
|
||||
private String name;
|
||||
private final String name;
|
||||
/** 信息匹配模式 */
|
||||
private Pattern pattern;
|
||||
private final Pattern pattern;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package cn.hutool.http.useragent;
|
||||
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -19,6 +20,9 @@ public class UserAgentParser {
|
||||
* @return {@link UserAgent}
|
||||
*/
|
||||
public static UserAgent parse(String userAgentString) {
|
||||
if(StrUtil.isBlank(userAgentString)){
|
||||
return null;
|
||||
}
|
||||
final UserAgent userAgent = new UserAgent();
|
||||
|
||||
final Browser browser = parseBrowser(userAgentString);
|
||||
|
@@ -1,11 +1,15 @@
|
||||
package cn.hutool.http.webservice;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.XmlUtil;
|
||||
import cn.hutool.http.HttpBase;
|
||||
import cn.hutool.http.HttpGlobalConfig;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.namespace.QName;
|
||||
@@ -18,17 +22,12 @@ import javax.xml.soap.SOAPException;
|
||||
import javax.xml.soap.SOAPHeader;
|
||||
import javax.xml.soap.SOAPHeaderElement;
|
||||
import javax.xml.soap.SOAPMessage;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.core.util.XmlUtil;
|
||||
import cn.hutool.http.HttpGlobalConfig;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* SOAP客户端
|
||||
@@ -52,7 +51,7 @@ import cn.hutool.http.HttpResponse;
|
||||
* @author looly
|
||||
* @since 4.5.4
|
||||
*/
|
||||
public class SoapClient {
|
||||
public class SoapClient extends HttpBase<SoapClient> {
|
||||
|
||||
/**
|
||||
* XML消息体的Content-Type
|
||||
@@ -63,10 +62,7 @@ public class SoapClient {
|
||||
* 请求的URL地址
|
||||
*/
|
||||
private String url;
|
||||
/**
|
||||
* 编码
|
||||
*/
|
||||
private Charset charset = CharsetUtil.CHARSET_UTF_8;
|
||||
|
||||
/**
|
||||
* 默认连接超时
|
||||
*/
|
||||
@@ -91,7 +87,7 @@ public class SoapClient {
|
||||
/**
|
||||
* 应用于方法上的命名空间URI
|
||||
*/
|
||||
private String namespaceURI;
|
||||
private final String namespaceURI;
|
||||
|
||||
/**
|
||||
* 创建SOAP客户端,默认使用soap1.1版本协议
|
||||
@@ -204,11 +200,17 @@ public class SoapClient {
|
||||
*
|
||||
* @param charset 编码
|
||||
* @return this
|
||||
* @see #charset(Charset)
|
||||
*/
|
||||
public SoapClient setCharset(Charset charset) {
|
||||
this.charset = charset;
|
||||
return this.charset(charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SoapClient charset(Charset charset) {
|
||||
super.charset(charset);
|
||||
try {
|
||||
this.message.setProperty(SOAPMessage.CHARACTER_SET_ENCODING, this.charset.toString());
|
||||
this.message.setProperty(SOAPMessage.CHARACTER_SET_ENCODING, this.charset());
|
||||
this.message.setProperty(SOAPMessage.WRITE_XML_DECLARATION, "true");
|
||||
} catch (SOAPException e) {
|
||||
// ignore
|
||||
@@ -229,17 +231,45 @@ public class SoapClient {
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置头信息
|
||||
* 设置SOAP头信息
|
||||
*
|
||||
* @param name 头信息标签名
|
||||
* @return this
|
||||
* @deprecated 为了和Http Hrader区分,请使用{@link #setSOAPHeader(QName)}
|
||||
*/
|
||||
@Deprecated
|
||||
public SoapClient setHeader(QName name) {
|
||||
return setSOAPHeader(name, null, null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置SOAP头信息
|
||||
*
|
||||
* @param name 头信息标签名
|
||||
* @return this
|
||||
*/
|
||||
public SoapClient setHeader(QName name) {
|
||||
return setHeader(name, null, null, null, null);
|
||||
public SoapClient setSOAPHeader(QName name) {
|
||||
return setSOAPHeader(name, null, null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置头信息
|
||||
* 设置SOAP头信息
|
||||
*
|
||||
* @param name 头信息标签名
|
||||
* @param actorURI 中间的消息接收者
|
||||
* @param roleUri Role的URI
|
||||
* @param mustUnderstand 标题项对于要对其进行处理的接收者来说是强制的还是可选的
|
||||
* @param relay relay属性
|
||||
* @return this
|
||||
* @deprecated 为了和Http Hrader区分,请使用{@link #setSOAPHeader(QName, String, String, Boolean, Boolean)}
|
||||
*/
|
||||
@Deprecated
|
||||
public SoapClient setHeader(QName name, String actorURI, String roleUri, Boolean mustUnderstand, Boolean relay) {
|
||||
return setSOAPHeader(name, actorURI, roleUri, mustUnderstand, relay);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置SOAP头信息
|
||||
*
|
||||
* @param name 头信息标签名
|
||||
* @param actorURI 中间的消息接收者
|
||||
@@ -248,7 +278,7 @@ public class SoapClient {
|
||||
* @param relay relay属性
|
||||
* @return this
|
||||
*/
|
||||
public SoapClient setHeader(QName name, String actorURI, String roleUri, Boolean mustUnderstand, Boolean relay) {
|
||||
public SoapClient setSOAPHeader(QName name, String actorURI, String roleUri, Boolean mustUnderstand, Boolean relay) {
|
||||
SOAPHeader header;
|
||||
SOAPHeaderElement ele;
|
||||
try {
|
||||
@@ -549,6 +579,7 @@ public class SoapClient {
|
||||
.setConnectionTimeout(this.connectionTimeout)
|
||||
.setReadTimeout(this.readTimeout)
|
||||
.contentType(getXmlContentType())//
|
||||
.header(this.headers())
|
||||
.body(getMsgStr(false))//
|
||||
.executeAsync();
|
||||
}
|
||||
|
@@ -23,7 +23,7 @@ public enum SoapProtocol {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
private String value;
|
||||
private final String value;
|
||||
|
||||
/**
|
||||
* 获取版本值信息
|
||||
|
@@ -0,0 +1,13 @@
|
||||
package cn.hutool.http.server;
|
||||
|
||||
import cn.hutool.http.HttpUtil;
|
||||
|
||||
public class BlankServerTest {
|
||||
public static void main(String[] args) {
|
||||
HttpUtil.createServer(8888)
|
||||
.addAction("/", (req, res)->{
|
||||
res.write("Hello Hutool Server");
|
||||
})
|
||||
.start();
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
package cn.hutool.http.server;
|
||||
|
||||
import cn.hutool.core.swing.DesktopUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
|
||||
public class DocServerTest {
|
||||
|
||||
public static void main(String[] args) {
|
||||
HttpUtil.createServer(80)
|
||||
// 设置默认根目录,
|
||||
.setRoot("D:\\workspace\\site\\hutool-site")
|
||||
.start();
|
||||
|
||||
DesktopUtil.browse("http://localhost/");
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
package cn.hutool.http.server;
|
||||
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.net.multipart.UploadFile;
|
||||
import cn.hutool.http.ContentType;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
|
||||
public class SimpleServerTest {
|
||||
|
||||
public static void main(String[] args) {
|
||||
HttpUtil.createServer(8888)
|
||||
// 设置默认根目录,
|
||||
.setRoot("d:/test")
|
||||
// get数据测试,返回请求的PATH
|
||||
.addAction("/get", (request, response) ->
|
||||
response.write(request.getURI().toString(), ContentType.TEXT_PLAIN.toString())
|
||||
)
|
||||
// 返回JSON数据测试
|
||||
.addAction("/restTest", (request, response) ->
|
||||
response.write("{\"id\": 1, \"msg\": \"OK\"}", ContentType.JSON.toString())
|
||||
)
|
||||
// 获取表单数据测试
|
||||
// http://localhost:8888/formTest?a=1&a=2&b=3
|
||||
.addAction("/formTest", (request, response) ->
|
||||
response.write(request.getParams().toString(), ContentType.TEXT_PLAIN.toString())
|
||||
)
|
||||
// 文件上传测试
|
||||
// http://localhost:8888/formTest?a=1&a=2&b=3
|
||||
.addAction("/file", (request, response) -> {
|
||||
final UploadFile[] files = request.getMultipart().getFiles("file");
|
||||
// 传入目录,默认读取HTTP头中的文件名然后创建文件
|
||||
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();
|
||||
}
|
||||
}
|
@@ -44,7 +44,7 @@ public class DownloadTest {
|
||||
// 带进度显示的文件下载
|
||||
HttpUtil.downloadFile("http://mirrors.sohu.com/centos/7/isos/x86_64/CentOS-7-x86_64-DVD-1810.iso", FileUtil.file("d:/"), new StreamProgress() {
|
||||
|
||||
long time = System.currentTimeMillis();
|
||||
final long time = System.currentTimeMillis();
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
|
@@ -1,11 +1,5 @@
|
||||
package cn.hutool.http.test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.date.TimeInterval;
|
||||
import cn.hutool.core.lang.Console;
|
||||
@@ -13,6 +7,11 @@ import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpResponse;
|
||||
import cn.hutool.http.ssl.SSLSocketFactoryBuilder;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* {@link HttpRequest}单元测试
|
||||
@@ -37,19 +36,16 @@ public class HttpRequestTest {
|
||||
HttpResponse res = HttpRequest.get("https://www.oschina.net/").execute();
|
||||
String body = res.body();
|
||||
Console.log(res.getCookies());
|
||||
Console.log(body);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void getWithParamsTest() {
|
||||
public void toStringTest() {
|
||||
String url = "http://gc.ditu.aliyun.com/geocoding?ccc=你好";
|
||||
|
||||
HttpRequest request = HttpRequest.get(url).setEncodeUrlParams(true).body("a=乌海");
|
||||
String body = request.execute().body();
|
||||
Console.log(body);
|
||||
|
||||
// String body2 = HttpUtil.get(url);
|
||||
// Console.log(body2);
|
||||
HttpRequest request = HttpRequest.get(url).body("a=乌海");
|
||||
Console.log(request.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@@ -1,13 +1,5 @@
|
||||
package cn.hutool.http.test;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.lang.Console;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
@@ -15,6 +7,14 @@ import cn.hutool.core.util.ReUtil;
|
||||
import cn.hutool.http.Header;
|
||||
import cn.hutool.http.HttpRequest;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class HttpUtilTest {
|
||||
|
||||
@@ -72,6 +72,17 @@ public class HttpUtilTest {
|
||||
Console.log(str);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void getTest5() {
|
||||
String url2 = "http://storage.chancecloud.com.cn/20200413_%E7%B2%A4B12313_386.pdf";
|
||||
ByteArrayOutputStream os2 = new ByteArrayOutputStream();
|
||||
HttpUtil.download(url2, os2, false);
|
||||
|
||||
url2 = "http://storage.chancecloud.com.cn/20200413_粤B12313_386.pdf";
|
||||
HttpUtil.download(url2, os2, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void get12306Test() {
|
||||
@@ -114,6 +125,14 @@ public class HttpUtilTest {
|
||||
Assert.assertEquals("?#@!$%^&=dsssss555555", map.get("c").get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeParamMapTest() {
|
||||
// 参数值存在分界标记等号时
|
||||
Map<String, String> paramMap = HttpUtil.decodeParamMap("https://www.xxx.com/api.action?aa=123&f_token=NzBkMjQxNDM1MDVlMDliZTk1OTU3ZDI1OTI0NTBiOWQ=", CharsetUtil.CHARSET_UTF_8);
|
||||
Assert.assertEquals("123",paramMap.get("aa"));
|
||||
Assert.assertEquals("NzBkMjQxNDM1MDVlMDliZTk1OTU3ZDI1OTI0NTBiOWQ=",paramMap.get("f_token"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toParamsTest() {
|
||||
String paramsStr = "uuuu=0&a=b&c=3Ddsssss555555";
|
||||
@@ -164,6 +183,16 @@ public class HttpUtilTest {
|
||||
paramsStr = "a=bbb&c=你好&哈喽&";
|
||||
encode = HttpUtil.encodeParams(paramsStr, CharsetUtil.CHARSET_UTF_8);
|
||||
Assert.assertEquals("a=bbb&c=%E4%BD%A0%E5%A5%BD&%E5%93%88%E5%96%BD=", encode);
|
||||
|
||||
// URL原样输出
|
||||
paramsStr = "https://www.hutool.cn/";
|
||||
encode = HttpUtil.encodeParams(paramsStr, CharsetUtil.CHARSET_UTF_8);
|
||||
Assert.assertEquals(paramsStr, encode);
|
||||
|
||||
// URL原样输出
|
||||
paramsStr = "https://www.hutool.cn/?";
|
||||
encode = HttpUtil.encodeParams(paramsStr, CharsetUtil.CHARSET_UTF_8);
|
||||
Assert.assertEquals("https://www.hutool.cn/", encode);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -274,4 +303,13 @@ public class HttpUtilTest {
|
||||
String mimeType = HttpUtil.getMimeType("aaa.aaa");
|
||||
Assert.assertNull(mimeType);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void getWeixinTest(){
|
||||
// 测试特殊URL,即URL中有&情况是否请求正常
|
||||
String url = "https://mp.weixin.qq.com/s?__biz=MzI5NjkyNTIxMg==&mid=100000465&idx=1&sn=1044c0d19723f74f04f4c1da34eefa35&chksm=6cbda3a25bca2ab4516410db6ce6e125badaac2f8c5548ea6e18eab6dc3c5422cb8cbe1095f7";
|
||||
final String s = HttpUtil.get(url);
|
||||
Console.log(s);
|
||||
}
|
||||
}
|
||||
|
@@ -14,13 +14,14 @@ import org.junit.Test;
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public class RestTest {
|
||||
|
||||
@Test
|
||||
public void contentTypeTest() {
|
||||
HttpRequest request = HttpRequest.post("http://localhost:8090/rest/restTest/")//
|
||||
.body(JSONUtil.createObj().put("aaa", "aaaValue").put("键2", "值2").toString());
|
||||
.body(JSONUtil.createObj()
|
||||
.set("aaa", "aaaValue")
|
||||
.set("键2", "值2").toString());
|
||||
Assert.assertEquals("application/json;charset=UTF-8", request.header("Content-Type"));
|
||||
}
|
||||
|
||||
@@ -28,7 +29,9 @@ public class RestTest {
|
||||
@Ignore
|
||||
public void postTest() {
|
||||
HttpRequest request = HttpRequest.post("http://localhost:8090/rest/restTest/")//
|
||||
.body(JSONUtil.createObj().put("aaa", "aaaValue").put("键2", "值2").toString());
|
||||
.body(JSONUtil.createObj()
|
||||
.set("aaa", "aaaValue")
|
||||
.set("键2", "值2").toString());
|
||||
Console.log(request.execute().body());
|
||||
}
|
||||
|
||||
@@ -36,7 +39,8 @@ public class RestTest {
|
||||
@Ignore
|
||||
public void postTest2() {
|
||||
String result = HttpUtil.post("http://localhost:8090/rest/restTest/", JSONUtil.createObj()//
|
||||
.put("aaa", "aaaValue").put("键2", "值2").toString());
|
||||
.set("aaa", "aaaValue")
|
||||
.set("键2", "值2").toString());
|
||||
Console.log(result);
|
||||
}
|
||||
|
||||
@@ -44,7 +48,9 @@ public class RestTest {
|
||||
@Ignore
|
||||
public void postTest3() {
|
||||
HttpRequest request = HttpRequest.post("http://211.162.39.204:8181/jeesite-simple/a/open/bizGwbnService/test")//
|
||||
.body(JSONUtil.createObj().put("aaa", "aaaValue").put("键2", "值2").toString());
|
||||
.body(JSONUtil.createObj()
|
||||
.set("aaa", "aaaValue")
|
||||
.set("键2", "值2").toString());
|
||||
Console.log(request.execute().body());
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user