mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
fix redirect bug
This commit is contained in:
parent
c2ea26e40d
commit
9cc75d5e34
@ -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>
|
||||||
|
@ -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 进度条
|
* @param streamProgress 进度条
|
||||||
* @return 文件
|
* @return this
|
||||||
*/
|
*/
|
||||||
public static File downloadFile(final String url, final File targetFileOrDir, final int timeout, final StreamProgress streamProgress) {
|
public HttpDownloader setStreamProgress(final StreamProgress streamProgress) {
|
||||||
Assert.notNull(targetFileOrDir, "[targetFileOrDir] is null !");
|
this.streamProgress = streamProgress;
|
||||||
return requestDownload(url, timeout).body().write(targetFileOrDir, streamProgress);
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下载文件-避免未完成的文件<br>
|
* 设置是否关闭引擎,默认为true,即自动关闭引擎
|
||||||
* 来自:https://gitee.com/dromara/hutool/pulls/407<br>
|
|
||||||
* 此方法原理是先在目标文件同级目录下创建临时文件,下载之,等下载完毕后重命名,避免因下载错误导致的文件不完整。
|
|
||||||
*
|
*
|
||||||
* @param url 请求的url
|
* @param closeEngine 是否关闭引擎
|
||||||
* @param targetFileOrDir 目标文件或目录,当为目录时,取URL中的文件名,取不到使用编码后的URL做为文件名
|
* @return this
|
||||||
* @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) {
|
public HttpDownloader setCloseEngine(final boolean closeEngine) {
|
||||||
Assert.notNull(targetFileOrDir, "[targetFileOrDir] is null !");
|
this.closeEngine = closeEngine;
|
||||||
return requestDownload(url, timeout).body().write(targetFileOrDir, tempFileSuffix, streamProgress);
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下载远程文件
|
* 下载文件
|
||||||
*
|
*
|
||||||
* @param url 请求的url
|
* @param targetFileOrDir 目标文件或目录,当为目录时,自动使用文件名作为下载文件名
|
||||||
* @param out 将下载内容写到输出流中 {@link OutputStream}
|
* @return 下载文件
|
||||||
* @param isCloseOut 是否关闭输出流
|
|
||||||
* @param streamProgress 进度条
|
|
||||||
* @return 文件大小
|
|
||||||
*/
|
*/
|
||||||
public static long download(final String url, final OutputStream out, final boolean isCloseOut, final StreamProgress streamProgress) {
|
public File downloadFile(final File targetFileOrDir) {
|
||||||
Assert.notNull(out, "[out] is null !");
|
return downloadFile(targetFileOrDir, null);
|
||||||
return requestDownload(url, -1).body().write(out, isCloseOut, streamProgress);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 请求下载文件
|
* 下载文件
|
||||||
*
|
*
|
||||||
* @param url 请求下载文件地址
|
* @param targetFileOrDir 目标文件或目录,当为目录时,自动使用文件名作为下载文件名
|
||||||
* @param timeout 超时时间
|
* @param tempFileSuffix 临时文件后缀
|
||||||
* @return HttpResponse
|
* @return 下载文件
|
||||||
* @since 5.4.1
|
|
||||||
*/
|
*/
|
||||||
private static Response requestDownload(final String url, final int timeout) {
|
public File downloadFile(final File targetFileOrDir, final String tempFileSuffix) {
|
||||||
Assert.notBlank(url, "[url] is blank !");
|
try (final ResponseBody body = send()) {
|
||||||
|
return body.write(targetFileOrDir, tempFileSuffix, this.streamProgress);
|
||||||
final ClientConfig config = ClientConfig.of();
|
} catch (final IOException e) {
|
||||||
if(timeout > 0){
|
throw new HttpException(e);
|
||||||
config.setTimeout(timeout);
|
} 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 输出流
|
||||||
throw new HttpException("Server response error with status code: [{}]", response.getStatus());
|
* @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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送请求,获取响应
|
||||||
|
*
|
||||||
|
* @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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
|
||||||
// 请求配置
|
// 请求配置
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行发送
|
* 执行发送
|
||||||
*
|
*
|
||||||
@ -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);
|
||||||
@ -172,9 +178,11 @@ 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());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user