From 9cc75d5e34c4f273414f975651c46d3b7449e15b Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 3 Dec 2024 00:13:49 +0800 Subject: [PATCH] fix redirect bug --- .../hutool/http/client/ClientConfig.java | 26 -- .../hutool/http/client/HttpDownloader.java | 232 ++++++++++-------- .../dromara/hutool/http/client/Request.java | 8 +- .../hutool/http/client/RequestContext.java | 63 +++++ .../engine/httpclient4/HttpClient4Engine.java | 7 - .../engine/httpclient5/HttpClient5Engine.java | 14 +- .../client/engine/jdk/JdkClientEngine.java | 45 ++-- .../client/engine/jdk/JdkHttpConnection.java | 4 - .../client/engine/okhttp/OkHttpEngine.java | 5 +- .../hutool/http/client/DownloadTest.java | 84 ++----- 10 files changed, 255 insertions(+), 233 deletions(-) create mode 100644 hutool-http/src/main/java/org/dromara/hutool/http/client/RequestContext.java diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/client/ClientConfig.java b/hutool-http/src/main/java/org/dromara/hutool/http/client/ClientConfig.java index b6c580fee..c96a1d1f8 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/client/ClientConfig.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/client/ClientConfig.java @@ -56,10 +56,6 @@ public class ClientConfig { * 代理 */ private ProxyInfo proxy; - /** - * 是否遇到响应状态码3xx时自动重定向请求 - */ - private boolean followRedirects; /** * 是否使用引擎默认的Cookie管理器,默认为true
* 默认情况下每个客户端维护一个自己的Cookie管理器,这个管理器用于在多次请求中记录并自动附带Cookie信息
@@ -215,28 +211,6 @@ public class ClientConfig { return this; } - /** - * 是否遇到响应状态码3xx时自动重定向请求
- * 注意:当打开客户端级别的自动重定向,则{@link Request#maxRedirects()}无效 - * - * @return 是否遇到响应状态码3xx时自动重定向请求 - */ - public boolean isFollowRedirects() { - return followRedirects; - } - - /** - * 设置是否遇到响应状态码3xx时自动重定向请求
- * 注意:当打开客户端级别的自动重定向,则{@link Request#maxRedirects()}无效 - * - * @param followRedirects 是否遇到响应状态码3xx时自动重定向请求 - * @return this - */ - public ClientConfig setFollowRedirects(final boolean followRedirects) { - this.followRedirects = followRedirects; - return this; - } - /** * 是否使用引擎默认的Cookie管理器,默认为true
* 默认情况下每个客户端维护一个自己的Cookie管理器,这个管理器用于在多次请求中记录并自动附带Cookie信息
diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/client/HttpDownloader.java b/hutool-http/src/main/java/org/dromara/hutool/http/client/HttpDownloader.java index ac2baad05..02bb69964 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/client/HttpDownloader.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/client/HttpDownloader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2024 Hutool Team and hutool.cn + * Copyright (c) 2024 Hutool Team and hutool.cn * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,155 +16,189 @@ package org.dromara.hutool.http.client; +import org.dromara.hutool.core.io.IoUtil; import org.dromara.hutool.core.io.StreamProgress; -import org.dromara.hutool.core.io.stream.FastByteArrayOutputStream; -import org.dromara.hutool.core.lang.Assert; import org.dromara.hutool.http.HttpException; +import org.dromara.hutool.http.client.body.ResponseBody; +import org.dromara.hutool.http.client.engine.ClientEngine; import org.dromara.hutool.http.client.engine.ClientEngineFactory; import java.io.File; +import java.io.IOException; import java.io.OutputStream; -import java.nio.charset.Charset; +import java.util.Map; /** - * 下载封装,下载统一使用{@code GET}请求,默认支持30x跳转 + * HTTP下载器,两种使用方式:
+ * 1. 一次性使用: + *
{@code HttpDownloader.of(url).downloadFile(file)}
+ * 2. 多次下载复用: + *
{@code
+ *   HttpDownloader downloader = HttpDownloader.of(url).setCloseEngine(false);
+ *   downloader.downloadFile(file);
+ *   downloader.downloadFile(file2);
+ *   downloader.close();
+ * }
* * @author looly - * @since 5.6.4 + * @since 6.0.0 */ -@SuppressWarnings("resource") public class HttpDownloader { /** - * 下载远程文本 + * 创建下载器 * - * @param url 请求的url - * @param customCharset 自定义的字符集,可以使用{@code CharsetUtil#charset} 方法转换 - * @param streamPress 进度条 {@link StreamProgress} - * @return 文本 + * @param url 请求地址 + * @return 下载器 */ - public static String downloadString(final String url, final Charset customCharset, final StreamProgress streamPress) { - final FastByteArrayOutputStream out = new FastByteArrayOutputStream(); - download(url, out, true, streamPress); - return null == customCharset ? out.toString() : out.toString(customCharset); + public static HttpDownloader of(final String url) { + return new HttpDownloader(url); + } + + private final Request request; + private ClientConfig config; + private ClientEngine engine; + private StreamProgress streamProgress; + private boolean closeEngine = true; + + /** + * 构造 + * + * @param url 请求地址 + */ + public HttpDownloader(final String url) { + this.request = Request.of(url); } /** - * 下载远程文件数据,支持30x跳转 + * 设置请求头 * - * @param url 请求的url - * @return 文件数据 + * @param headers 请求头 + * @return this */ - public static byte[] downloadBytes(final String url) { - return downloadBytes(url, 0); + public HttpDownloader header(final Map headers) { + this.request.header(headers); + return this; } /** - * 下载远程文件数据,支持30x跳转 + * 设置配置 * - * @param url 请求的url - * @param timeout 超时毫秒数 - * @return 文件数据 - * @since 5.8.28 + * @param config 配置 + * @return this */ - public static byte[] downloadBytes(final String url, final int timeout) { - return requestDownload(url, timeout).bodyBytes(); + public HttpDownloader setConfig(final ClientConfig config) { + this.config = config; + return this; } /** - * 下载远程文件 + * 设置超时 * - * @param url 请求的url - * @param targetFileOrDir 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名 - * @return 文件 + * @param milliseconds 超时毫秒数 + * @return this */ - public static File downloadFile(final String url, final File targetFileOrDir) { - return downloadFile(url, targetFileOrDir, -1); + public HttpDownloader setTimeout(final int milliseconds) { + if (null == this.config) { + this.config = ClientConfig.of(); + } + this.config.setTimeout(milliseconds); + return this; } /** - * 下载远程文件 + * 设置引擎,用于自定义引擎 * - * @param url 请求的url - * @param targetFileOrDir 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名 - * @param timeout 超时,单位毫秒,-1表示默认超时 - * @return 文件 + * @param engine 引擎 + * @return this */ - public static File downloadFile(final String url, final File targetFileOrDir, final int timeout) { - Assert.notNull(targetFileOrDir, "[targetFileOrDir] is null !"); - return downloadFile(url, targetFileOrDir, timeout, null); + public HttpDownloader setEngine(final ClientEngine engine) { + this.engine = engine; + return this; } /** - * 下载远程文件 + * 设置进度条 * - * @param url 请求的url - * @param targetFileOrDir 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名 - * @param timeout 超时,单位毫秒,-1表示默认超时 - * @param streamProgress 进度条 - * @return 文件 - */ - public static File downloadFile(final String url, final File targetFileOrDir, final int timeout, final StreamProgress streamProgress) { - Assert.notNull(targetFileOrDir, "[targetFileOrDir] is null !"); - return requestDownload(url, timeout).body().write(targetFileOrDir, streamProgress); - } - - /** - * 下载文件-避免未完成的文件
- * 来自:https://gitee.com/dromara/hutool/pulls/407
- * 此方法原理是先在目标文件同级目录下创建临时文件,下载之,等下载完毕后重命名,避免因下载错误导致的文件不完整。 - * - * @param url 请求的url - * @param targetFileOrDir 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名 - * @param tempFileSuffix 临时文件后缀,默认".temp" - * @param timeout 超时,单位毫秒,-1表示默认超时 - * @param streamProgress 进度条 - * @return 文件 - * @since 5.7.12 - */ - public static File downloadFile(final String url, final File targetFileOrDir, final String tempFileSuffix, final int timeout, final StreamProgress streamProgress) { - Assert.notNull(targetFileOrDir, "[targetFileOrDir] is null !"); - return requestDownload(url, timeout).body().write(targetFileOrDir, tempFileSuffix, streamProgress); - } - - /** - * 下载远程文件 - * - * @param url 请求的url - * @param out 将下载内容写到输出流中 {@link OutputStream} - * @param isCloseOut 是否关闭输出流 * @param streamProgress 进度条 - * @return 文件大小 + * @return this */ - public static long download(final String url, final OutputStream out, final boolean isCloseOut, final StreamProgress streamProgress) { - Assert.notNull(out, "[out] is null !"); - return requestDownload(url, -1).body().write(out, isCloseOut, streamProgress); + public HttpDownloader setStreamProgress(final StreamProgress streamProgress) { + this.streamProgress = streamProgress; + return this; } /** - * 请求下载文件 + * 设置是否关闭引擎,默认为true,即自动关闭引擎 * - * @param url 请求下载文件地址 - * @param timeout 超时时间 - * @return HttpResponse - * @since 5.4.1 + * @param closeEngine 是否关闭引擎 + * @return this */ - private static Response requestDownload(final String url, final int timeout) { - Assert.notBlank(url, "[url] is blank !"); + public HttpDownloader setCloseEngine(final boolean closeEngine) { + this.closeEngine = closeEngine; + return this; + } - final ClientConfig config = ClientConfig.of(); - if(timeout > 0){ - config.setTimeout(timeout); + /** + * 下载文件 + * + * @param targetFileOrDir 目标文件或目录,当为目录时,自动使用文件名作为下载文件名 + * @return 下载文件 + */ + public File downloadFile(final File targetFileOrDir) { + return downloadFile(targetFileOrDir, null); + } + + /** + * 下载文件 + * + * @param targetFileOrDir 目标文件或目录,当为目录时,自动使用文件名作为下载文件名 + * @param tempFileSuffix 临时文件后缀 + * @return 下载文件 + */ + public File downloadFile(final File targetFileOrDir, final String tempFileSuffix) { + try (final ResponseBody body = send()) { + return body.write(targetFileOrDir, tempFileSuffix, this.streamProgress); + } catch (final IOException e) { + throw new HttpException(e); + } finally { + if (this.closeEngine) { + IoUtil.closeQuietly(this.engine); + } } + } - final Response response = ClientEngineFactory.getEngine() - .init(config) - .send(Request.of(url)); - - if (response.isOk()) { - return response; + /** + * 下载文件 + * + * @param out 输出流 + * @param isCloseOut 是否关闭输出流,true关闭 + * @return 下载的字节数 + */ + public long download(final OutputStream out, final boolean isCloseOut) { + try (final ResponseBody body = send()) { + return body.write(out, isCloseOut, this.streamProgress); + } catch (final IOException e) { + throw new HttpException(e); + } finally { + if (this.closeEngine) { + IoUtil.closeQuietly(this.engine); + } } + } - throw new HttpException("Server response error with status code: [{}]", response.getStatus()); + /** + * 发送请求,获取响应 + * + * @return 响应 + */ + private ResponseBody send() { + if (null == this.engine) { + this.engine = ClientEngineFactory.createEngine(); + } + if (null != this.config) { + this.engine.init(this.config); + } + return engine.send(this.request).body(); } } diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/client/Request.java b/hutool-http/src/main/java/org/dromara/hutool/http/client/Request.java index 0270d0022..30aa405c4 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/client/Request.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/client/Request.java @@ -138,6 +138,11 @@ public class Request implements HeaderOperation { * 是否是REST请求模式,REST模式运行GET请求附带body */ private boolean isRest; + /** + * 重定向计数器,内部使用
+ * 单次请求时,重定向次数内部自增 + */ + private int redirectCount; /** * 默认构造 @@ -399,7 +404,7 @@ public class Request implements HeaderOperation { /** * 获取最大重定向请求次数
- * 注意:当{@link ClientConfig#isFollowRedirects()}为{@code true}时,此参数无效 + * 如果次数小于1则表示不重定向,大于等于1表示打开重定向。 * * @return 最大重定向请求次数 */ @@ -410,7 +415,6 @@ public class Request implements HeaderOperation { /** * 设置最大重定向次数
* 如果次数小于1则表示不重定向,大于等于1表示打开重定向
- * 注意:当{@link ClientConfig#isFollowRedirects()}为{@code true}时,此参数无效 * * @param maxRedirects 最大重定向次数 * @return this diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/client/RequestContext.java b/hutool-http/src/main/java/org/dromara/hutool/http/client/RequestContext.java new file mode 100644 index 000000000..ad7b8bc8f --- /dev/null +++ b/hutool-http/src/main/java/org/dromara/hutool/http/client/RequestContext.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hutool.http.client; + +/** + * 请求上下文,用于在多次请求时保存状态信息
+ * 非线程安全。 + * + * @author Looly + */ +public class RequestContext { + + private Request request; + private int redirectCount; + + /** + * 获取请求 + * + * @return 请求 + */ + public Request getRequest() { + return request; + } + + /** + * 设置请求 + * + * @param request 请求 + * @return this + */ + public RequestContext setRequest(final Request request) { + this.request = request; + return this; + } + + /** + * 是否允许重定向,如果允许,则重定向计数器+1 + * + * @return 是否允许重定向 + */ + public boolean canRedirect(){ + final int maxRedirects = request.maxRedirects(); + if(maxRedirects > 0 && redirectCount < maxRedirects){ + redirectCount++; + return true; + } + return false; + } +} diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/httpclient4/HttpClient4Engine.java b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/httpclient4/HttpClient4Engine.java index 1a7c8e3cc..aa74f486d 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/httpclient4/HttpClient4Engine.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/httpclient4/HttpClient4Engine.java @@ -134,13 +134,6 @@ public class HttpClient4Engine extends AbstractClientEngine { // 设置默认头信息 clientBuilder.setDefaultHeaders(toHeaderList(GlobalHeaders.INSTANCE.headers())); - // 重定向 - if (config.isFollowRedirects()) { - clientBuilder.setRedirectStrategy(LaxRedirectStrategy.INSTANCE); - } else { - clientBuilder.disableRedirectHandling(); - } - // 设置代理 setProxy(clientBuilder, config); diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/httpclient5/HttpClient5Engine.java b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/httpclient5/HttpClient5Engine.java index 16d092bd7..f0e4ac950 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/httpclient5/HttpClient5Engine.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/httpclient5/HttpClient5Engine.java @@ -21,7 +21,6 @@ import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; import org.apache.hc.client5.http.config.ConnectionConfig; import org.apache.hc.client5.http.config.RequestConfig; -import org.apache.hc.client5.http.impl.DefaultRedirectStrategy; import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; @@ -122,7 +121,7 @@ public class HttpClient5Engine extends AbstractClientEngine { clientBuilder.setConnectionManager(buildConnectionManager(config)); // 实例级别默认请求配置 - clientBuilder.setDefaultRequestConfig(buildRequestConfig(config)); + clientBuilder.setDefaultRequestConfig(buildDefaultRequestConfig(config)); // 缓存 if (config.isDisableCache()) { @@ -132,13 +131,6 @@ public class HttpClient5Engine extends AbstractClientEngine { // 设置默认头信息 clientBuilder.setDefaultHeaders(toHeaderList(GlobalHeaders.INSTANCE.headers())); - // 重定向 - if (config.isFollowRedirects()) { - clientBuilder.setRedirectStrategy(DefaultRedirectStrategy.INSTANCE); - } else { - clientBuilder.disableRedirectHandling(); - } - // 设置代理 setProxy(clientBuilder, config); @@ -261,12 +253,12 @@ public class HttpClient5Engine extends AbstractClientEngine { } /** - * 构建请求配置,包括连接请求超时和响应(读取)超时 + * 构建默认请求配置,包括连接请求超时和响应(读取)超时 * * @param config {@link ClientConfig} * @return {@link RequestConfig} */ - private static RequestConfig buildRequestConfig(final ClientConfig config) { + private static RequestConfig buildDefaultRequestConfig(final ClientConfig config) { final int connectionTimeout = config.getConnectionTimeout(); // 请求配置 diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/jdk/JdkClientEngine.java b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/jdk/JdkClientEngine.java index 1e9ab1169..95eb24277 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/jdk/JdkClientEngine.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/jdk/JdkClientEngine.java @@ -27,6 +27,7 @@ import org.dromara.hutool.http.HttpException; import org.dromara.hutool.http.HttpUtil; import org.dromara.hutool.http.client.ClientConfig; import org.dromara.hutool.http.client.Request; +import org.dromara.hutool.http.client.RequestContext; import org.dromara.hutool.http.client.body.HttpBody; import org.dromara.hutool.http.client.engine.AbstractClientEngine; import org.dromara.hutool.http.meta.HeaderName; @@ -69,16 +70,7 @@ public class JdkClientEngine extends AbstractClientEngine { @Override public JdkHttpResponse send(final Request message) { - final JdkHttpConnection conn = buildConn(message); - try { - doSend(conn, message); - } catch (final IOException e) { - // 出错后关闭连接 - IoUtil.closeQuietly(conn); - throw new IORuntimeException(e); - } - - return sendRedirectIfPossible(conn, message); + return doSend(new RequestContext().setRequest(message)); } @Override @@ -102,6 +94,20 @@ public class JdkClientEngine extends AbstractClientEngine { this.cookieManager = (null != this.config && this.config.isUseCookieManager()) ? new JdkCookieManager() : new JdkCookieManager(null); } + private JdkHttpResponse doSend(final RequestContext context) { + final Request message = context.getRequest(); + final JdkHttpConnection conn = buildConn(message); + try { + doSend(conn, message); + } catch (final IOException e) { + // 出错后关闭连接 + IoUtil.closeQuietly(conn); + throw new IORuntimeException(e); + } + + return sendRedirectIfPossible(conn, context); + } + /** * 执行发送 * @@ -132,7 +138,7 @@ public class JdkClientEngine extends AbstractClientEngine { final URL url = message.handledUrl().toURL(); Proxy proxy = null; final ProxyInfo proxyInfo = config.getProxy(); - if(null != proxyInfo){ + if (null != proxyInfo) { proxy = proxyInfo.selectFirst(UrlUtil.toURI(url)); } final JdkHttpConnection conn = JdkHttpConnection @@ -141,8 +147,8 @@ public class JdkClientEngine extends AbstractClientEngine { .setReadTimeout(config.getReadTimeout()) .setMethod(message.method())// .setSSLInfo(config.getSslInfo()) - // 如果客户端设置自动重定向,则Request中maxRedirectCount无效 - .setInstanceFollowRedirects(config.isFollowRedirects()) + // 关闭自动重定向,手动处理重定向 + .setInstanceFollowRedirects(false) .setDisableCache(config.isDisableCache()) // 覆盖默认Header .header(message.headers(), true); @@ -171,10 +177,12 @@ public class JdkClientEngine extends AbstractClientEngine { /** * 调用转发,如果需要转发返回转发结果,否则返回{@code null} * - * @param conn {@link JdkHttpConnection}} + * @param conn {@link JdkHttpConnection}} + * @param context 请求上下文 * @return {@link JdkHttpResponse},无转发返回 {@code null} */ - private JdkHttpResponse sendRedirectIfPossible(final JdkHttpConnection conn, final Request message) { + private JdkHttpResponse sendRedirectIfPossible(final JdkHttpConnection conn, final RequestContext context) { + final Request message = context.getRequest(); // 手动实现重定向 if (message.maxRedirects() > 0) { final int code; @@ -198,16 +206,15 @@ public class JdkClientEngine extends AbstractClientEngine { message.method(Method.GET); } - if (conn.redirectCount < message.maxRedirects()) { - conn.redirectCount++; - return send(message); + if (context.canRedirect()) { + return doSend(context); } } } } // 最终页面 - return new JdkHttpResponse(conn, this.cookieManager, message); + return new JdkHttpResponse(conn, this.cookieManager, context.getRequest()); } /** diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/jdk/JdkHttpConnection.java b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/jdk/JdkHttpConnection.java index 57fd62d59..3e0588561 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/jdk/JdkHttpConnection.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/jdk/JdkHttpConnection.java @@ -47,10 +47,6 @@ public class JdkHttpConnection implements HeaderOperation, Cl private final URL url; private final Proxy proxy; private final HttpURLConnection conn; - /** - * 重定向次数计数器,内部使用 - */ - protected int redirectCount; /** * 创建HttpConnection diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/okhttp/OkHttpEngine.java b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/okhttp/OkHttpEngine.java index e8a38ff7d..baca6a38f 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/okhttp/OkHttpEngine.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/okhttp/OkHttpEngine.java @@ -130,8 +130,8 @@ public class OkHttpEngine extends AbstractClientEngine { builder.connectionPool(((OkHttpClientConfig) config).getConnectionPool()); } - // 重定向 - builder.followRedirects(config.isFollowRedirects()); + // 关闭自动重定向,手动实现 + builder.followRedirects(false); // 设置代理 setProxy(builder, config); @@ -168,7 +168,6 @@ public class OkHttpEngine extends AbstractClientEngine { // 填充头信息 message.headers().forEach((key, values) -> values.forEach(value -> builder.addHeader(key, value))); - return builder.build(); } diff --git a/hutool-http/src/test/java/org/dromara/hutool/http/client/DownloadTest.java b/hutool-http/src/test/java/org/dromara/hutool/http/client/DownloadTest.java index f07cca45e..85268d6e3 100644 --- a/hutool-http/src/test/java/org/dromara/hutool/http/client/DownloadTest.java +++ b/hutool-http/src/test/java/org/dromara/hutool/http/client/DownloadTest.java @@ -16,23 +16,17 @@ package org.dromara.hutool.http.client; -import org.dromara.hutool.core.codec.binary.Base64; -import org.dromara.hutool.core.io.IORuntimeException; import org.dromara.hutool.core.io.StreamProgress; import org.dromara.hutool.core.io.file.FileUtil; import org.dromara.hutool.core.lang.Console; -import org.dromara.hutool.core.util.CharsetUtil; import org.dromara.hutool.http.HttpGlobalConfig; import org.dromara.hutool.http.client.engine.ClientEngineFactory; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.UUID; /** @@ -46,31 +40,22 @@ public class DownloadTest { @Disabled public void downloadPicTest() { final String url = "http://wx.qlogo.cn/mmopen/vKhlFcibVUtNBVDjcIowlg0X8aJfHXrTNCEFBukWVH9ta99pfEN88lU39MKspCUCOP3yrFBH3y2NbV7sYtIIlon8XxLwAEqv2/0"; - HttpDownloader.downloadFile(url, new File("e:/shape/t3.jpg")); + HttpDownloader.of(url).downloadFile(new File("d:/test/download/t3.jpg")); Console.log("ok"); } - @SuppressWarnings("resource") - @Test - @Disabled - public void downloadSizeTest() { - final String url = "https://res.t-io.org/im/upload/img/67/8948/1119501/88097554/74541310922/85/231910/366466 - 副本.jpg"; - ClientEngineFactory.getEngine().send(Request.of(url)).body().write("e:/shape/366466.jpg"); - //HttpRequest.get(url).setSSLProtocol("TLSv1.2").executeAsync().body().write("e:/shape/366466.jpg"); - } - @Test @Disabled public void downloadTest1() { - final File size = HttpDownloader.downloadFile("http://explorer.bbfriend.com/crossdomain.xml", new File("e:/temp/")); - System.out.println("Download size: " + size); + final File size = HttpDownloader.of("http://explorer.bbfriend.com/crossdomain.xml").downloadFile(new File("d:test/download/temp/")); + Console.log("Download size: " + size); } @Test @Disabled public void downloadTest() { // 带进度显示的文件下载 - HttpDownloader.downloadFile("http://mirrors.sohu.com/centos/7/isos/x86_64/CentOS-7-x86_64-DVD-2009.iso", FileUtil.file("d:/"), -1, new StreamProgress() { + HttpDownloader.of("http://mirrors.sohu.com/centos/7/isos/x86_64/CentOS-7-x86_64-DVD-2009.iso").setStreamProgress(new StreamProgress() { final long time = System.currentTimeMillis(); @@ -89,13 +74,14 @@ public class DownloadTest { public void finish() { Console.log("下载完成!"); } - }); + }).downloadFile(FileUtil.file("d:/test/")); } @Test @Disabled public void downloadFileFromUrlTest1() { - final File file = HttpDownloader.downloadFile("http://groovy-lang.org/changelogs/changelog-3.0.5.html", new File("d:/download/temp")); + final File file = HttpDownloader.of("http://groovy-lang.org/changelogs/changelog-3.0.5.html") + .downloadFile(new File("d:/download/temp")); Assertions.assertNotNull(file); Assertions.assertTrue(file.isFile()); Assertions.assertTrue(file.length() > 0); @@ -106,7 +92,8 @@ public class DownloadTest { public void downloadFileFromUrlTest2() { File file = null; try { - file = HttpDownloader.downloadFile("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() { + file = HttpDownloader.of("https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.4.0/hutool-all-5.4.0-sources.jar") + .setStreamProgress(new StreamProgress() { @Override public void start() { System.out.println("start"); @@ -121,15 +108,13 @@ public class DownloadTest { public void finish() { System.out.println("end"); } - }); + }).downloadFile(FileUtil.file("d:/download/temp")); Assertions.assertNotNull(file); Assertions.assertTrue(file.exists()); Assertions.assertTrue(file.isFile()); Assertions.assertTrue(file.length() > 0); Assertions.assertFalse(file.getName().isEmpty()); - } catch (final Exception e) { - Assertions.assertInstanceOf(IORuntimeException.class, e); } finally { FileUtil.del(file); } @@ -140,7 +125,7 @@ public class DownloadTest { public void downloadFileFromUrlTest3() { File file = null; try { - file = HttpDownloader.downloadFile("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() { + file = HttpDownloader.of("https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.4.0/hutool-all-5.4.0-sources.jar").setStreamProgress(new StreamProgress() { @Override public void start() { System.out.println("start"); @@ -155,7 +140,7 @@ public class DownloadTest { public void finish() { System.out.println("end"); } - }); + }).downloadFile(FileUtil.file("d:/download/temp")); Assertions.assertNotNull(file); Assertions.assertTrue(file.exists()); @@ -172,15 +157,14 @@ public class DownloadTest { public void downloadFileFromUrlTest4() { File file = null; try { - file = HttpDownloader.downloadFile("http://groovy-lang.org/changelogs/changelog-3.0.5.html", FileUtil.file("d:/download/temp"), 1); + file = HttpDownloader.of("http://groovy-lang.org/changelogs/changelog-3.0.5.html") + .downloadFile(FileUtil.file("d:/download/temp")); Assertions.assertNotNull(file); Assertions.assertTrue(file.exists()); Assertions.assertTrue(file.isFile()); Assertions.assertTrue(file.length() > 0); Assertions.assertFalse(file.getName().isEmpty()); - } catch (final Exception e) { - Assertions.assertInstanceOf(IORuntimeException.class, e); } finally { FileUtil.del(file); } @@ -192,7 +176,8 @@ public class DownloadTest { public void downloadFileFromUrlTest5() { File file = null; try { - file = HttpDownloader.downloadFile("http://groovy-lang.org/changelogs/changelog-3.0.5.html", FileUtil.file("d:/download/temp", UUID.randomUUID().toString())); + file = HttpDownloader.of("http://groovy-lang.org/changelogs/changelog-3.0.5.html") + .downloadFile(FileUtil.file("d:/download/temp", UUID.randomUUID().toString())); Assertions.assertNotNull(file); Assertions.assertTrue(file.exists()); @@ -204,7 +189,8 @@ public class DownloadTest { File file1 = null; try { - file1 = HttpDownloader.downloadFile("http://groovy-lang.org/changelogs/changelog-3.0.5.html", FileUtil.file("d:/download/temp")); + file1 = HttpDownloader.of("http://groovy-lang.org/changelogs/changelog-3.0.5.html") + .downloadFile(FileUtil.file("d:/download/temp")); Assertions.assertNotNull(file1); Assertions.assertTrue(file1.exists()); @@ -220,36 +206,10 @@ public class DownloadTest { public void downloadTeamViewerTest() throws IOException { // 此URL有3次重定向, 需要请求4次 final String url = "https://download.teamviewer.com/download/TeamViewer_Setup_x64.exe"; - HttpGlobalConfig.setMaxRedirects(20); - final Path temp = Files.createTempFile("tmp", ".exe"); - final File file = HttpDownloader.downloadFile(url, temp.toFile()); - Console.log(file.length()); - } + HttpGlobalConfig.setMaxRedirects(2); - @Test - @Disabled - public void downloadToStreamTest() { - String url2 = "http://storage.chancecloud.com.cn/20200413_%E7%B2%A4B12313_386.pdf"; - final ByteArrayOutputStream os2 = new ByteArrayOutputStream(); - HttpDownloader.download(url2, os2, false, null); - - url2 = "http://storage.chancecloud.com.cn/20200413_粤B12313_386.pdf"; - HttpDownloader.download(url2, os2, false, null); - } - - @Test - @Disabled - public void downloadStringTest() { - final String url = "https://www.baidu.com"; - // 从远程直接读取字符串,需要自定义编码,直接调用JDK方法 - final String content2 = HttpDownloader.downloadString(url, CharsetUtil.UTF_8, null); - Console.log(content2); - } - - @Test - @Disabled - public void gimg2Test(){ - final byte[] bytes = HttpDownloader.downloadBytes("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic.jj20.com%2Fup%2Fallimg%2F1114%2F0H320120Z3%2F200H3120Z3-6-1200.jpg&refer=http%3A%2F%2Fpic.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1621996490&t=8c384c2823ea453da15a1b9cd5183eea"); - Console.log(Base64.encode(bytes)); + final Response send = Request.of(url).send(ClientEngineFactory.createEngine("jdkClient")); + Console.log(send.getStatus()); + Console.log(send.headers()); } }