Merge pull request #1023 from hdfg159/v5-dev

add HttpUtil download file return file
This commit is contained in:
Golden Looly 2020-08-20 16:36:02 +08:00 committed by GitHub
commit 9a94f2e750
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 277 additions and 58 deletions

View File

@ -1,25 +1,14 @@
package cn.hutool.http;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FastByteArrayOutputStream;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.StreamProgress;
import cn.hutool.core.io.*;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.http.cookie.GlobalCookieManager;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.EOFException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.*;
import java.net.HttpCookie;
import java.nio.charset.Charset;
import java.util.List;
@ -249,46 +238,39 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
}
}
}
/**
* 将响应内容写出到文件<br>
* 异步模式下直接读取Http流写出同步模式下将存储在内存中的响应内容写出<br>
* 写出后会关闭Http流异步模式
*
* @param destFile 写出到的文件
*
* @param destFile 写出到的文件
* @param streamProgress 进度显示接口通过实现此接口显示下载进度
*
* @return 写出bytes数
*
* @since 3.3.2
*/
public long writeBody(File destFile, StreamProgress streamProgress) {
if (null == destFile) {
throw new NullPointerException("[destFile] is null!");
}
if (destFile.isDirectory()) {
// 从头信息中获取文件名
String fileName = getFileNameFromDisposition();
if (StrUtil.isBlank(fileName)) {
final String path = this.httpConnection.getUrl().getPath();
// 从路径中获取文件名
fileName = StrUtil.subSuf(path, path.lastIndexOf('/') + 1);
if (StrUtil.isBlank(fileName)) {
// 编码后的路径做为文件名
fileName = URLUtil.encodeQuery(path, CharsetUtil.CHARSET_UTF_8);
}
}
destFile = FileUtil.file(destFile, fileName);
}
return writeBody(FileUtil.getOutputStream(destFile), true, streamProgress);
File outFile = completeFileNameFromHeader(destFile);
OutputStream outputStream = FileUtil.getOutputStream(outFile);
return writeBody(outputStream, true, streamProgress);
}
/**
* 将响应内容写出到文件<br>
* 异步模式下直接读取Http流写出同步模式下将存储在内存中的响应内容写出<br>
* 写出后会关闭Http流异步模式
*
*
* @param destFile 写出到的文件
*
* @return 写出bytes数
*
* @since 3.3.2
*/
public long writeBody(File destFile) {
@ -316,7 +298,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
// 关闭连接
this.httpConnection.disconnectQuietly();
}
@Override
public String toString() {
StringBuilder sb = StrUtil.builder();
@ -324,18 +306,46 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
for (Entry<String, List<String>> entry : this.headers.entrySet()) {
sb.append(" ").append(entry).append(StrUtil.CRLF);
}
sb.append("Response Body: ").append(StrUtil.CRLF);
sb.append(" ").append(this.body()).append(StrUtil.CRLF);
return sb.toString();
}
/**
* 从响应头补全下载文件名
*
* @param destFile 目标文件夹或者目标文件
*
* @return File 保存的文件
*/
public File completeFileNameFromHeader(File destFile) {
if (!destFile.isDirectory()) {
// 非目录直接返回
return destFile;
}
// 从头信息中获取文件名
String fileName = getFileNameFromDisposition();
if (StrUtil.isBlank(fileName)) {
final String path = httpConnection.getUrl().getPath();
// 从路径中获取文件名
fileName = StrUtil.subSuf(path, path.lastIndexOf('/') + 1);
if (StrUtil.isBlank(fileName)) {
// 编码后的路径做为文件名
fileName = URLUtil.encodeQuery(path, CharsetUtil.CHARSET_UTF_8);
}
}
return FileUtil.file(destFile, fileName);
}
// ---------------------------------------------------------------- Private method start
/**
* 初始化Http响应并在报错时关闭连接<br>
* 初始化包括
*
*
* <pre>
* 1读取Http状态
* 2读取头信息
@ -463,10 +473,10 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
}
return this;
}
/**
* 从Content-Disposition头中获取文件名
*
*
* @return 文件名empty表示无
*/
private String getFileNameFromDisposition() {
@ -480,5 +490,6 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
}
return fileName;
}
// ---------------------------------------------------------------- Private method end
}

