From b14bbffa32c166a42685957b8e1d0e4fce7e0d0b Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 25 Apr 2021 18:13:44 +0800 Subject: [PATCH] add HttpDownloader --- CHANGELOG.md | 3 +- .../cn/hutool/core/util/DesensitizedUtil.java | 3 +- .../java/cn/hutool/core/util/RadixUtil.java | 4 +- .../hutool/core/util/ServiceLoaderUtil.java | 2 +- .../java/cn/hutool/http/HttpDownloader.java | 105 ++++++++++++++++++ .../java/cn/hutool/http/HttpResponse.java | 21 +++- .../main/java/cn/hutool/http/HttpUtil.java | 102 +++++------------ 7 files changed, 157 insertions(+), 83 deletions(-) create mode 100644 hutool-http/src/main/java/cn/hutool/http/HttpDownloader.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 05f166a1c..c7335dc43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.6.4 (2021-04-23) +# 5.6.4 (2021-04-25) ### 🐣新特性 * 【core 】 DatePattern补充DateTimeFormatter(pr#308@Gitee) @@ -13,6 +13,7 @@ * 【core 】 BeanUtil增加copyToList方法(issue#1526@Github) * 【extra 】 MailAccount增加customProperty可以用户自定义属性(pr#317@Gitee) * 【system 】 SystemUtil.getUserInfo()中所有平台路径统一末尾加/(issue#I3NM39@Gitee) +* 【http 】 新增HttpDownloader,默认开启自动跳转(issue#I3NM39@Gitee) ### 🐞Bug修复 * 【db 】 修复SQL分页时未使用别名导致的错误,同时count时取消order by子句(issue#I3IJ8X@Gitee) diff --git a/hutool-core/src/main/java/cn/hutool/core/util/DesensitizedUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/DesensitizedUtil.java index 0654a1ff5..1e8857775 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/DesensitizedUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/DesensitizedUtil.java @@ -130,8 +130,7 @@ public class DesensitizedUtil { if (StrUtil.isBlank(fullName)) { return StrUtil.EMPTY; } - final String name = StrUtil.subPre(fullName, 1); - return StrUtil.padAfter(name, StrUtil.length(fullName), "*"); + return StrUtil.hide(fullName, 1, fullName.length()); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RadixUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RadixUtil.java index 4e408ec7b..2d5278f41 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RadixUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RadixUtil.java @@ -1,6 +1,8 @@ package cn.hutool.core.util; /** + * 进制转换工具类,可以转换为任意进制 + *

* 把一个十进制整数根据自己定义的进制规则进行转换
* from:https://gitee.com/loolly/hutool/pulls/260 *

