Merge branch 'v5-dev' into v5-dev

This commit is contained in:
大火yzs
2020-05-29 14:26:53 +08:00
committed by GitHub
481 changed files with 15001 additions and 7851 deletions

View File

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

View File

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

View File

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

View File

@@ -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;
/**

View File

@@ -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此时提供状态码说明

View File

@@ -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
}

View File

@@ -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;

View File

@@ -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
}

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

View File

@@ -0,0 +1,16 @@
package cn.hutool.http.body;
import java.io.OutputStream;
/**
* 定义请求体接口
*/
public interface RequestBody {
/**
* 写出数据,不关闭流
*
* @param out out流
*/
void write(OutputStream out);
}

View File

@@ -0,0 +1,7 @@
/**
* 请求体封装实现
*
* @author looly
*
*/
package cn.hutool.http.body;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,7 @@
/**
* Http服务器封装
*
* @author looly
*
*/
package cn.hutool.http.server;

View File

@@ -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;
/**
* 构造

View File

@@ -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;
/**
* 证书管理

View File

@@ -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;
/**
* 构造

View File

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

View File

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

View File

@@ -23,7 +23,7 @@ public enum SoapProtocol {
this.value = value;
}
private String value;
private final String value;
/**
* 获取版本值信息

View File

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

View File

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

View File

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

View File

@@ -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() {

View File

@@ -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

View File

@@ -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中有&amp;情况是否请求正常
String url = "https://mp.weixin.qq.com/s?__biz=MzI5NjkyNTIxMg==&amp;mid=100000465&amp;idx=1&amp;sn=1044c0d19723f74f04f4c1da34eefa35&amp;chksm=6cbda3a25bca2ab4516410db6ce6e125badaac2f8c5548ea6e18eab6dc3c5422cb8cbe1095f7";
final String s = HttpUtil.get(url);
Console.log(s);
}
}

View File

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

View File

@@ -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