View File

@ -8,11 +8,7 @@ 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.core.util.*;
import cn.hutool.http.server.SimpleServer;
import java.io.File;
@ -314,6 +310,88 @@ public class HttpUtil {
* @since 4.0.4
*/
public static long downloadFile(String url, File destFile, int timeout, StreamProgress streamProgress) {
HttpResponse response = requestDownloadFile(url, destFile, timeout);
return response.writeBody(destFile, streamProgress);
}
/**
* 下载远程文件
*
* @param url 请求的url
* @param dest 目标文件或目录当为目录时取URL中的文件名取不到使用编码后的URL做为文件名
*
* @return 文件
*/
public static File downloadFileFromUrl(String url, String dest) {
return downloadFileFromUrl(url, FileUtil.file(dest));
}
/**
* 下载远程文件
*
* @param url 请求的url
* @param destFile 目标文件或目录当为目录时取URL中的文件名取不到使用编码后的URL做为文件名
*
* @return 文件
*/
public static File downloadFileFromUrl(String url, File destFile) {
return downloadFileFromUrl(url, destFile, null);
}
/**
* 下载远程文件
*
* @param url 请求的url
* @param destFile 目标文件或目录当为目录时取URL中的文件名取不到使用编码后的URL做为文件名
* @param timeout 超时单位毫秒-1表示默认超时
*
* @return 文件
*/
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 文件
*/
public static File downloadFileFromUrl(String url, File destFile, StreamProgress streamProgress) {
return downloadFileFromUrl(url, destFile, -1, streamProgress);
}
/**
* 下载远程文件
*
* @param url 请求的url
* @param destFile 目标文件或目录当为目录时取URL中的文件名取不到使用编码后的URL做为文件名
* @param timeout 超时单位毫秒-1表示默认超时
* @param streamProgress 进度条
*
* @return 文件
*/
public static File downloadFileFromUrl(String url, File destFile, int timeout, StreamProgress streamProgress) {
HttpResponse response = requestDownloadFile(url, destFile, timeout);
File outFile = response.completeFileNameFromHeader(destFile);
long writeBytes = response.writeBody(outFile, streamProgress);
return outFile;
}
/**
* 请求下载文件
*
* @param url 请求下载文件地址
* @param destFile 目标目录或者目标文件
* @param timeout 超时时间
*
* @return HttpResponse
*/
private static HttpResponse requestDownloadFile(String url, File destFile, int timeout) {
if (StrUtil.isBlank(url)) {
throw new NullPointerException("[url] is null!");
}
@ -321,18 +399,19 @@ public class HttpUtil {
throw new NullPointerException("[destFile] is null!");
}
final HttpResponse response = HttpRequest.get(url).timeout(timeout).executeAsync();
if (false == response.isOk()) {
if (!response.isOk()) {
throw new HttpException("Server response error with status code: [{}]", response.getStatus());
}
return response.writeBody(destFile, streamProgress);
return response;
}
/**
* 下载远程文件
*
* @param url 请求的url
* @param out 将下载内容写到输出流中 {@link OutputStream}
* @param isCloseOut 是否关闭输出流
*
* @return 文件大小
*/
public static long download(String url, OutputStream out, boolean isCloseOut) {
@ -355,29 +434,31 @@ public class HttpUtil {
if (null == out) {
throw new NullPointerException("[out] is null!");
}
final HttpResponse response = HttpRequest.get(url).executeAsync();
if (false == response.isOk()) {
if (!response.isOk()) {
throw new HttpException("Server response error with status code: [{}]", response.getStatus());
}
return response.writeBody(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 (false == response.isOk()) {
if (!response.isOk()) {
throw new HttpException("Server response error with status code: [{}]", response.getStatus());
}
return response.bodyBytes();

View File

@ -1,13 +1,17 @@
package cn.hutool.http.test;
import org.junit.Ignore;
import org.junit.Test;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.StreamProgress;
import cn.hutool.core.lang.Console;
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.File;
import java.util.UUID;
/**
* 下载单元测试
@ -56,11 +60,134 @@ public class DownloadTest {
long speed = progressSize / (System.currentTimeMillis() - time) * 1000;
Console.log("已下载:{}, 速度:{}/s", FileUtil.readableFileSize(progressSize), FileUtil.readableFileSize(speed));
}
@Override
public void finish() {
Console.log("下载完成!");
}
});
}
@Test
@Ignore
public void downloadFileFromUrlTest1() {
File file = HttpUtil.downloadFileFromUrl("http://groovy-lang.org/changelogs/changelog-3.0.5.html", "d:/download/temp");
Assert.assertNotNull(file);
Assert.assertTrue(file.isFile());
Assert.assertTrue(file.length() > 0);
}
@Test
@Ignore
public void downloadFileFromUrlTest2() {
File file = null;
try {
file = HttpUtil.downloadFileFromUrl("https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.4.0/hutool-all-5.4.0-sources.jar", FileUtil.file("d:/download/temp"), 1, new StreamProgress() {
@Override
public void start() {
System.out.println("start");
}
@Override
public void progress(long progressSize) {
System.out.println("download size:" + progressSize);
}
@Override
public void finish() {
System.out.println("end");
}
});
Assert.assertNotNull(file);
Assert.assertTrue(file.exists());
Assert.assertTrue(file.isFile());
Assert.assertTrue(file.length() > 0);
Assert.assertTrue(file.getName().length() > 0);
} catch (Exception e) {
Assert.assertTrue(e instanceof IORuntimeException);
} finally {
FileUtil.del(file);
}
}
@Test
@Ignore
public void downloadFileFromUrlTest3() {
File file = null;
try {
file = HttpUtil.downloadFileFromUrl("https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.4.0/hutool-all-5.4.0-sources.jar", FileUtil.file("d:/download/temp"), new StreamProgress() {
@Override
public void start() {
System.out.println("start");
}
@Override
public void progress(long progressSize) {
System.out.println("download size:" + progressSize);
}
@Override
public void finish() {
System.out.println("end");
}
});
Assert.assertNotNull(file);
Assert.assertTrue(file.exists());
Assert.assertTrue(file.isFile());
Assert.assertTrue(file.length() > 0);
Assert.assertTrue(file.getName().length() > 0);
} finally {
FileUtil.del(file);
}
}
@Test
@Ignore
public void downloadFileFromUrlTest4() {
File file = null;
try {
file = HttpUtil.downloadFileFromUrl("http://groovy-lang.org/changelogs/changelog-3.0.5.html", FileUtil.file("d:/download/temp"), 1);
Assert.assertNotNull(file);
Assert.assertTrue(file.exists());
Assert.assertTrue(file.isFile());
Assert.assertTrue(file.length() > 0);
Assert.assertTrue(file.getName().length() > 0);
} catch (Exception e) {
Assert.assertTrue(e instanceof IORuntimeException);
} finally {
FileUtil.del(file);
}
}
@Test
@Ignore
public void downloadFileFromUrlTest5() {
File file = null;
try {
file = HttpUtil.downloadFileFromUrl("http://groovy-lang.org/changelogs/changelog-3.0.5.html", FileUtil.file("d:/download/temp", UUID.randomUUID().toString()));
Assert.assertNotNull(file);
Assert.assertTrue(file.exists());
Assert.assertTrue(file.isFile());
Assert.assertTrue(file.length() > 0);
} finally {
FileUtil.del(file);
}
File file1 = null;
try {
file1 = HttpUtil.downloadFileFromUrl("http://groovy-lang.org/changelogs/changelog-3.0.5.html", FileUtil.file("d:/download/temp"));
Assert.assertNotNull(file1);
Assert.assertTrue(file1.exists());
Assert.assertTrue(file1.isFile());
Assert.assertTrue(file1.length() > 0);
} finally {
FileUtil.del(file1);
}
}
}