fix redirect bug

This commit is contained in:
Looly 2024-12-03 00:13:49 +08:00
parent c2ea26e40d
commit 9cc75d5e34
10 changed files with 255 additions and 233 deletions

View File

@ -56,10 +56,6 @@ public class ClientConfig {
* 代理 * 代理
*/ */
private ProxyInfo proxy; private ProxyInfo proxy;
/**
* 是否遇到响应状态码3xx时自动重定向请求
*/
private boolean followRedirects;
/** /**
* 是否使用引擎默认的Cookie管理器默认为true<br> * 是否使用引擎默认的Cookie管理器默认为true<br>
* 默认情况下每个客户端维护一个自己的Cookie管理器这个管理器用于在多次请求中记录并自动附带Cookie信息<br> * 默认情况下每个客户端维护一个自己的Cookie管理器这个管理器用于在多次请求中记录并自动附带Cookie信息<br>
@ -215,28 +211,6 @@ public class ClientConfig {
return this; return this;
} }
/**
* 是否遇到响应状态码3xx时自动重定向请求<br>
* 注意当打开客户端级别的自动重定向{@link Request#maxRedirects()}无效
*
* @return 是否遇到响应状态码3xx时自动重定向请求
*/
public boolean isFollowRedirects() {
return followRedirects;
}
/**
* 设置是否遇到响应状态码3xx时自动重定向请求<br>
* 注意当打开客户端级别的自动重定向{@link Request#maxRedirects()}无效
*
* @param followRedirects 是否遇到响应状态码3xx时自动重定向请求
* @return this
*/
public ClientConfig setFollowRedirects(final boolean followRedirects) {
this.followRedirects = followRedirects;
return this;
}
/** /**
* 是否使用引擎默认的Cookie管理器默认为true<br> * 是否使用引擎默认的Cookie管理器默认为true<br>
* 默认情况下每个客户端维护一个自己的Cookie管理器这个管理器用于在多次请求中记录并自动附带Cookie信息<br> * 默认情况下每个客户端维护一个自己的Cookie管理器这个管理器用于在多次请求中记录并自动附带Cookie信息<br>

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,155 +16,189 @@
package org.dromara.hutool.http.client; 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.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.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 org.dromara.hutool.http.client.engine.ClientEngineFactory;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.nio.charset.Charset; import java.util.Map;
/** /**
* 下载封装下载统一使用{@code GET}请求默认支持30x跳转 * HTTP下载器两种使用方式<br>
* 1. 一次性使用
* <pre>{@code HttpDownloader.of(url).downloadFile(file)}</pre>
* 2. 多次下载复用
* <pre>{@code
* HttpDownloader downloader = HttpDownloader.of(url).setCloseEngine(false);
* downloader.downloadFile(file);
* downloader.downloadFile(file2);
* downloader.close();
* }</pre>
* *
* @author looly * @author looly
* @since 5.6.4 * @since 6.0.0
*/ */
@SuppressWarnings("resource")
public class HttpDownloader { public class HttpDownloader {
/** /**
* 下载远程文本 * 创建下载器
* *
* @param url 请求的url * @param url 请求地址
* @param customCharset 自定义的字符集可以使用{@code CharsetUtil#charset} 方法转换 * @return 下载器
* @param streamPress 进度条 {@link StreamProgress}
* @return 文本
*/ */
public static String downloadString(final String url, final Charset customCharset, final StreamProgress streamPress) { public static HttpDownloader of(final String url) {
final FastByteArrayOutputStream out = new FastByteArrayOutputStream(); return new HttpDownloader(url);
download(url, out, true, streamPress); }
return null == customCharset ? out.toString() : out.toString(customCharset);
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 * @param headers 请求头
* @return 文件数据 * @return this
*/ */
public static byte[] downloadBytes(final String url) { public HttpDownloader header(final Map<String, String> headers) {
return downloadBytes(url, 0); this.request.header(headers);
return this;
} }
/** /**
* 下载远程文件数据支持30x跳转 * 设置配置
* *
* @param url 请求的url * @param config 配置
* @param timeout 超时毫秒数 * @return this
* @return 文件数据
* @since 5.8.28
*/ */
public static byte[] downloadBytes(final String url, final int timeout) { public HttpDownloader setConfig(final ClientConfig config) {
return requestDownload(url, timeout).bodyBytes(); this.config = config;
return this;
} }
/** /**
* 下载远程文件 * 设置超时
* *
* @param url 请求的url * @param milliseconds 超时毫秒数
* @param targetFileOrDir 目标文件或目录当为目录时取URL中的文件名取不到使用编码后的URL做为文件名 * @return this
* @return 文件
*/ */
public static File downloadFile(final String url, final File targetFileOrDir) { public HttpDownloader setTimeout(final int milliseconds) {
return downloadFile(url, targetFileOrDir, -1); if (null == this.config) {
this.config = ClientConfig.of();
}
this.config.setTimeout(milliseconds);
return this;
} }
/** /**
* 下载远程文件 * 设置引擎用于自定义引擎
* *
* @param url 请求的url * @param engine 引擎
* @param targetFileOrDir 目标文件或目录当为目录时取URL中的文件名取不到使用编码后的URL做为文件名 * @return this
* @param timeout 超时单位毫秒-1表示默认超时
* @return 文件
*/ */
public static File downloadFile(final String url, final File targetFileOrDir, final int timeout) { public HttpDownloader setEngine(final ClientEngine engine) {
Assert.notNull(targetFileOrDir, "[targetFileOrDir] is null !"); this.engine = engine;
return downloadFile(url, targetFileOrDir, timeout, null); 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);
}
/**
* 下载文件-避免未完成的文件<br>
* 来自https://gitee.com/dromara/hutool/pulls/407<br>
* 此方法原理是先在目标文件同级目录下创建临时文件下载之等下载完毕后重命名避免因下载错误导致的文件不完整
*
* @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 进度条 * @param streamProgress 进度条
* @return 文件大小 * @return this
*/ */
public static long download(final String url, final OutputStream out, final boolean isCloseOut, final StreamProgress streamProgress) { public HttpDownloader setStreamProgress(final StreamProgress streamProgress) {
Assert.notNull(out, "[out] is null !"); this.streamProgress = streamProgress;
return requestDownload(url, -1).body().write(out, isCloseOut, streamProgress); return this;
} }
/** /**
* 请求下载文件 * 设置是否关闭引擎默认为true即自动关闭引擎
* *
* @param url 请求下载文件地址 * @param closeEngine 是否关闭引擎
* @param timeout 超时时间 * @return this
* @return HttpResponse
* @since 5.4.1
*/ */
private static Response requestDownload(final String url, final int timeout) { public HttpDownloader setCloseEngine(final boolean closeEngine) {
Assert.notBlank(url, "[url] is blank !"); 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)); *
* @param out 输出流
if (response.isOk()) { * @param isCloseOut 是否关闭输出流true关闭
return response; * @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();
} }
} }

View File

@ -138,6 +138,11 @@ public class Request implements HeaderOperation<Request> {
* 是否是REST请求模式REST模式运行GET请求附带body * 是否是REST请求模式REST模式运行GET请求附带body
*/ */
private boolean isRest; private boolean isRest;
/**
* 重定向计数器内部使用<br>
* 单次请求时重定向次数内部自增
*/
private int redirectCount;
/** /**
* 默认构造 * 默认构造
@ -399,7 +404,7 @@ public class Request implements HeaderOperation<Request> {
/** /**
* 获取最大重定向请求次数<br> * 获取最大重定向请求次数<br>
* 注意{@link ClientConfig#isFollowRedirects()}{@code true}此参数无效 * 如果次数小于1则表示不重定向大于等于1表示打开重定向
* *
* @return 最大重定向请求次数 * @return 最大重定向请求次数
*/ */
@ -410,7 +415,6 @@ public class Request implements HeaderOperation<Request> {
/** /**
* 设置最大重定向次数<br> * 设置最大重定向次数<br>
* 如果次数小于1则表示不重定向大于等于1表示打开重定向<br> * 如果次数小于1则表示不重定向大于等于1表示打开重定向<br>
* 注意{@link ClientConfig#isFollowRedirects()}{@code true}此参数无效
* *
* @param maxRedirects 最大重定向次数 * @param maxRedirects 最大重定向次数
* @return this * @return this

View File

@ -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;
/**
* 请求上下文用于在多次请求时保存状态信息<br>
* 非线程安全
*
* @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;
}
}

View File

@ -134,13 +134,6 @@ public class HttpClient4Engine extends AbstractClientEngine {
// 设置默认头信息 // 设置默认头信息
clientBuilder.setDefaultHeaders(toHeaderList(GlobalHeaders.INSTANCE.headers())); clientBuilder.setDefaultHeaders(toHeaderList(GlobalHeaders.INSTANCE.headers()));
// 重定向
if (config.isFollowRedirects()) {
clientBuilder.setRedirectStrategy(LaxRedirectStrategy.INSTANCE);
} else {
clientBuilder.disableRedirectHandling();
}
// 设置代理 // 设置代理
setProxy(clientBuilder, config); setProxy(clientBuilder, config);

View File

@ -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.classic.methods.HttpUriRequestBase;
import org.apache.hc.client5.http.config.ConnectionConfig; import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig; 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.auth.BasicCredentialsProvider;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
@ -122,7 +121,7 @@ public class HttpClient5Engine extends AbstractClientEngine {
clientBuilder.setConnectionManager(buildConnectionManager(config)); clientBuilder.setConnectionManager(buildConnectionManager(config));
// 实例级别默认请求配置 // 实例级别默认请求配置
clientBuilder.setDefaultRequestConfig(buildRequestConfig(config)); clientBuilder.setDefaultRequestConfig(buildDefaultRequestConfig(config));
// 缓存 // 缓存
if (config.isDisableCache()) { if (config.isDisableCache()) {
@ -132,13 +131,6 @@ public class HttpClient5Engine extends AbstractClientEngine {
// 设置默认头信息 // 设置默认头信息
clientBuilder.setDefaultHeaders(toHeaderList(GlobalHeaders.INSTANCE.headers())); clientBuilder.setDefaultHeaders(toHeaderList(GlobalHeaders.INSTANCE.headers()));
// 重定向
if (config.isFollowRedirects()) {
clientBuilder.setRedirectStrategy(DefaultRedirectStrategy.INSTANCE);
} else {
clientBuilder.disableRedirectHandling();
}
// 设置代理 // 设置代理
setProxy(clientBuilder, config); setProxy(clientBuilder, config);
@ -261,12 +253,12 @@ public class HttpClient5Engine extends AbstractClientEngine {
} }
/** /**
* 构建请求配置包括连接请求超时和响应读取超时 * 构建默认请求配置包括连接请求超时和响应读取超时
* *
* @param config {@link ClientConfig} * @param config {@link ClientConfig}
* @return {@link RequestConfig} * @return {@link RequestConfig}
*/ */
private static RequestConfig buildRequestConfig(final ClientConfig config) { private static RequestConfig buildDefaultRequestConfig(final ClientConfig config) {
final int connectionTimeout = config.getConnectionTimeout(); final int connectionTimeout = config.getConnectionTimeout();
// 请求配置 // 请求配置

View File

@ -27,6 +27,7 @@ import org.dromara.hutool.http.HttpException;
import org.dromara.hutool.http.HttpUtil; import org.dromara.hutool.http.HttpUtil;
import org.dromara.hutool.http.client.ClientConfig; import org.dromara.hutool.http.client.ClientConfig;
import org.dromara.hutool.http.client.Request; 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.body.HttpBody;
import org.dromara.hutool.http.client.engine.AbstractClientEngine; import org.dromara.hutool.http.client.engine.AbstractClientEngine;
import org.dromara.hutool.http.meta.HeaderName; import org.dromara.hutool.http.meta.HeaderName;
@ -69,16 +70,7 @@ public class JdkClientEngine extends AbstractClientEngine {
@Override @Override
public JdkHttpResponse send(final Request message) { public JdkHttpResponse send(final Request message) {
final JdkHttpConnection conn = buildConn(message); return doSend(new RequestContext().setRequest(message));
try {
doSend(conn, message);
} catch (final IOException e) {
// 出错后关闭连接
IoUtil.closeQuietly(conn);
throw new IORuntimeException(e);
}
return sendRedirectIfPossible(conn, message);
} }
@Override @Override
@ -102,6 +94,20 @@ public class JdkClientEngine extends AbstractClientEngine {
this.cookieManager = (null != this.config && this.config.isUseCookieManager()) ? new JdkCookieManager() : new JdkCookieManager(null); 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(); final URL url = message.handledUrl().toURL();
Proxy proxy = null; Proxy proxy = null;
final ProxyInfo proxyInfo = config.getProxy(); final ProxyInfo proxyInfo = config.getProxy();
if(null != proxyInfo){ if (null != proxyInfo) {
proxy = proxyInfo.selectFirst(UrlUtil.toURI(url)); proxy = proxyInfo.selectFirst(UrlUtil.toURI(url));
} }
final JdkHttpConnection conn = JdkHttpConnection final JdkHttpConnection conn = JdkHttpConnection
@ -141,8 +147,8 @@ public class JdkClientEngine extends AbstractClientEngine {
.setReadTimeout(config.getReadTimeout()) .setReadTimeout(config.getReadTimeout())
.setMethod(message.method())// .setMethod(message.method())//
.setSSLInfo(config.getSslInfo()) .setSSLInfo(config.getSslInfo())
// 如果客户端设置自动重定向则Request中maxRedirectCount无效 // 关闭自动重定向手动处理重定向
.setInstanceFollowRedirects(config.isFollowRedirects()) .setInstanceFollowRedirects(false)
.setDisableCache(config.isDisableCache()) .setDisableCache(config.isDisableCache())
// 覆盖默认Header // 覆盖默认Header
.header(message.headers(), true); .header(message.headers(), true);
@ -171,10 +177,12 @@ public class JdkClientEngine extends AbstractClientEngine {
/** /**
* 调用转发如果需要转发返回转发结果否则返回{@code null} * 调用转发如果需要转发返回转发结果否则返回{@code null}
* *
* @param conn {@link JdkHttpConnection}} * @param conn {@link JdkHttpConnection}}
* @param context 请求上下文
* @return {@link JdkHttpResponse}无转发返回 {@code null} * @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) { if (message.maxRedirects() > 0) {
final int code; final int code;
@ -198,16 +206,15 @@ public class JdkClientEngine extends AbstractClientEngine {
message.method(Method.GET); message.method(Method.GET);
} }
if (conn.redirectCount < message.maxRedirects()) { if (context.canRedirect()) {
conn.redirectCount++; return doSend(context);
return send(message);
} }
} }
} }
} }
// 最终页面 // 最终页面
return new JdkHttpResponse(conn, this.cookieManager, message); return new JdkHttpResponse(conn, this.cookieManager, context.getRequest());
} }
/** /**

View File

@ -47,10 +47,6 @@ public class JdkHttpConnection implements HeaderOperation<JdkHttpConnection>, Cl
private final URL url; private final URL url;
private final Proxy proxy; private final Proxy proxy;
private final HttpURLConnection conn; private final HttpURLConnection conn;
/**
* 重定向次数计数器内部使用
*/
protected int redirectCount;
/** /**
* 创建HttpConnection * 创建HttpConnection

View File

@ -130,8 +130,8 @@ public class OkHttpEngine extends AbstractClientEngine {
builder.connectionPool(((OkHttpClientConfig) config).getConnectionPool()); builder.connectionPool(((OkHttpClientConfig) config).getConnectionPool());
} }
// 重定向 // 关闭自动重定向手动实现
builder.followRedirects(config.isFollowRedirects()); builder.followRedirects(false);
// 设置代理 // 设置代理
setProxy(builder, config); setProxy(builder, config);
@ -168,7 +168,6 @@ public class OkHttpEngine extends AbstractClientEngine {
// 填充头信息 // 填充头信息
message.headers().forEach((key, values) -> values.forEach(value -> builder.addHeader(key, value))); message.headers().forEach((key, values) -> values.forEach(value -> builder.addHeader(key, value)));
return builder.build(); return builder.build();
} }

View File

@ -16,23 +16,17 @@
package org.dromara.hutool.http.client; 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.StreamProgress;
import org.dromara.hutool.core.io.file.FileUtil; import org.dromara.hutool.core.io.file.FileUtil;
import org.dromara.hutool.core.lang.Console; 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.HttpGlobalConfig;
import org.dromara.hutool.http.client.engine.ClientEngineFactory; import org.dromara.hutool.http.client.engine.ClientEngineFactory;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.UUID; import java.util.UUID;
/** /**
@ -46,31 +40,22 @@ public class DownloadTest {
@Disabled @Disabled
public void downloadPicTest() { public void downloadPicTest() {
final String url = "http://wx.qlogo.cn/mmopen/vKhlFcibVUtNBVDjcIowlg0X8aJfHXrTNCEFBukWVH9ta99pfEN88lU39MKspCUCOP3yrFBH3y2NbV7sYtIIlon8XxLwAEqv2/0"; 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"); 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 @Test
@Disabled @Disabled
public void downloadTest1() { public void downloadTest1() {
final File size = HttpDownloader.downloadFile("http://explorer.bbfriend.com/crossdomain.xml", new File("e:/temp/")); final File size = HttpDownloader.of("http://explorer.bbfriend.com/crossdomain.xml").downloadFile(new File("d:test/download/temp/"));
System.out.println("Download size: " + size); Console.log("Download size: " + size);
} }
@Test @Test
@Disabled @Disabled
public void downloadTest() { 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(); final long time = System.currentTimeMillis();
@ -89,13 +74,14 @@ public class DownloadTest {
public void finish() { public void finish() {
Console.log("下载完成!"); Console.log("下载完成!");
} }
}); }).downloadFile(FileUtil.file("d:/test/"));
} }
@Test @Test
@Disabled @Disabled
public void downloadFileFromUrlTest1() { 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.assertNotNull(file);
Assertions.assertTrue(file.isFile()); Assertions.assertTrue(file.isFile());
Assertions.assertTrue(file.length() > 0); Assertions.assertTrue(file.length() > 0);
@ -106,7 +92,8 @@ public class DownloadTest {
public void downloadFileFromUrlTest2() { public void downloadFileFromUrlTest2() {
File file = null; File file = null;
try { 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 @Override
public void start() { public void start() {
System.out.println("start"); System.out.println("start");
@ -121,15 +108,13 @@ public class DownloadTest {
public void finish() { public void finish() {
System.out.println("end"); System.out.println("end");
} }
}); }).downloadFile(FileUtil.file("d:/download/temp"));
Assertions.assertNotNull(file); Assertions.assertNotNull(file);
Assertions.assertTrue(file.exists()); Assertions.assertTrue(file.exists());
Assertions.assertTrue(file.isFile()); Assertions.assertTrue(file.isFile());
Assertions.assertTrue(file.length() > 0); Assertions.assertTrue(file.length() > 0);
Assertions.assertFalse(file.getName().isEmpty()); Assertions.assertFalse(file.getName().isEmpty());
} catch (final Exception e) {
Assertions.assertInstanceOf(IORuntimeException.class, e);
} finally { } finally {
FileUtil.del(file); FileUtil.del(file);
} }
@ -140,7 +125,7 @@ public class DownloadTest {
public void downloadFileFromUrlTest3() { public void downloadFileFromUrlTest3() {
File file = null; File file = null;
try { 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 @Override
public void start() { public void start() {
System.out.println("start"); System.out.println("start");
@ -155,7 +140,7 @@ public class DownloadTest {
public void finish() { public void finish() {
System.out.println("end"); System.out.println("end");
} }
}); }).downloadFile(FileUtil.file("d:/download/temp"));
Assertions.assertNotNull(file); Assertions.assertNotNull(file);
Assertions.assertTrue(file.exists()); Assertions.assertTrue(file.exists());
@ -172,15 +157,14 @@ public class DownloadTest {
public void downloadFileFromUrlTest4() { public void downloadFileFromUrlTest4() {
File file = null; File file = null;
try { 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.assertNotNull(file);
Assertions.assertTrue(file.exists()); Assertions.assertTrue(file.exists());
Assertions.assertTrue(file.isFile()); Assertions.assertTrue(file.isFile());
Assertions.assertTrue(file.length() > 0); Assertions.assertTrue(file.length() > 0);
Assertions.assertFalse(file.getName().isEmpty()); Assertions.assertFalse(file.getName().isEmpty());
} catch (final Exception e) {
Assertions.assertInstanceOf(IORuntimeException.class, e);
} finally { } finally {
FileUtil.del(file); FileUtil.del(file);
} }
@ -192,7 +176,8 @@ public class DownloadTest {
public void downloadFileFromUrlTest5() { public void downloadFileFromUrlTest5() {
File file = null; File file = null;
try { 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.assertNotNull(file);
Assertions.assertTrue(file.exists()); Assertions.assertTrue(file.exists());
@ -204,7 +189,8 @@ public class DownloadTest {
File file1 = null; File file1 = null;
try { 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.assertNotNull(file1);
Assertions.assertTrue(file1.exists()); Assertions.assertTrue(file1.exists());
@ -220,36 +206,10 @@ public class DownloadTest {
public void downloadTeamViewerTest() throws IOException { public void downloadTeamViewerTest() throws IOException {
// 此URL有3次重定向, 需要请求4次 // 此URL有3次重定向, 需要请求4次
final String url = "https://download.teamviewer.com/download/TeamViewer_Setup_x64.exe"; final String url = "https://download.teamviewer.com/download/TeamViewer_Setup_x64.exe";
HttpGlobalConfig.setMaxRedirects(20); HttpGlobalConfig.setMaxRedirects(2);
final Path temp = Files.createTempFile("tmp", ".exe");
final File file = HttpDownloader.downloadFile(url, temp.toFile());
Console.log(file.length());
}
@Test final Response send = Request.of(url).send(ClientEngineFactory.createEngine("jdkClient"));
@Disabled Console.log(send.getStatus());
public void downloadToStreamTest() { Console.log(send.headers());
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));
} }
} }