prepare 5.3.0

This commit is contained in:
Looly 2020-04-04 00:50:44 +08:00
parent 3921a568dd
commit 6b13cb5263
53 changed files with 619 additions and 331 deletions

View File

@ -18,9 +18,12 @@
* 【core 】 CollUtil增加toMap方法
* 【core 】 CollUtil和IterUtil废弃一些方法
* 【core 】 添加ValidateObjectInputStream避免对象反序列化漏洞风险
* 【core 】 添加BiMap
* 【all 】 cn.hutool.extra.servlet.multipart包迁移到cn.hutool.core.net下
### Bug修复
* 【extra 】 修复SpringUtil使用devtools重启报错问题
* 【http 】 修复HttpUtil.encodeParams针对无参数URL问题issue#817@Github
-------------------------------------------------------------------------------------------------------------

View File

@ -40,7 +40,7 @@
-- 主页:<a href="https://hutool.cn">https://hutool.cn/</a> | <a href="https://www.hutool.club/">https://www.hutool.club/</a> --
</p>
<p align="center">
-- QQ群③<a href="https://shang.qq.com/wpa/qunwpa?idkey=35764b2247c46ffebe28e45.2.6b2af8f5dee5efcf47ceec69d21e4521aa8c75">555368316</a> --
-- QQ群③<a href="https://shang.qq.com/wpa/qunwpa?idkey=35764b2247c46ffebe28e45.3.0b2af8f5dee5efcf47ceec69d21e4521aa8c75">555368316</a> --
-- QQ群④<a href="https://shang.qq.com/wpa/qunwpa?idkey=309056e409a304a454c7ba250a10d38dd82b9b49cd0e1f180fedbde78b02ae0d">718802356</a> --
</p>
@ -116,21 +116,21 @@ Hutool的存在就是为了减少代码搜索成本避免网络上参差不
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.2.6</version>
<version>5.3.0</version>
</dependency>
```
### Gradle
```
compile 'cn.hutool:hutool-all:5.2.6'
compile 'cn.hutool:hutool-all:5.3.0'
```
### 非Maven项目
点击以下任一链接,下载`hutool-all-X.X.X.jar`即可:
- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.2.6/)
- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.2.6/)
- [Maven中央库1](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.3.0/)
- [Maven中央库2](http://repo2.maven.org/maven2/cn/hutool/hutool-all/5.3.0/)
> 注意
> Hutool 5.x支持JDK8+对Android平台没有测试不能保证所有工具类获工具方法可用。

View File

@ -1 +1 @@
5.2.6
5.3.0

View File

@ -1 +1 @@
var version = '5.2.6'
var version = '5.3.0'

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.2.6-SNAPSHOT</version>
<version>5.3.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-all</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.2.6-SNAPSHOT</version>
<version>5.3.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-aop</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.2.6-SNAPSHOT</version>
<version>5.3.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-bloomFilter</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.2.6-SNAPSHOT</version>
<version>5.3.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-bom</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.2.6-SNAPSHOT</version>
<version>5.3.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-cache</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.2.6-SNAPSHOT</version>
<version>5.3.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-captcha</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.2.6-SNAPSHOT</version>
<version>5.3.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-core</artifactId>

View File

@ -1,14 +1,14 @@
package cn.hutool.core.codec;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.CharsetUtil;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.CharsetUtil;
/**
* Base64工具类提供Base64的编码和解码方案<br>
* base64编码是用642的6次方个ASCII字符来表示2562的8次方个ASCII字符<br>
@ -72,7 +72,7 @@ public class Base64 {
* @return 被加密后的字符串
*/
public static String encode(CharSequence source, String charset) {
return Base64Encoder.encode(source, CharsetUtil.charset(charset));
return encode(source, CharsetUtil.charset(charset));
}
/**
@ -84,7 +84,7 @@ public class Base64 {
* @since 3.0.6
*/
public static String encodeUrlSafe(CharSequence source, String charset) {
return Base64Encoder.encodeUrlSafe(source, CharsetUtil.charset(charset));
return encodeUrlSafe(source, CharsetUtil.charset(charset));
}
/**
@ -272,7 +272,7 @@ public class Base64 {
* @return 被加密后的字符串
*/
public static String decodeStr(CharSequence source, String charset) {
return Base64Decoder.decodeStr(source, CharsetUtil.charset(charset));
return decodeStr(source, CharsetUtil.charset(charset));
}
/**

View File

@ -0,0 +1,71 @@
package cn.hutool.core.map;
import java.util.Map;
/**
* 双向Map<br>
* 互换键值对不检查值是否有重复如果有则后加入的元素替换先加入的元素<br>
* 值的顺序在HashMap中不确定所以谁覆盖谁也不确定在有序的Map中按照先后顺序覆盖保留最后的值<br>
* 它与TableMap的区别是BiMap维护两个Map实现高效的正向和反向查找
*
* @param <K> 键类型
* @param <V> 值类型
* @since 5.2.6
*/
public class BiMap<K, V> extends MapWrapper<K, V> {
private Map<V, K> inverse;
/**
* 构造
*
* @param raw 被包装的Map
*/
public BiMap(Map<K, V> raw) {
super(raw);
}
@Override
public V put(K key, V value) {
if (null != this.inverse) {
this.inverse.put(value, key);
}
return super.put(key, value);
}
@Override
public void putAll(Map<? extends K, ? extends V> m) {
super.putAll(m);
if (null != this.inverse) {
m.forEach((key, value) -> this.inverse.put(value, key));
}
}
@Override
public void clear() {
super.clear();
this.inverse = null;
}
/**
* 获取反向Map
*
* @return 反向Map
*/
public Map<V, K> getInverse() {
if (null == this.inverse) {
inverse = MapUtil.inverse(getRaw());
}
return this.inverse;
}
/**
* 根据值获得键
*
* @param value
* @return
*/
public K getKey(V value) {
return getInverse().get(value);
}
}

View File

@ -681,11 +681,14 @@ public class MapUtil {
/**
* Map的键和值互换
* 互换键值对不检查值是否有重复如果有则后加入的元素替换先加入的元素<br>
* 值的顺序在HashMap中不确定所以谁覆盖谁也不确定在有序的Map中按照先后顺序覆盖保留最后的值
*
* @param <T> 键和值类型
* @param map Map对象键值类型必须一致
* @return 互换后的Map
* @since 3.2.2
* @see #inverse(Map)
*/
public static <T> Map<T, T> reverse(Map<T, T> map) {
return filter(map, (Editor<Entry<T, T>>) t -> new Entry<T, T>() {
@ -707,6 +710,23 @@ public class MapUtil {
});
}
/**
* Map的键和值互换<br>
* 互换键值对不检查值是否有重复如果有则后加入的元素替换先加入的元素<br>
* 值的顺序在HashMap中不确定所以谁覆盖谁也不确定在有序的Map中按照先后顺序覆盖保留最后的值
*
* @param <K> 键和值类型
* @param <V> 键和值类型
* @param map Map对象键值类型必须一致
* @return 互换后的Map
* @since 5.2.6
*/
public static <K, V> Map<V, K> inverse(Map<K, V> map) {
final Map<V, K> result = createMap(map.getClass());
map.forEach((key, value) -> result.put(value, key));
return result;
}
/**
* 排序已有MapKey有序的Map使用默认Key排序方式字母顺序
*

View File

@ -16,12 +16,13 @@ import java.util.Objects;
import java.util.Set;
/**
* 可重复键的Map
*
* @author looly
* 可重复键和值的Map<br>
* 通过键值单独建立List方式使键值对一一对应实现正向和反向两种查找<br>
* 无论是正向还是反向都是遍历列表查找过程相比标准的HashMap要慢数据越多越慢
*
* @param <K> 键类型
* @param <V> 值类型
* @author looly
*/
public class TableMap<K, V> implements Map<K, V>, Serializable {
private static final long serialVersionUID = 1L;
@ -31,7 +32,7 @@ public class TableMap<K, V> implements Map<K, V>, Serializable {
/**
* 构造
*
*
* @param size 初始容量
*/
public TableMap(int size) {
@ -41,8 +42,8 @@ public class TableMap<K, V> implements Map<K, V>, Serializable {
/**
* 构造
*
* @param keys 键列表
*
* @param keys 键列表
* @param values 值列表
*/
public TableMap(K[] keys, V[] values) {
@ -89,10 +90,10 @@ public class TableMap<K, V> implements Map<K, V>, Serializable {
* @return 值列表
* @since 5.2.5
*/
public List<V> getValues(K key){
public List<V> getValues(K key) {
return CollUtil.getAny(
this.values,
ListUtil.indexOfAll(this.keys, (ele)-> ObjectUtil.equal(ele, key))
ListUtil.indexOfAll(this.keys, (ele) -> ObjectUtil.equal(ele, key))
);
}
@ -103,10 +104,10 @@ public class TableMap<K, V> implements Map<K, V>, Serializable {
* @return 值列表
* @since 5.2.5
*/
public List<K> getKeys(V value){
public List<K> getKeys(V value) {
return CollUtil.getAny(
this.keys,
ListUtil.indexOfAll(this.values, (ele)-> ObjectUtil.equal(ele, value))
ListUtil.indexOfAll(this.values, (ele) -> ObjectUtil.equal(ele, value))
);
}
@ -189,12 +190,13 @@ public class TableMap<K, V> implements Map<K, V>, Serializable {
public V setValue(V value) {
throw new UnsupportedOperationException("setValue not supported.");
}
@Override
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
return Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue());
}

View File

@ -1,12 +1,12 @@
package cn.hutool.core.map.multi;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapWrapper;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapWrapper;
/**
* 值作为集合的Map实现通过调用putValue可以在相同key时加入多个值多个值用集合表示
*
@ -67,7 +67,7 @@ public abstract class CollectionValueMap<K, V> extends MapWrapper<K, Collection<
* @param loadFactor 加载因子
*/
public CollectionValueMap(int initialCapacity, float loadFactor) {
super(new HashMap<K, Collection<V>>(initialCapacity, loadFactor));
super(new HashMap<>(initialCapacity, loadFactor));
}
// ------------------------------------------------------------------------- Constructor end
@ -81,7 +81,7 @@ public abstract class CollectionValueMap<K, V> extends MapWrapper<K, Collection<
public void putValue(K key, V value) {
Collection<V> collection = this.get(key);
if (null == collection) {
collection = createCollction();
collection = createCollection();
this.put(key, collection);
}
collection.add(value);
@ -105,5 +105,5 @@ public abstract class CollectionValueMap<K, V> extends MapWrapper<K, Collection<
*
* @return {@link Collection}
*/
protected abstract Collection<V> createCollction();
protected abstract Collection<V> createCollection();
}

View File

@ -62,7 +62,7 @@ public class ListValueMap<K, V> extends CollectionValueMap<K, V> {
* @param loadFactor 加载因子
*/
public ListValueMap(int initialCapacity, float loadFactor) {
super(new HashMap<K, Collection<V>>(initialCapacity, loadFactor));
super(new HashMap<>(initialCapacity, loadFactor));
}
// ------------------------------------------------------------------------- Constructor end
@ -72,7 +72,7 @@ public class ListValueMap<K, V> extends CollectionValueMap<K, V> {
}
@Override
protected Collection<V> createCollction() {
protected Collection<V> createCollection() {
return new ArrayList<>(DEFAULT_COLLCTION_INITIAL_CAPACITY);
}
}

View File

@ -62,7 +62,7 @@ public class SetValueMap<K, V> extends CollectionValueMap<K, V> {
* @param loadFactor 加载因子
*/
public SetValueMap(int initialCapacity, float loadFactor) {
super(new HashMap<K, Collection<V>>(initialCapacity, loadFactor));
super(new HashMap<>(initialCapacity, loadFactor));
}
// ------------------------------------------------------------------------- Constructor end
@ -72,7 +72,7 @@ public class SetValueMap<K, V> extends CollectionValueMap<K, V> {
}
@Override
protected Collection<V> createCollction() {
protected Collection<V> createCollection() {
return new LinkedHashSet<>(DEFAULT_COLLCTION_INITIAL_CAPACITY);
}
}

View File

@ -1,17 +1,16 @@
package cn.hutool.extra.servlet.multipart;
package cn.hutool.core.net.multipart;
import cn.hutool.core.util.ArrayUtil;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletRequest;
import cn.hutool.core.util.ArrayUtil;
/**
* HttpRequest解析器<br>
* 来自Jodd
@ -21,9 +20,9 @@ import cn.hutool.core.util.ArrayUtil;
public class MultipartFormData {
/** 请求参数 */
private Map<String, String[]> requestParameters = new HashMap<String, String[]>();
private Map<String, String[]> requestParameters = new HashMap<>();
/** 请求文件 */
private Map<String, UploadFile[]> requestFiles = new HashMap<String, UploadFile[]>();
private Map<String, UploadFile[]> requestFiles = new HashMap<>();
/** 是否解析完毕 */
private boolean loaded;
@ -48,16 +47,6 @@ public class MultipartFormData {
}
// --------------------------------------------------------------------- Constructor end
/**
* 解析上传文件和表单数据
*
* @param request Http请求
* @throws IOException IO异常
*/
public void parseRequest(ServletRequest request) throws IOException {
parseRequestStream(request.getInputStream(), request.getCharacterEncoding());
}
/**
* 提取上传的文件和表单数据
*
@ -65,7 +54,7 @@ public class MultipartFormData {
* @param charset 编码
* @throws IOException IO异常
*/
public void parseRequestStream(InputStream inputStream, String charset) throws IOException {
public void parseRequestStream(InputStream inputStream, Charset charset) throws IOException {
setLoaded();
MultipartRequestInputStream input = new MultipartRequestInputStream(inputStream);

View File

@ -1,15 +1,16 @@
package cn.hutool.extra.servlet.multipart;
package cn.hutool.core.net.multipart;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
/**
* Http请求解析流提供了专门针对带文件的form表单的解析<br>
* 来自Jodd
*
*
* @author jodd.org
*/
public class MultipartRequestInputStream extends BufferedInputStream {
@ -20,7 +21,7 @@ public class MultipartRequestInputStream extends BufferedInputStream {
/**
* 读取byte字节流在末尾抛出异常
*
*
* @return byte
* @throws IOException 读取异常
*/
@ -34,7 +35,7 @@ public class MultipartRequestInputStream extends BufferedInputStream {
/**
* 跳过指定位数的 bytes.
*
*
* @param i 跳过的byte数
* @throws IOException IO异常
*/
@ -47,12 +48,14 @@ public class MultipartRequestInputStream extends BufferedInputStream {
// ---------------------------------------------------------------- boundary
/** part部分边界 */
/**
* part部分边界
*/
protected byte[] boundary;
/**
* 输入流中读取边界
*
*
* @return 边界
* @throws IOException 读取异常
*/
@ -60,6 +63,7 @@ public class MultipartRequestInputStream extends BufferedInputStream {
ByteArrayOutputStream boundaryOutput = new ByteArrayOutputStream(1024);
byte b;
// skip optional whitespaces
//noinspection StatementWithEmptyBody
while ((b = readByte()) <= ' ') {
}
boundaryOutput.write(b);
@ -89,12 +93,12 @@ public class MultipartRequestInputStream extends BufferedInputStream {
/**
* 从流中读取文件头部信息 如果达到末尾则返回null
*
*
* @param encoding 字符集
* @return 头部信息 如果达到末尾则返回null
* @throws IOException 读取异常
*/
public UploadFileHeader readDataHeader(String encoding) throws IOException {
public UploadFileHeader readDataHeader(Charset encoding) throws IOException {
String dataHeader = readDataHeaderString(encoding);
if (dataHeader != null) {
lastHeader = new UploadFileHeader(dataHeader);
@ -104,7 +108,14 @@ public class MultipartRequestInputStream extends BufferedInputStream {
return lastHeader;
}
protected String readDataHeaderString(String encoding) throws IOException {
/**
* 读取数据头信息字符串
*
* @param charset 编码
* @return 数据头信息字符串
* @throws IOException IO异常
*/
protected String readDataHeaderString(Charset charset) throws IOException {
ByteArrayOutputStream data = new ByteArrayOutputStream();
byte b;
while (true) {
@ -128,13 +139,13 @@ public class MultipartRequestInputStream extends BufferedInputStream {
data.write(b);
}
skipBytes(3);
return encoding == null ? data.toString() : data.toString(encoding);
return charset == null ? data.toString() : data.toString(charset.name());
}
// ---------------------------------------------------------------- copy
/**
* 全部字节流复制到out
*
*
* @param out 输出流
* @return 复制的字节数
* @throws IOException 读取异常
@ -154,8 +165,8 @@ public class MultipartRequestInputStream extends BufferedInputStream {
/**
* 复制字节流到out 大于maxBytes或者文件末尾停止
*
* @param out 输出流
*
* @param out 输出流
* @param limit 最大字节数
* @return 复制的字节数
* @throws IOException 读取异常
@ -178,7 +189,7 @@ public class MultipartRequestInputStream extends BufferedInputStream {
/**
* 跳过边界表示
*
*
* @return 跳过的字节数
* @throws IOException 读取异常
*/

View File

@ -1,4 +1,8 @@
package cn.hutool.extra.servlet.multipart;
package cn.hutool.core.net.multipart;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
@ -10,27 +14,18 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
/**
* 上传的文件对象
*
*
* @author xiaoleilu
*
*/
public class UploadFile {
private static Log log = LogFactory.get();
private static final String TMP_FILE_PREFIX = "hutool-";
private static final String TMP_FILE_SUFFIX = ".upload.tmp";
private UploadFileHeader header;
private UploadSetting setting;
private int size = -1;
// 文件流小文件位于内存中
@ -40,8 +35,8 @@ public class UploadFile {
/**
* 构造
*
* @param header 头部信息
*
* @param header 头部信息
* @param setting 上传设置
*/
public UploadFile(UploadFileHeader header, UploadSetting setting) {
@ -56,6 +51,7 @@ public class UploadFile {
*/
public void delete() {
if (tempFile != null) {
//noinspection ResultOfMethodCallIgnored
tempFile.delete();
}
if (data != null) {
@ -66,17 +62,18 @@ public class UploadFile {
/**
* 将上传的文件写入指定的目标文件路径自动创建文件<br>
* 写入后原临时文件会被删除
*
* @param destPath 目标文件路径
* @return 目标文件
* @throws IOException IO异常
*/
public File write(String destPath) throws IOException {
if(data != null || tempFile != null) {
if (data != null || tempFile != null) {
return write(FileUtil.touch(destPath));
}
return null;
}
/**
* 将上传的文件写入目标文件<br>
* 写入后原临时文件会被删除
@ -87,7 +84,7 @@ public class UploadFile {
*/
public File write(File destination) throws IOException {
assertValid();
if (destination.isDirectory() == true) {
destination = new File(destination, this.header.getFileName());
}
@ -101,14 +98,14 @@ public class UploadFile {
}
return destination;
}
/**
* @return 获得文件字节流
* @throws IOException IO异常
*/
public byte[] getFileContent() throws IOException {
assertValid();
if (data != null) {
return data;
}
@ -124,7 +121,7 @@ public class UploadFile {
*/
public InputStream getFileInputStream() throws IOException {
assertValid();
if (data != null) {
return new BufferedInputStream(new ByteArrayInputStream(data));
}
@ -174,9 +171,10 @@ public class UploadFile {
}
// ---------------------------------------------------------------- process
/**
* 处理上传表单流提取出文件
*
*
* @param input 上传表单的流
* @return 是否成功
* @throws IOException IO异常
@ -184,7 +182,6 @@ public class UploadFile {
protected boolean processStream(MultipartRequestInputStream input) throws IOException {
if (!isAllowedExtension()) {
// 非允许的扩展名
log.debug("Forbidden uploaded file [{}]", this.getFileName());
size = input.skipToBoundary();
return false;
}
@ -220,9 +217,9 @@ public class UploadFile {
size += input.copy(out, maxFileSize - size + 1); // one more byte to detect larger files
if (size > maxFileSize) {
// 超出上传大小限制
//noinspection ResultOfMethodCallIgnored
tempFile.delete();
tempFile = null;
log.debug("Upload file [{}] too big, file size > [{}]", this.getFileName(), maxFileSize);
input.skipToBoundary();
return false;
}
@ -236,6 +233,7 @@ public class UploadFile {
}
// ---------------------------------------------------------------------------- Private method start
/**
* @return 是否为允许的扩展名
*/
@ -257,13 +255,14 @@ public class UploadFile {
// 未匹配到扩展名如果为允许列表返回false 否则true
return !isAllow;
}
/**
* 断言是否文件流可用
*
* @throws IOException IO异常
*/
private void assertValid() throws IOException {
if(! isUploaded()) {
if (!isUploaded()) {
throw new IOException(StrUtil.format("File [{}] upload fail", getFileName()));
}
}

View File

@ -1,4 +1,4 @@
package cn.hutool.extra.servlet.multipart;
package cn.hutool.core.net.multipart;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;

View File

@ -1,4 +1,4 @@
package cn.hutool.extra.servlet.multipart;
package cn.hutool.core.net.multipart;
import cn.hutool.core.util.URLUtil;
import cn.hutool.log.Log;
@ -8,7 +8,7 @@ import java.net.URL;
/**
* 上传文件设定文件
*
*
* @author xiaoleilu
*
*/
@ -42,7 +42,7 @@ public class UploadSetting {
/**
* 设定最大文件大小-1表示无限制
*
*
* @param maxFileSize 最大文件大小
*/
public void setMaxFileSize(int maxFileSize) {
@ -59,7 +59,7 @@ public class UploadSetting {
/**
* 设定文件保存到内存的边界<br>
* 如果文件大小小于这个边界将保存于内存中否则保存至临时目录中
*
*
* @param memoryThreshold 文件保存到内存的边界
*/
public void setMemoryThreshold(int memoryThreshold) {
@ -75,7 +75,7 @@ public class UploadSetting {
/**
* 设定上传文件的临时目录null表示使用系统临时目录
*
*
* @param tmpUploadPath 临时目录绝对路径
*/
public void setTmpUploadPath(String tmpUploadPath) {
@ -92,7 +92,7 @@ public class UploadSetting {
/**
* 设定文件扩展名限定里列表<br>
* 禁止列表还是允许列表取决于isAllowFileExts
*
*
* @param fileExts 文件扩展名列表
*/
public void setFileExts(String[] fileExts) {
@ -101,7 +101,7 @@ public class UploadSetting {
/**
* 是否允许文件扩展名<br>
*
*
* @return 若true表示只允许列表里的扩展名否则是禁止列表里的扩展名
*/
public boolean isAllowFileExts() {
@ -110,7 +110,7 @@ public class UploadSetting {
/**
* 设定是否允许扩展名
*
*
* @param isAllowFileExts 若true表示只允许列表里的扩展名否则是禁止列表里的扩展名
*/
public void setAllowFileExts(boolean isAllowFileExts) {
@ -128,7 +128,7 @@ public class UploadSetting {
/**
* 加载全局设定
*
*
* @param settingPath 设定文件路径相对Classpath
*/
synchronized public void load(String settingPath) {

View File

@ -0,0 +1,7 @@
/**
* 文件上传封装
*
* @author looly
*
*/
package cn.hutool.core.net.multipart;

View File

@ -62,6 +62,17 @@ public class CharsetUtil {
return StrUtil.isBlank(charsetName) ? Charset.defaultCharset() : Charset.forName(charsetName);
}
/**
* 解析字符串编码为Charset对象解析失败返回系统默认编码
*
* @param charsetName 字符集为空则返回默认字符集
* @return Charset
* @since 5.2.6
*/
public static Charset parse(String charsetName) {
return parse(charsetName, Charset.defaultCharset());
}
/**
* 解析字符串编码为Charset对象解析失败返回默认编码
*
@ -70,7 +81,7 @@ public class CharsetUtil {
* @return Charset
* @since 5.2.6
*/
public static Charset parse(String charsetName, Charset defaultCharset) throws UnsupportedCharsetException {
public static Charset parse(String charsetName, Charset defaultCharset) {
if (StrUtil.isBlank(charsetName)) {
return defaultCharset;
}

View File

@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.2.6-SNAPSHOT</version>
<version>5.3.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-cron</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.2.6-SNAPSHOT</version>
<version>5.3.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-crypto</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.2.6-SNAPSHOT</version>
<version>5.3.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-db</artifactId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.2.6-SNAPSHOT</version>
<version>5.3.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-dfa</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.2.6-SNAPSHOT</version>
<version>5.3.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-extra</artifactId>

View File

@ -11,14 +11,14 @@ import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.CaseInsensitiveMap;
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.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.servlet.multipart.MultipartFormData;
import cn.hutool.extra.servlet.multipart.UploadSetting;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
@ -265,7 +265,7 @@ public class ServletUtil {
public static MultipartFormData getMultipart(ServletRequest request, UploadSetting uploadSetting) throws IORuntimeException {
final MultipartFormData formData = new MultipartFormData(uploadSetting);
try {
formData.parseRequest(request);
formData.parseRequestStream(request.getInputStream(), request.getCharacterEncoding());
} catch (IOException e) {
throw new IORuntimeException(e);
}

View File

@ -1,7 +0,0 @@
/**
* 基于Servlet的文件上传封装
*
* @author looly
*
*/
package cn.hutool.extra.servlet.multipart;

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.2.6-SNAPSHOT</version>
<version>5.3.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-http</artifactId>

View File

@ -28,10 +28,18 @@ 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;
@ -39,9 +47,19 @@ public enum ContentType {
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 +80,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 +89,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

@ -2,6 +2,7 @@ package cn.hutool.http;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.IterUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FastByteArrayOutputStream;
import cn.hutool.core.io.FileUtil;
@ -416,7 +417,7 @@ public class HttpUtil {
if (value instanceof Iterable) {
value = CollUtil.join((Iterable<?>) value, ",");
} else if (value instanceof Iterator) {
value = CollUtil.join((Iterator<?>) value, ",");
value = IterUtil.join((Iterator<?>) value, ",");
}
valueStr = Convert.toStr(value);
if (StrUtil.isNotEmpty(key)) {
@ -435,30 +436,33 @@ public class HttpUtil {
*
* <p>注意此方法只能标准化整个URL并不适合于单独编码参数值</p>
*
* @param paramsStr url参数可以包含url本身
* @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 {
// 无URL
paramPart = paramsStr;
} else if(false == StrUtil.contains(urlWithParams, '=')){
// 无参数的URL
return urlWithParams;
}else {
// 无URL的参数
paramPart = urlWithParams;
}
paramPart = normalizeParams(paramPart, charset);
@ -534,6 +538,18 @@ public class HttpUtil {
* @since 4.0.2
*/
public static HashMap<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 HashMap<String, String> decodeParamMap(String paramsStr, Charset charset) {
final Map<String, List<String>> paramsMap = decodeParams(paramsStr, charset);
final HashMap<String, String> result = MapUtil.newHashMap(paramsMap.size());
List<String> valueList;
@ -552,6 +568,18 @@ public class HttpUtil {
* @return 参数Map
*/
public static Map<String, List<String>> decodeParams(String paramsStr, String charset) {
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) {
if (StrUtil.isBlank(paramsStr)) {
return Collections.emptyMap();
}
@ -811,7 +839,7 @@ public class HttpUtil {
* @param value value
* @param charset 编码
*/
private static void addParam(Map<String, List<String>> params, String name, String value, String charset) {
private static void addParam(Map<String, List<String>> params, String name, String value, Charset charset) {
name = URLUtil.decode(name, charset);
value = URLUtil.decode(value, charset);
final List<String> values = params.computeIfAbsent(name, k -> new ArrayList<>(1));

View File

@ -1,7 +1,10 @@
package cn.hutool.http.server;
import cn.hutool.core.util.CharsetUtil;
import com.sun.net.httpserver.HttpExchange;
import java.nio.charset.Charset;
/**
* HttpServer公用对象提供HttpExchange包装和公用方法
*
@ -10,6 +13,8 @@ import com.sun.net.httpserver.HttpExchange;
*/
public class HttpServerBase {
final static Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8;
final HttpExchange httpExchange;
/**

View File

@ -1,9 +1,13 @@
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;
@ -15,6 +19,7 @@ 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;
@ -32,6 +37,9 @@ import java.util.Map;
public class HttpServerRequest extends HttpServerBase {
private Map<String, HttpCookie> cookieCache;
private ListValueMap<String, String> paramsCache;
private Charset charsetCache;
private byte[] bodyCache;
/**
* 构造
@ -159,9 +167,13 @@ public class HttpServerRequest extends HttpServerBase {
* @return 编码默认UTF-8
*/
public Charset getCharset() {
final String contentType = getContentType();
final String charsetStr = HttpUtil.getCharset(contentType);
return CharsetUtil.parse(charsetStr, CharsetUtil.CHARSET_UTF_8);
if(null == this.charsetCache){
final String contentType = getContentType();
final String charsetStr = HttpUtil.getCharset(contentType);
this.charsetCache = CharsetUtil.parse(charsetStr, DEFAULT_CHARSET);
}
return this.charsetCache;
}
/**
@ -226,12 +238,20 @@ public class HttpServerRequest extends HttpServerBase {
}
/**
* 获取请求体的流流中可以读取请求内容包括请求表单数据或文件上传数据
* 是否为Multipart类型表单此类型表单用于文件上传
*
* @return
* @return 是否为Multipart类型表单此类型表单用于文件上传
*/
public InputStream getBodyStream() {
return this.httpExchange.getRequestBody();
public boolean isMultipart() {
if (false == isPostMethod()) {
return false;
}
final String contentType = getContentType();
if (StrUtil.isBlank(contentType)) {
return false;
}
return contentType.toLowerCase().startsWith("multipart/");
}
/**
@ -251,31 +271,49 @@ public class HttpServerRequest extends HttpServerBase {
* @return 请求
*/
public String getBody(Charset charset) {
InputStream in = null;
try {
in = getBodyStream();
return IoUtil.read(in, charset);
} finally {
IoUtil.close(in);
}
return StrUtil.str(getBodyBytes(), charset);
}
/**
* 是否为Multipart类型表单此类型表单用于文件上传
* 获取body的bytes数组
*
* @return 是否为Multipart类型表单此类型表单用于文件上传
* @return body的bytes数组
*/
public boolean isMultipart() {
if (false == isPostMethod()) {
return false;
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));
}
// 解析body中的参数
final String body = getBody();
if(StrUtil.isNotBlank(body)){
this.paramsCache.putAll(HttpUtil.decodeParams(body, charset));
}
}
final String contentType = getContentType();
if (StrUtil.isBlank(contentType)) {
return false;
}
return contentType.toLowerCase().startsWith("multipart/");
return this.paramsCache;
}
/**
@ -332,4 +370,36 @@ public class HttpServerRequest extends HttpServerBase {
ip = this.httpExchange.getRemoteAddress().getHostName();
return NetUtil.getMultistageReverseProxyIp(ip);
}
/**
* 获得MultiPart表单内容多用于获得上传的文件 在同一次请求中此方法只能被执行一次
*
* @return MultipartFormData
* @throws IORuntimeException IO异常
* @since 5.3.0
*/
public MultipartFormData getMultipart() throws IORuntimeException {
return getMultipart(new UploadSetting());
}
/**
* 获得multipart/form-data 表单内容<br>
* 包括文件和普通表单数据<br>
* 在同一次请求中此方法只能被执行一次
*
* @param uploadSetting 上传文件的设定包括最大文件大小保存在内存的边界大小临时目录扩展名限定等
* @return MultiPart表单
* @throws IORuntimeException IO异常
* @since 5.3.0
*/
public MultipartFormData getMultipart(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

@ -3,12 +3,14 @@ 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.CharsetUtil;
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;
@ -18,6 +20,8 @@ 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;
@ -28,6 +32,10 @@ import java.util.Map;
public class HttpServerResponse extends HttpServerBase {
private Charset charset;
/**
* 是否已经发送了Http状态码如果没有提前写出状态码
*/
private boolean isSendCode;
/**
* 构造
@ -48,6 +56,38 @@ public class HttpServerResponse extends HttpServerBase {
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状态码
*
@ -56,11 +96,17 @@ public class HttpServerResponse extends HttpServerBase {
* @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;
}
@ -70,6 +116,9 @@ public class HttpServerResponse extends HttpServerBase {
* @return 响应头
*/
public Headers getHeaders() {
if (false == this.isSendCode) {
sendOk();
}
return this.httpExchange.getResponseHeaders();
}
@ -141,7 +190,7 @@ public class HttpServerResponse extends HttpServerBase {
public HttpServerResponse setContentType(String contentType) {
if (null != contentType && null != this.charset) {
if (false == contentType.contains(";charset=")) {
contentType += ";charset=" + this.charset;
contentType = ContentType.build(contentType, this.charset);
}
}
@ -169,12 +218,27 @@ public class HttpServerResponse extends HttpServerBase {
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();
}
@ -183,8 +247,43 @@ public class HttpServerResponse extends HttpServerBase {
*
* @return 响应数据流
*/
public OutputStream getWriter() {
return this.httpExchange.getResponseBody();
public PrintWriter getWriter() {
final Charset charset = ObjectUtil.defaultIfNull(this.charset, DEFAULT_CHARSET);
return new PrintWriter(new OutputStreamWriter(getOut(), charset));
}
/**
* 写出数据到客户端
*
* @param data 数据
* @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);
}
/**
@ -194,8 +293,19 @@ public class HttpServerResponse extends HttpServerBase {
* @return this
*/
public HttpServerResponse write(byte[] data) {
write(new ByteArrayInputStream(data));
return this;
return write(new ByteArrayInputStream(data));
}
/**
* 返回数据给客户端
*
* @param in 需要返回客户端的内容
* @param contentType 返回的类型
* @since 5.2.6
*/
public HttpServerResponse write(InputStream in, String contentType) {
setContentType(contentType);
return write(in);
}
/**
@ -236,7 +346,7 @@ public class HttpServerResponse extends HttpServerBase {
}
/**
* 返回数据给客户端
* 返回文件数据给客户端文件下载
*
* @param in 需要返回客户端的内容
* @param contentType 返回的类型
@ -244,7 +354,7 @@ public class HttpServerResponse extends HttpServerBase {
* @since 5.2.6
*/
public void write(InputStream in, String contentType, String fileName) {
final Charset charset = ObjectUtil.defaultIfNull(this.charset, CharsetUtil.CHARSET_UTF_8);
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

@ -1,9 +1,10 @@
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 cn.hutool.http.server.handler.RootHandler;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
@ -72,7 +73,7 @@ public class SimpleServer {
* @return this
*/
public SimpleServer setRoot(String root) {
return addHandler("/", new RootHandler(root));
return addAction("/", new RootAction(root));
}
/**
@ -119,6 +120,8 @@ public class SimpleServer {
* 启动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,62 @@
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 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);
}
}
}
}
response.send404("404 Not Found !");
}
}

View File

@ -27,6 +27,9 @@ public class ActionHandler implements HttpHandler {
@Override
public void handle(HttpExchange httpExchange) {
action.doAction(new HttpServerRequest(httpExchange), new HttpServerResponse(httpExchange));
action.doAction(
new HttpServerRequest(httpExchange),
new HttpServerResponse(httpExchange)
);
}
}

View File

@ -1,91 +0,0 @@
package cn.hutool.http.server.handler;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.http.Header;
import cn.hutool.http.HttpStatus;
import cn.hutool.http.HttpUtil;
import com.sun.net.httpserver.HttpExchange;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
/**
* 请求处理器相关工具类
*
* @since 5.2.6
*/
public class HandlerUtil {
/**
* 返回404页面
*
* @param httpExchange HttpExchange
* @param content 要发送的404页面内容
* @throws IOException IO异常
*/
public static void send404(HttpExchange httpExchange, String content) throws IOException {
if (null == httpExchange) {
return;
}
if (null == content) {
content = "404 Not Found !";
}
httpExchange.sendResponseHeaders(HttpStatus.HTTP_NOT_FOUND, 0);
try (OutputStream out = httpExchange.getResponseBody()) {
IoUtil.writeUtf8(out, false, content);
}
}
/**
* 返回文件
*
* @param httpExchange HttpExchange
* @param file 要发送的文件
* @throws IOException IO异常
*/
public static void sendFile(HttpExchange httpExchange, File file) throws IOException {
if (ArrayUtil.hasNull(httpExchange, file)) {
return;
}
addHeader(httpExchange,
Header.CONTENT_TYPE.toString(),
HttpUtil.getMimeType(file.getName(), "text/html"));
httpExchange.sendResponseHeaders(HttpStatus.HTTP_OK, 0);
try (OutputStream out = httpExchange.getResponseBody()) {
FileUtil.writeToStream(file, out);
}
}
/**
* 增加响应头信息
*
* @param httpExchange HttpExchange
* @param header 头名
* @param value 头值
*/
public static void addHeader(HttpExchange httpExchange, String header, String value) {
if (ArrayUtil.hasEmpty(httpExchange, header)) {
return;
}
httpExchange.getResponseHeaders().add(header, value);
}
/**
* 获取响应头信息
*
* @param httpExchange HttpExchange
* @param header 头名
* @return 不存在返回null
*/
public static String getHeader(HttpExchange httpExchange, String header) {
if (ArrayUtil.hasEmpty(httpExchange, header)) {
return null;
}
return httpExchange.getRequestHeaders().getFirst(header);
}
}

View File

@ -1,45 +0,0 @@
package cn.hutool.http.server.handler;
import cn.hutool.core.io.FileUtil;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import java.io.File;
import java.io.IOException;
import java.net.URI;
/**
* 默认的处理器通过解析用户传入的path找到网页根目录下对应文件后返回
*
* @author looly
* @since 5.2.6
*/
public class RootHandler implements HttpHandler {
private final String rootDir;
/**
* 构造
*
* @param rootDir 网页根目录
*/
public RootHandler(String rootDir) {
this.rootDir = rootDir;
}
@Override
public void handle(HttpExchange httpExchange) throws IOException {
final URI uri = httpExchange.getRequestURI();
File file = FileUtil.file(rootDir, uri.getPath());
if (file.exists()) {
if (file.isDirectory()) {
//默认读取主页
file = FileUtil.file(file, "index.html");
}
HandlerUtil.sendFile(httpExchange, file);
}
// 文件未找到
HandlerUtil.send404(httpExchange, null);
}
}

View File

@ -1,13 +1,23 @@
package cn.hutool.http.server;
import cn.hutool.http.ContentType;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.server.handler.RootHandler;
public class SimpleServerTest {
public static void main(String[] args) {
HttpUtil.createServer(8888)
.addHandler("/", new RootHandler("D:\\test"))
// 设置默认根目录
.setRoot("d:/test")
// 返回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())
)
.start();
}
}

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,13 @@ 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.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class HttpUtilTest {
@ -164,6 +163,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

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.2.6-SNAPSHOT</version>
<version>5.3.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-json</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.2.6-SNAPSHOT</version>
<version>5.3.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-log</artifactId>

View File

@ -8,7 +8,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.2.6-SNAPSHOT</version>
<version>5.3.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-poi</artifactId>

View File

@ -8,7 +8,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.2.6-SNAPSHOT</version>
<version>5.3.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-script</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.2.6-SNAPSHOT</version>
<version>5.3.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-setting</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.2.6-SNAPSHOT</version>
<version>5.3.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-socket</artifactId>

View File

@ -9,7 +9,7 @@
<parent>
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.2.6-SNAPSHOT</version>
<version>5.3.0-SNAPSHOT</version>
</parent>
<artifactId>hutool-system</artifactId>

View File

@ -8,7 +8,7 @@
<groupId>cn.hutool</groupId>
<artifactId>hutool-parent</artifactId>
<version>5.2.6-SNAPSHOT</version>
<version>5.3.0-SNAPSHOT</version>
<name>hutool</name>
<description>提供丰富的Java工具方法</description>
<url>https://github.com/looly/hutool</url>