Browse Source

add downloadFileFromUrl

Looly 5 years ago
parent
commit
9eec8ec6e0

+ 1 - 0
CHANGELOG.md

@@ -8,6 +8,7 @@
 ### 新特性
 * 【core  】     StrUtil增加firstNonXXX方法(issue#1020@Github)
 * 【core  】     BeanCopier修改规则,可选bean拷贝空字段报错问题(pr#160@Gitee)
+* 【http  】     HttpUtil增加downloadFileFromUrl(pr#1023@Github)
 
 ### Bug修复#
 * 【poi   】     修复ExcelBase.isXlsx方法判断问题(issue#I1S502@Gitee)

+ 81 - 62
hutool-http/src/main/java/cn/hutool/http/HttpResponse.java

@@ -1,14 +1,26 @@
 package cn.hutool.http;
 
 import cn.hutool.core.convert.Convert;
-import cn.hutool.core.io.*;
+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.lang.Assert;
 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.*;
+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.net.HttpCookie;
 import java.nio.charset.Charset;
 import java.util.List;
@@ -17,32 +29,43 @@ import java.util.Map.Entry;
 /**
  * Http响应类<br>
  * 非线程安全对象
- * 
- * @author Looly
  *
+ * @author Looly
  */
 public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
 
-	/** 持有连接对象 */
+	/**
+	 * 持有连接对象
+	 */
 	protected HttpConnection httpConnection;
-	/** Http请求原始流 */
+	/**
+	 * Http请求原始流
+	 */
 	protected InputStream in;
-	/** 是否异步,异步下只持有流,否则将在初始化时直接读取body内容 */
+	/**
+	 * 是否异步,异步下只持有流,否则将在初始化时直接读取body内容
+	 */
 	private volatile boolean isAsync;
-	/** 响应状态码 */
+	/**
+	 * 响应状态码
+	 */
 	protected int status;
-	/** 是否忽略读取Http响应体 */
+	/**
+	 * 是否忽略读取Http响应体
+	 */
 	private final boolean ignoreBody;
-	/** 从响应中获取的编码 */
+	/**
+	 * 从响应中获取的编码
+	 */
 	private Charset charsetFromResponse;
 
 	/**
 	 * 构造
-	 * 
+	 *
 	 * @param httpConnection {@link HttpConnection}
-	 * @param charset 编码,从请求编码中获取默认编码
-	 * @param isAsync 是否异步
-	 * @param isIgnoreBody 是否忽略读取响应体
+	 * @param charset        编码,从请求编码中获取默认编码
+	 * @param isAsync        是否异步
+	 * @param isIgnoreBody   是否忽略读取响应体
 	 * @since 3.1.2
 	 */
 	protected HttpResponse(HttpConnection httpConnection, Charset charset, boolean isAsync, boolean isIgnoreBody) {
@@ -55,7 +78,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
 
 	/**
 	 * 获取状态码
-	 * 
+	 *
 	 * @return 状态码
 	 */
 	public int getStatus() {
@@ -64,7 +87,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
 
 	/**
 	 * 请求是否成功,判断依据为:状态码范围在200~299内。
-	 * 
+	 *
 	 * @return 是否成功请求
 	 * @since 4.1.9
 	 */
@@ -76,7 +99,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
 	 * 同步<br>
 	 * 如果为异步状态,则暂时不读取服务器中响应的内容,而是持有Http链接的{@link InputStream}。<br>
 	 * 当调用此方法时,异步状态转为同步状态,此时从Http链接流中读取body内容并暂存在内容中。如果已经是同步状态,则不进行任何操作。
-	 * 
+	 *
 	 * @return this
 	 */
 	public HttpResponse sync() {
@@ -84,9 +107,10 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
 	}
 
 	// ---------------------------------------------------------------- Http Response Header start
+
 	/**
 	 * 获取内容编码
-	 * 
+	 *
 	 * @return String
 	 */
 	public String contentEncoding() {
@@ -95,7 +119,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
 
 	/**
 	 * 是否为gzip压缩过的内容
-	 * 
+	 *
 	 * @return 是否为gzip压缩过的内容
 	 */
 	public boolean isGzip() {
@@ -105,7 +129,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
 
 	/**
 	 * 是否为zlib(Defalte)压缩过的内容
-	 * 
+	 *
 	 * @return 是否为zlib(Defalte)压缩过的内容
 	 * @since 4.5.7
 	 */
@@ -116,7 +140,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
 
 	/**
 	 * 是否为Transfer-Encoding:Chunked的内容
-	 * 
+	 *
 	 * @return 是否为Transfer-Encoding:Chunked的内容
 	 * @since 4.6.2
 	 */
@@ -127,7 +151,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
 
 	/**
 	 * 获取本次请求服务器返回的Cookie信息
-	 * 
+	 *
 	 * @return Cookie字符串
 	 * @since 3.1.1
 	 */
@@ -137,7 +161,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
 
 	/**
 	 * 获取Cookie
-	 * 
+	 *
 	 * @return Cookie列表
 	 * @since 3.1.1
 	 */
@@ -147,7 +171,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
 
 	/**
 	 * 获取Cookie
-	 * 
+	 *
 	 * @param name Cookie名
 	 * @return {@link HttpCookie}
 	 * @since 4.1.4
@@ -166,7 +190,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
 
 	/**
 	 * 获取Cookie值
-	 * 
+	 *
 	 * @param name Cookie名
 	 * @return Cookie值
 	 * @since 4.1.4
@@ -178,12 +202,13 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
 	// ---------------------------------------------------------------- Http Response Header end
 
 	// ---------------------------------------------------------------- Body start
+
 	/**
 	 * 获得服务区响应流<br>
 	 * 异步模式下获取Http原生流,同步模式下获取获取到的在内存中的副本<br>
 	 * 如果想在同步模式下获取流,请先调用{@link #sync()}方法强制同步<br>
 	 * 流获取后处理完毕需关闭此类
-	 * 
+	 *
 	 * @return 响应流
 	 */
 	public InputStream bodyStream() {
@@ -196,7 +221,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
 	/**
 	 * 获取响应流字节码<br>
 	 * 此方法会转为同步模式
-	 * 
+	 *
 	 * @return byte[]
 	 */
 	public byte[] bodyBytes() {
@@ -206,7 +231,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
 
 	/**
 	 * 获取响应主体
-	 * 
+	 *
 	 * @return String
 	 * @throws HttpException 包装IO异常
 	 */
@@ -218,9 +243,9 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
 	 * 将响应内容写出到{@link OutputStream}<br>
 	 * 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出<br>
 	 * 写出后会关闭Http流(异步模式)
-	 * 
-	 * @param out 写出的流
-	 * @param isCloseOut 是否关闭输出流
+	 *
+	 * @param out            写出的流
+	 * @param isCloseOut     是否关闭输出流
 	 * @param streamProgress 进度显示接口,通过实现此接口显示下载进度
 	 * @return 写出bytes数
 	 * @since 3.3.2
@@ -238,7 +263,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
 			}
 		}
 	}
-	
+
 	/**
 	 * 将响应内容写出到文件<br>
 	 * 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出<br>
@@ -246,31 +271,25 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
 	 *
 	 * @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!");
-		}
-		
-		File outFile = completeFileNameFromHeader(destFile);
-		OutputStream outputStream = FileUtil.getOutputStream(outFile);
+		Assert.notNull(destFile, "[destFile] is null!");
+
+		final File outFile = completeFileNameFromHeader(destFile);
+		final 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) {
@@ -281,7 +300,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
 	 * 将响应内容写出到文件<br>
 	 * 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出<br>
 	 * 写出后会关闭Http流(异步模式)
-	 * 
+	 *
 	 * @param destFilePath 写出到的文件的路径
 	 * @return 写出bytes数
 	 * @since 3.3.2
@@ -298,7 +317,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
 		// 关闭连接
 		this.httpConnection.disconnectQuietly();
 	}
-	
+
 	@Override
 	public String toString() {
 		StringBuilder sb = StrUtil.builder();
@@ -306,26 +325,26 @@ 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 保存的文件
+	 * @since 5.4.1
 	 */
 	public File completeFileNameFromHeader(File destFile) {
-		if (!destFile.isDirectory()) {
+		if (false == destFile.isDirectory()) {
 			// 非目录直接返回
 			return destFile;
 		}
-		
+
 		// 从头信息中获取文件名
 		String fileName = getFileNameFromDisposition();
 		if (StrUtil.isBlank(fileName)) {
@@ -339,9 +358,9 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
 		}
 		return FileUtil.file(destFile, fileName);
 	}
-	
+
 	// ---------------------------------------------------------------- Private method start
-	
+
 	/**
 	 * 初始化Http响应,并在报错时关闭连接。<br>
 	 * 初始化包括:
@@ -351,7 +370,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
 	 * 2、读取头信息
 	 * 3、持有Http流,并不关闭流
 	 * </pre>
-	 * 
+	 *
 	 * @return this
 	 * @throws HttpException IO异常
 	 */
@@ -368,13 +387,13 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
 	/**
 	 * 初始化Http响应<br>
 	 * 初始化包括:
-	 * 
+	 *
 	 * <pre>
 	 * 1、读取Http状态
 	 * 2、读取头信息
 	 * 3、持有Http流,并不关闭流
 	 * </pre>
-	 * 
+	 *
 	 * @return this
 	 * @throws HttpException IO异常
 	 */
@@ -417,7 +436,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
 
 	/**
 	 * 读取主体,忽略EOFException异常
-	 * 
+	 *
 	 * @param in 输入流
 	 * @throws IORuntimeException IO异常
 	 */
@@ -444,14 +463,14 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
 	/**
 	 * 强制同步,用于初始化<br>
 	 * 强制同步后变化如下:
-	 * 
+	 *
 	 * <pre>
 	 * 1、读取body内容到内存
 	 * 2、异步状态设为false(变为同步状态)
 	 * 3、关闭Http流
 	 * 4、断开与服务器连接
 	 * </pre>
-	 * 
+	 *
 	 * @return this
 	 */
 	private HttpResponse forceSync() {
@@ -473,7 +492,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
 		}
 		return this;
 	}
-	
+
 	/**
 	 * 从Content-Disposition头中获取文件名
 	 *
@@ -490,6 +509,6 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
 		}
 		return fileName;
 	}
-	
+
 	// ---------------------------------------------------------------- Private method end
 }

+ 26 - 18
hutool-http/src/main/java/cn/hutool/http/HttpUtil.java

@@ -5,10 +5,15 @@ 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;
-import cn.hutool.core.util.*;
+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;
@@ -310,8 +315,7 @@ 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);
+		return requestDownloadFile(url, destFile, timeout).writeBody(destFile, streamProgress);
 	}
 	
 	/**
@@ -320,7 +324,8 @@ public class HttpUtil {
 	 * @param url  请求的url
 	 * @param dest 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名
 	 *
-	 * @return 文件
+	 * @return 下载的文件对象
+	 * @since 5.4.1
 	 */
 	public static File downloadFileFromUrl(String url, String dest) {
 		return downloadFileFromUrl(url, FileUtil.file(dest));
@@ -332,7 +337,8 @@ public class HttpUtil {
 	 * @param url      请求的url
 	 * @param destFile 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名
 	 *
-	 * @return 文件
+	 * @return 下载的文件对象
+	 * @since 5.4.1
 	 */
 	public static File downloadFileFromUrl(String url, File destFile) {
 		return downloadFileFromUrl(url, destFile, null);
@@ -345,7 +351,8 @@ public class HttpUtil {
 	 * @param destFile 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名
 	 * @param timeout  超时,单位毫秒,-1表示默认超时
 	 *
-	 * @return 文件
+	 * @return 下载的文件对象
+	 * @since 5.4.1
 	 */
 	public static File downloadFileFromUrl(String url, File destFile, int timeout) {
 		return downloadFileFromUrl(url, destFile, timeout, null);
@@ -358,7 +365,8 @@ public class HttpUtil {
 	 * @param destFile       目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名
 	 * @param streamProgress 进度条
 	 *
-	 * @return 文件
+	 * @return 下载的文件对象
+	 * @since 5.4.1
 	 */
 	public static File downloadFileFromUrl(String url, File destFile, StreamProgress streamProgress) {
 		return downloadFileFromUrl(url, destFile, -1, streamProgress);
@@ -372,12 +380,13 @@ public class HttpUtil {
 	 * @param timeout        超时,单位毫秒,-1表示默认超时
 	 * @param streamProgress 进度条
 	 *
-	 * @return 文件
+	 * @return 下载的文件对象
+	 * @since 5.4.1
 	 */
 	public static File downloadFileFromUrl(String url, File destFile, int timeout, StreamProgress streamProgress) {
 		HttpResponse response = requestDownloadFile(url, destFile, timeout);
 		
-		File outFile = response.completeFileNameFromHeader(destFile);
+		final File outFile = response.completeFileNameFromHeader(destFile);
 		long writeBytes = response.writeBody(outFile, streamProgress);
 		return outFile;
 	}
@@ -390,19 +399,18 @@ public class HttpUtil {
 	 * @param timeout  超时时间
 	 *
 	 * @return HttpResponse
+	 * @since 5.4.1
 	 */
 	private static HttpResponse requestDownloadFile(String url, File destFile, int timeout) {
-		if (StrUtil.isBlank(url)) {
-			throw new NullPointerException("[url] is null!");
-		}
-		if (null == destFile) {
-			throw new NullPointerException("[destFile] is null!");
-		}
+		Assert.notBlank(url, "[url] is blank !");
+		Assert.notNull(url, "[destFile] is null !");
+
 		final HttpResponse response = HttpRequest.get(url).timeout(timeout).executeAsync();
-		if (!response.isOk()) {
-			throw new HttpException("Server response error with status code: [{}]", response.getStatus());
+		if (response.isOk()) {
+			return response;
 		}
-		return response;
+
+		throw new HttpException("Server response error with status code: [{}]", response.getStatus());
 	}
 	
 	/**