@@ -107,7 +109,7 @@ public class RadixUtil { /** * 把转换后进制的字符还原成long 值 * - * @param radixs 自定进制,需要和encode的保持一致 + * @param radixs 自定进制,需要和encode的保持一致 * @param encodeStr 需要转换成十进制的字符串 * @return long */ diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ServiceLoaderUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ServiceLoaderUtil.java index 0b60f9940..7c28c655b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/ServiceLoaderUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/ServiceLoaderUtil.java @@ -23,7 +23,7 @@ import java.util.ServiceLoader; public class ServiceLoaderUtil { /** - * 加载第一个可用服务,如果用户定义了多个接口实现类,只获取第一个不报错的服务。 + * 。加载第一个可用服务,如果用户定义了多个接口实现类,只获取第一个不报错的服务 * * @param 接口类型 * @param clazz 服务接口 diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpDownloader.java b/hutool-http/src/main/java/cn/hutool/http/HttpDownloader.java new file mode 100644 index 000000000..51aca79bb --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/HttpDownloader.java @@ -0,0 +1,105 @@ +package cn.hutool.http; + +import cn.hutool.core.io.FastByteArrayOutputStream; +import cn.hutool.core.io.StreamProgress; +import cn.hutool.core.lang.Assert; + +import java.io.File; +import java.io.OutputStream; +import java.nio.charset.Charset; + +/** + * 下载封装,下载统一使用{@code GET}请求,默认支持30x跳转 + * + * @since 5.6.4 + * @author looly + */ +public class HttpDownloader { + + /** + * 下载远程文本 + * + * @param url 请求的url + * @param customCharset 自定义的字符集,可以使用{@code CharsetUtil#charset} 方法转换 + * @param streamPress 进度条 {@link StreamProgress} + * @return 文本 + */ + public static String downloadString(String url, Charset customCharset, StreamProgress streamPress) { + final FastByteArrayOutputStream out = new FastByteArrayOutputStream(); + download(url, out, true, streamPress); + return null == customCharset ? out.toString() : out.toString(customCharset); + } + + /** + * 下载远程文件数据,支持30x跳转 + * + * @param url 请求的url + * @return 文件数据 + */ + public static byte[] downloadBytes(String url) { + return requestDownload(url, -1).bodyBytes(); + } + + /** + * 下载远程文件 + * + * @param url 请求的url + * @param destFile 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名 + * @param timeout 超时,单位毫秒,-1表示默认超时 + * @param streamProgress 进度条 + * @return 文件大小 + */ + public static long downloadFile(String url, File destFile, int timeout, StreamProgress streamProgress) { + return requestDownload(url, timeout).writeBody(destFile, streamProgress); + } + + /** + * 下载远程文件,返回文件 + * + * @param url 请求的url + * @param destFile 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名 + * @param timeout 超时,单位毫秒,-1表示默认超时 + * @param streamProgress 进度条 + * @return 文件 + */ + public static File downloadForFile(String url, File destFile, int timeout, StreamProgress streamProgress) { + return requestDownload(url, timeout).writeBodyForFile(destFile, streamProgress); + } + + /** + * 下载远程文件 + * + * @param url 请求的url + * @param out 将下载内容写到输出流中 {@link OutputStream} + * @param isCloseOut 是否关闭输出流 + * @param streamProgress 进度条 + * @return 文件大小 + */ + public static long download(String url, OutputStream out, boolean isCloseOut, StreamProgress streamProgress) { + Assert.notNull(out, "[out] is null !"); + + return requestDownload(url, -1).writeBody(out, isCloseOut, streamProgress); + } + + /** + * 请求下载文件 + * + * @param url 请求下载文件地址 + * @param timeout 超时时间 + * @return HttpResponse + * @since 5.4.1 + */ + private static HttpResponse requestDownload(String url, int timeout) { + Assert.notBlank(url, "[url] is blank !"); + + final HttpResponse response = HttpUtil.createGet(url, true) + .timeout(timeout) + .executeAsync(); + + if (response.isOk()) { + return response; + } + + throw new HttpException("Server response error with status code: [{}]", response.getStatus()); + } +} diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java b/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java index 71528b3d6..060cca9a8 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java @@ -278,10 +278,27 @@ public class HttpResponse extends HttpBase implements Closeable { Assert.notNull(destFile, "[destFile] is null!"); final File outFile = completeFileNameFromHeader(destFile); - final OutputStream outputStream = FileUtil.getOutputStream(outFile); - return writeBody(outputStream, true, streamProgress); + return writeBody(FileUtil.getOutputStream(outFile), true, streamProgress); } + /** + * 将响应内容写出到文件
+ * 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出
+ * 写出后会关闭Http流(异步模式) + * + * @param destFile 写出到的文件 + * @param streamProgress 进度显示接口,通过实现此接口显示下载进度 + * @return 写出的文件 + * @since 5.6.4 + */ + public File writeBodyForFile(File destFile, StreamProgress streamProgress) { + Assert.notNull(destFile, "[destFile] is null!"); + + final File outFile = completeFileNameFromHeader(destFile); + writeBody(FileUtil.getOutputStream(outFile), true, streamProgress); + + return outFile; + } /** * 将响应内容写出到文件
diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java index c24a1e5f8..9384413ef 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java @@ -2,11 +2,9 @@ package cn.hutool.http; import cn.hutool.core.codec.Base64; 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.lang.Assert; import cn.hutool.core.map.MapUtil; import cn.hutool.core.net.url.UrlQuery; import cn.hutool.core.text.StrBuilder; @@ -85,7 +83,19 @@ public class HttpUtil { * @since 3.2.0 */ public static HttpRequest createGet(String url) { - return HttpRequest.get(url); + return createGet(url, false); + } + + /** + * 创建Http GET请求对象 + * + * @param url 请求的URL,可以使HTTP或者HTTPS + * @param isFollowRedirects 是否打开重定向 + * @return {@link HttpRequest} + * @since 5.6.4 + */ + public static HttpRequest createGet(String url, boolean isFollowRedirects) { + return HttpRequest.get(url).setFollowRedirects(isFollowRedirects); } /** @@ -249,13 +259,7 @@ public class HttpUtil { * @return 文本 */ public static String downloadString(String url, Charset customCharset, StreamProgress streamPress) { - if (StrUtil.isBlank(url)) { - throw new NullPointerException("[url] is null!"); - } - - FastByteArrayOutputStream out = new FastByteArrayOutputStream(); - download(url, out, true, streamPress); - return null == customCharset ? out.toString() : out.toString(customCharset); + return HttpDownloader.downloadString(url, customCharset, streamPress); } /** @@ -316,63 +320,59 @@ public class HttpUtil { * @since 4.0.4 */ public static long downloadFile(String url, File destFile, int timeout, StreamProgress streamProgress) { - return requestDownloadFile(url, destFile, timeout).writeBody(destFile, streamProgress); + return HttpDownloader.downloadFile(url, destFile, timeout, streamProgress); } - + /** * 下载远程文件 * * @param url 请求的url * @param dest 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名 - * * @return 下载的文件对象 * @since 5.4.1 */ public static File downloadFileFromUrl(String url, String dest) { return downloadFileFromUrl(url, FileUtil.file(dest)); } - + /** * 下载远程文件 * * @param url 请求的url * @param destFile 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名 - * * @return 下载的文件对象 * @since 5.4.1 */ public static File downloadFileFromUrl(String url, File destFile) { return downloadFileFromUrl(url, destFile, null); } - + /** * 下载远程文件 * * @param url 请求的url * @param destFile 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名 * @param timeout 超时,单位毫秒,-1表示默认超时 - * * @return 下载的文件对象 * @since 5.4.1 */ public static File downloadFileFromUrl(String url, File destFile, int timeout) { return downloadFileFromUrl(url, destFile, timeout, null); } - + /** * 下载远程文件 * * @param url 请求的url * @param destFile 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名 * @param streamProgress 进度条 - * * @return 下载的文件对象 * @since 5.4.1 */ public static File downloadFileFromUrl(String url, File destFile, StreamProgress streamProgress) { return downloadFileFromUrl(url, destFile, -1, streamProgress); } - + /** * 下载远程文件 * @@ -380,47 +380,19 @@ public class HttpUtil { * @param destFile 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名 * @param timeout 超时,单位毫秒,-1表示默认超时 * @param streamProgress 进度条 - * * @return 下载的文件对象 * @since 5.4.1 */ public static File downloadFileFromUrl(String url, File destFile, int timeout, StreamProgress streamProgress) { - HttpResponse response = requestDownloadFile(url, destFile, timeout); - - final File outFile = response.completeFileNameFromHeader(destFile); - long writeBytes = response.writeBody(outFile, streamProgress); - return outFile; + return HttpDownloader.downloadForFile(url, destFile, timeout, streamProgress); } - - /** - * 请求下载文件 - * - * @param url 请求下载文件地址 - * @param destFile 目标目录或者目标文件 - * @param timeout 超时时间 - * - * @return HttpResponse - * @since 5.4.1 - */ - private static HttpResponse requestDownloadFile(String url, File destFile, int timeout) { - Assert.notBlank(url, "[url] is blank !"); - Assert.notNull(destFile, "[destFile] is null !"); - final HttpResponse response = HttpRequest.get(url).timeout(timeout).executeAsync(); - if (response.isOk()) { - return response; - } - - throw new HttpException("Server response error with status code: [{}]", response.getStatus()); - } - /** * 下载远程文件 * * @param url 请求的url * @param out 将下载内容写到输出流中 {@link OutputStream} * @param isCloseOut 是否关闭输出流 - * * @return 文件大小 */ public static long download(String url, OutputStream out, boolean isCloseOut) { @@ -437,40 +409,18 @@ public class HttpUtil { * @return 文件大小 */ public static long download(String url, OutputStream out, boolean isCloseOut, StreamProgress streamProgress) { - if (StrUtil.isBlank(url)) { - throw new NullPointerException("[url] is null!"); - } - if (null == out) { - throw new NullPointerException("[out] is null!"); - } - - final HttpResponse response = HttpRequest.get(url).executeAsync(); - if (!response.isOk()) { - throw new HttpException("Server response error with status code: [{}]", response.getStatus()); - } - return response.writeBody(out, isCloseOut, streamProgress); + return HttpDownloader.download(url, out, isCloseOut, streamProgress); } - + /** * 下载远程文件数据,支持30x跳转 * * @param url 请求的url - * * @return 文件数据 - * * @since 5.3.6 */ public static byte[] downloadBytes(String url) { - if (StrUtil.isBlank(url)) { - throw new NullPointerException("[url] is null!"); - } - - final HttpResponse response = HttpRequest.get(url) - .setFollowRedirects(true).executeAsync(); - if (!response.isOk()) { - throw new HttpException("Server response error with status code: [{}]", response.getStatus()); - } - return response.bodyBytes(); + return HttpDownloader.downloadBytes(url); } /** @@ -880,11 +830,11 @@ public class HttpUtil { * * @param username 账号 * @param password 密码 - * @param charset 编码(如果账号或密码中有非ASCII字符适用) + * @param charset 编码(如果账号或密码中有非ASCII字符适用) * @return 密码验证信息 * @since 5.4.6 */ - public static String buildBasicAuth(String username, String password, Charset charset){ + public static String buildBasicAuth(String username, String password, Charset charset) { final String data = username.concat(":").concat(password); return "Basic " + Base64.encode(data, charset); }