mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-04-19 03:01:48 +08:00
fix code
This commit is contained in:
parent
11f47e5b0f
commit
35a8f5e281
@ -133,6 +133,23 @@ public class FileNameUtil {
|
|||||||
return extName(fileName);
|
return extName(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 增加临时扩展名
|
||||||
|
*
|
||||||
|
* @param fileName 文件名
|
||||||
|
* @param suffix 临时扩展名,如果为空,使用`.temp`
|
||||||
|
* @return 临时文件名
|
||||||
|
*/
|
||||||
|
public static String addTempSuffix(final String fileName, String suffix){
|
||||||
|
if (StrUtil.isBlank(suffix)) {
|
||||||
|
suffix = ".temp";
|
||||||
|
} else {
|
||||||
|
suffix = StrUtil.addPrefixIfNot(suffix, StrUtil.DOT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileName + suffix;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 返回主文件名
|
* 返回主文件名
|
||||||
*
|
*
|
||||||
|
@ -1,98 +0,0 @@
|
|||||||
package cn.hutool.http;
|
|
||||||
|
|
||||||
import cn.hutool.http.client.engine.jdk.HttpInterceptor;
|
|
||||||
import cn.hutool.http.client.engine.jdk.HttpRequest;
|
|
||||||
import cn.hutool.http.client.engine.jdk.HttpResponse;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 全局的拦截器<br>
|
|
||||||
* 包括请求拦截器和响应拦截器
|
|
||||||
*
|
|
||||||
* @author looly
|
|
||||||
* @since 5.8.0
|
|
||||||
*/
|
|
||||||
public enum GlobalInterceptor {
|
|
||||||
INSTANCE;
|
|
||||||
|
|
||||||
private final HttpInterceptor.Chain<HttpRequest> requestInterceptors = new HttpInterceptor.Chain<>();
|
|
||||||
private final HttpInterceptor.Chain<HttpResponse> responseInterceptors = new HttpInterceptor.Chain<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置拦截器,用于在请求前重新编辑请求
|
|
||||||
*
|
|
||||||
* @param interceptor 拦截器实现
|
|
||||||
* @return this
|
|
||||||
*/
|
|
||||||
synchronized public GlobalInterceptor addRequestInterceptor(final HttpInterceptor<HttpRequest> interceptor) {
|
|
||||||
this.requestInterceptors.addChain(interceptor);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置拦截器,用于在响应读取后完成编辑或读取
|
|
||||||
*
|
|
||||||
* @param interceptor 拦截器实现
|
|
||||||
* @return this
|
|
||||||
*/
|
|
||||||
synchronized public GlobalInterceptor addResponseInterceptor(final HttpInterceptor<HttpResponse> interceptor) {
|
|
||||||
this.responseInterceptors.addChain(interceptor);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清空请求和响应拦截器
|
|
||||||
*
|
|
||||||
* @return this
|
|
||||||
*/
|
|
||||||
public GlobalInterceptor clear() {
|
|
||||||
clearRequest();
|
|
||||||
clearResponse();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清空请求拦截器
|
|
||||||
*
|
|
||||||
* @return this
|
|
||||||
*/
|
|
||||||
synchronized public GlobalInterceptor clearRequest() {
|
|
||||||
requestInterceptors.clear();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清空响应拦截器
|
|
||||||
*
|
|
||||||
* @return this
|
|
||||||
*/
|
|
||||||
synchronized public GlobalInterceptor clearResponse() {
|
|
||||||
responseInterceptors.clear();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 复制请求过滤器列表
|
|
||||||
*
|
|
||||||
* @return {@link HttpInterceptor.Chain}
|
|
||||||
*/
|
|
||||||
HttpInterceptor.Chain<HttpRequest> getCopiedRequestInterceptor() {
|
|
||||||
final HttpInterceptor.Chain<HttpRequest> copied = new HttpInterceptor.Chain<>();
|
|
||||||
for (final HttpInterceptor<HttpRequest> interceptor : this.requestInterceptors) {
|
|
||||||
copied.addChain(interceptor);
|
|
||||||
}
|
|
||||||
return copied;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 复制响应过滤器列表
|
|
||||||
*
|
|
||||||
* @return {@link HttpInterceptor.Chain}
|
|
||||||
*/
|
|
||||||
HttpInterceptor.Chain<HttpResponse> getCopiedResponseInterceptor() {
|
|
||||||
final HttpInterceptor.Chain<HttpResponse> copied = new HttpInterceptor.Chain<>();
|
|
||||||
for (final HttpInterceptor<HttpResponse> interceptor : this.responseInterceptors) {
|
|
||||||
copied.addChain(interceptor);
|
|
||||||
}
|
|
||||||
return copied;
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,9 +2,6 @@ package cn.hutool.http;
|
|||||||
|
|
||||||
import cn.hutool.core.lang.Assert;
|
import cn.hutool.core.lang.Assert;
|
||||||
import cn.hutool.core.net.ssl.SSLUtil;
|
import cn.hutool.core.net.ssl.SSLUtil;
|
||||||
import cn.hutool.http.client.engine.jdk.HttpInterceptor;
|
|
||||||
import cn.hutool.http.client.engine.jdk.HttpRequest;
|
|
||||||
import cn.hutool.http.client.engine.jdk.HttpResponse;
|
|
||||||
|
|
||||||
import javax.net.ssl.HostnameVerifier;
|
import javax.net.ssl.HostnameVerifier;
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
@ -79,15 +76,6 @@ public class HttpConfig {
|
|||||||
*/
|
*/
|
||||||
boolean decodeUrl = HttpGlobalConfig.isDecodeUrl();
|
boolean decodeUrl = HttpGlobalConfig.isDecodeUrl();
|
||||||
|
|
||||||
/**
|
|
||||||
* 请求前的拦截器,用于在请求前重新编辑请求
|
|
||||||
*/
|
|
||||||
public final HttpInterceptor.Chain<HttpRequest> requestInterceptors = GlobalInterceptor.INSTANCE.getCopiedRequestInterceptor();
|
|
||||||
/**
|
|
||||||
* 响应后的拦截器,用于在响应后处理逻辑
|
|
||||||
*/
|
|
||||||
public final HttpInterceptor.Chain<HttpResponse> responseInterceptors = GlobalInterceptor.INSTANCE.getCopiedResponseInterceptor();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重定向时是否使用拦截器
|
* 重定向时是否使用拦截器
|
||||||
*/
|
*/
|
||||||
@ -125,7 +113,7 @@ public class HttpConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置连接超时,单位:毫秒
|
* 设置读取超时,单位:毫秒
|
||||||
*
|
*
|
||||||
* @param milliseconds 超时毫秒数
|
* @param milliseconds 超时毫秒数
|
||||||
* @return this
|
* @return this
|
||||||
@ -268,28 +256,6 @@ public class HttpConfig {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置拦截器,用于在请求前重新编辑请求
|
|
||||||
*
|
|
||||||
* @param interceptor 拦截器实现
|
|
||||||
* @return this
|
|
||||||
*/
|
|
||||||
public HttpConfig addRequestInterceptor(final HttpInterceptor<HttpRequest> interceptor) {
|
|
||||||
this.requestInterceptors.addChain(interceptor);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置拦截器,用于在请求前重新编辑请求
|
|
||||||
*
|
|
||||||
* @param interceptor 拦截器实现
|
|
||||||
* @return this
|
|
||||||
*/
|
|
||||||
public HttpConfig addResponseInterceptor(final HttpInterceptor<HttpResponse> interceptor) {
|
|
||||||
this.responseInterceptors.addChain(interceptor);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重定向时是否使用拦截器
|
* 重定向时是否使用拦截器
|
||||||
*
|
*
|
||||||
|
@ -124,7 +124,7 @@ public class HttpUtil {
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("resource")
|
@SuppressWarnings("resource")
|
||||||
public static String get(final String urlString, final Charset customCharset) {
|
public static String get(final String urlString, final Charset customCharset) {
|
||||||
return HttpRequest.get(urlString).charset(customCharset).execute().body();
|
return HttpRequest.get(urlString).charset(customCharset).execute().bodyStr();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -147,7 +147,7 @@ public class HttpUtil {
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("resource")
|
@SuppressWarnings("resource")
|
||||||
public static String get(final String urlString, final int timeout) {
|
public static String get(final String urlString, final int timeout) {
|
||||||
return HttpRequest.get(urlString).timeout(timeout).execute().body();
|
return HttpRequest.get(urlString).timeout(timeout).execute().bodyStr();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -159,7 +159,7 @@ public class HttpUtil {
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("resource")
|
@SuppressWarnings("resource")
|
||||||
public static String get(final String urlString, final Map<String, Object> paramMap) {
|
public static String get(final String urlString, final Map<String, Object> paramMap) {
|
||||||
return HttpRequest.get(urlString).form(paramMap).execute().body();
|
return HttpRequest.get(urlString).form(paramMap).execute().bodyStr();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -173,7 +173,7 @@ public class HttpUtil {
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("resource")
|
@SuppressWarnings("resource")
|
||||||
public static String get(final String urlString, final Map<String, Object> paramMap, final int timeout) {
|
public static String get(final String urlString, final Map<String, Object> paramMap, final int timeout) {
|
||||||
return HttpRequest.get(urlString).form(paramMap).timeout(timeout).execute().body();
|
return HttpRequest.get(urlString).form(paramMap).timeout(timeout).execute().bodyStr();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -198,7 +198,7 @@ public class HttpUtil {
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("resource")
|
@SuppressWarnings("resource")
|
||||||
public static String post(final String urlString, final Map<String, Object> paramMap, final int timeout) {
|
public static String post(final String urlString, final Map<String, Object> paramMap, final int timeout) {
|
||||||
return HttpRequest.post(urlString).form(paramMap).timeout(timeout).execute().body();
|
return HttpRequest.post(urlString).form(paramMap).timeout(timeout).execute().bodyStr();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -235,7 +235,7 @@ public class HttpUtil {
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("resource")
|
@SuppressWarnings("resource")
|
||||||
public static String post(final String urlString, final String body, final int timeout) {
|
public static String post(final String urlString, final String body, final int timeout) {
|
||||||
return HttpRequest.post(urlString).timeout(timeout).body(body).execute().body();
|
return HttpRequest.post(urlString).timeout(timeout).body(body).execute().bodyStr();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------------------- download
|
// ---------------------------------------------------------------------------------------- download
|
||||||
|
241
hutool-http/src/main/java/cn/hutool/http/client/ClientConfig.java
Executable file
241
hutool-http/src/main/java/cn/hutool/http/client/ClientConfig.java
Executable file
@ -0,0 +1,241 @@
|
|||||||
|
package cn.hutool.http.client;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
|
import cn.hutool.core.net.ssl.SSLUtil;
|
||||||
|
import cn.hutool.http.HttpGlobalConfig;
|
||||||
|
import cn.hutool.http.ssl.DefaultSSLInfo;
|
||||||
|
|
||||||
|
import javax.net.ssl.HostnameVerifier;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Proxy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Http客户端配置
|
||||||
|
*
|
||||||
|
* @author looly
|
||||||
|
*/
|
||||||
|
public class ClientConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建新的 ClientConfig
|
||||||
|
*
|
||||||
|
* @return ClientConfig
|
||||||
|
*/
|
||||||
|
public static ClientConfig of() {
|
||||||
|
return new ClientConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认连接超时
|
||||||
|
*/
|
||||||
|
private int connectionTimeout;
|
||||||
|
/**
|
||||||
|
* 默认读取超时
|
||||||
|
*/
|
||||||
|
private int readTimeout;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HostnameVerifier,用于HTTPS安全连接
|
||||||
|
*/
|
||||||
|
private HostnameVerifier hostnameVerifier;
|
||||||
|
/**
|
||||||
|
* SSLSocketFactory,用于HTTPS安全连接
|
||||||
|
*/
|
||||||
|
private SSLSocketFactory socketFactory;
|
||||||
|
/**
|
||||||
|
* 是否禁用缓存
|
||||||
|
*/
|
||||||
|
public boolean disableCache;
|
||||||
|
/**
|
||||||
|
* 代理
|
||||||
|
*/
|
||||||
|
public Proxy proxy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*/
|
||||||
|
public ClientConfig() {
|
||||||
|
connectionTimeout = HttpGlobalConfig.getTimeout();
|
||||||
|
readTimeout = HttpGlobalConfig.getTimeout();
|
||||||
|
hostnameVerifier = DefaultSSLInfo.TRUST_ANY_HOSTNAME_VERIFIER;
|
||||||
|
socketFactory = DefaultSSLInfo.DEFAULT_SSF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置超时,单位:毫秒<br>
|
||||||
|
* 超时包括:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 1. 连接超时
|
||||||
|
* 2. 读取响应超时
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param milliseconds 超时毫秒数
|
||||||
|
* @return this
|
||||||
|
* @see #setConnectionTimeout(int)
|
||||||
|
* @see #setReadTimeout(int)
|
||||||
|
*/
|
||||||
|
public ClientConfig setTimeout(final int milliseconds) {
|
||||||
|
setConnectionTimeout(milliseconds);
|
||||||
|
setReadTimeout(milliseconds);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取连接超时,单位:毫秒
|
||||||
|
*
|
||||||
|
* @return 连接超时,单位:毫秒
|
||||||
|
*/
|
||||||
|
public int getConnectionTimeout() {
|
||||||
|
return connectionTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置连接超时,单位:毫秒
|
||||||
|
*
|
||||||
|
* @param connectionTimeout 超时毫秒数
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public ClientConfig setConnectionTimeout(final int connectionTimeout) {
|
||||||
|
this.connectionTimeout = connectionTimeout;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取读取超时,单位:毫秒
|
||||||
|
*
|
||||||
|
* @return 读取超时,单位:毫秒
|
||||||
|
*/
|
||||||
|
public int getReadTimeout() {
|
||||||
|
return readTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置读取超时,单位:毫秒
|
||||||
|
*
|
||||||
|
* @param readTimeout 读取超时,单位:毫秒
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public ClientConfig setReadTimeout(final int readTimeout) {
|
||||||
|
this.readTimeout = readTimeout;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取域名验证器
|
||||||
|
*
|
||||||
|
* @return 域名验证器
|
||||||
|
*/
|
||||||
|
public HostnameVerifier getHostnameVerifier() {
|
||||||
|
return hostnameVerifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置域名验证器<br>
|
||||||
|
* 只针对HTTPS请求,如果不设置,不做验证,所有域名被信任
|
||||||
|
*
|
||||||
|
* @param hostnameVerifier HostnameVerifier
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public ClientConfig setHostnameVerifier(final HostnameVerifier hostnameVerifier) {
|
||||||
|
// 验证域
|
||||||
|
this.hostnameVerifier = hostnameVerifier;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取SSLSocketFactory
|
||||||
|
*
|
||||||
|
* @return SSLSocketFactory
|
||||||
|
*/
|
||||||
|
public SSLSocketFactory getSocketFactory() {
|
||||||
|
return socketFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置SSLSocketFactory<br>
|
||||||
|
* 只针对HTTPS请求,如果不设置,使用默认的SSLSocketFactory<br>
|
||||||
|
* 默认SSLSocketFactory为:SSLSocketFactoryBuilder.create().build();
|
||||||
|
*
|
||||||
|
* @param ssf SSLScketFactory
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public ClientConfig setSocketFactory(final SSLSocketFactory ssf) {
|
||||||
|
this.socketFactory = ssf;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置HTTPS安全连接协议,只针对HTTPS请求,可以使用的协议包括:<br>
|
||||||
|
* 此方法调用后{@link #setSocketFactory(SSLSocketFactory)} 将被覆盖。
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 1. TLSv1.2
|
||||||
|
* 2. TLSv1.1
|
||||||
|
* 3. SSLv3
|
||||||
|
* ...
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @param protocol 协议
|
||||||
|
* @return this
|
||||||
|
* @see SSLUtil#createSSLContext(String)
|
||||||
|
* @see #setSocketFactory(SSLSocketFactory)
|
||||||
|
*/
|
||||||
|
public ClientConfig setSSLProtocol(final String protocol) {
|
||||||
|
Assert.notBlank(protocol, "protocol must be not blank!");
|
||||||
|
setSocketFactory(SSLUtil.createSSLContext(protocol).getSocketFactory());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否禁用缓存
|
||||||
|
*
|
||||||
|
* @return 是否禁用缓存
|
||||||
|
*/
|
||||||
|
public boolean isDisableCache() {
|
||||||
|
return disableCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置是否禁用缓存
|
||||||
|
*
|
||||||
|
* @param disableCache 是否禁用缓存
|
||||||
|
*/
|
||||||
|
public void setDisableCache(final boolean disableCache) {
|
||||||
|
this.disableCache = disableCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取代理
|
||||||
|
*
|
||||||
|
* @return 代理
|
||||||
|
*/
|
||||||
|
public Proxy getProxy() {
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置Http代理
|
||||||
|
*
|
||||||
|
* @param host 代理 主机
|
||||||
|
* @param port 代理 端口
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public ClientConfig setHttpProxy(final String host, final int port) {
|
||||||
|
final Proxy proxy = new Proxy(Proxy.Type.HTTP,
|
||||||
|
new InetSocketAddress(host, port));
|
||||||
|
return setProxy(proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置代理
|
||||||
|
*
|
||||||
|
* @param proxy 代理 {@link Proxy}
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public ClientConfig setProxy(final Proxy proxy) {
|
||||||
|
this.proxy = proxy;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package cn.hutool.http.client;
|
package cn.hutool.http.client;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.map.MapUtil;
|
||||||
import cn.hutool.core.text.StrUtil;
|
import cn.hutool.core.text.StrUtil;
|
||||||
import cn.hutool.core.util.ArrayUtil;
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
import cn.hutool.http.meta.Header;
|
import cn.hutool.http.meta.Header;
|
||||||
@ -11,12 +12,12 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP请求头的存储和相关方法
|
* HTTP请求头的存储和读取相关方法
|
||||||
*
|
*
|
||||||
* @param <T> 返回对象类型,方便链式编程
|
* @param <T> 返回对象类型,方便链式编程
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public interface Headers<T extends Headers<T>> {
|
public interface HeaderOperation<T extends HeaderOperation<T>> {
|
||||||
|
|
||||||
// region ----------------------------------------------------------- headers
|
// region ----------------------------------------------------------- headers
|
||||||
|
|
||||||
@ -100,6 +101,27 @@ public interface Headers<T extends Headers<T>> {
|
|||||||
return header(name, value, true);
|
return header(name, value, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置请求头<br>
|
||||||
|
* 不覆盖原有请求头
|
||||||
|
*
|
||||||
|
* @param headerMap 请求头
|
||||||
|
* @param isOverride 是否覆盖
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
default T header(final Map<String, List<String>> headerMap, final boolean isOverride) {
|
||||||
|
if (MapUtil.isNotEmpty(headerMap)) {
|
||||||
|
String name;
|
||||||
|
for (final Map.Entry<String, List<String>> entry : headerMap.entrySet()) {
|
||||||
|
name = entry.getKey();
|
||||||
|
for (final String value : entry.getValue()) {
|
||||||
|
this.header(name, StrUtil.emptyIfNull(value), isOverride);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (T) this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置contentType
|
* 设置contentType
|
||||||
*
|
*
|
@ -4,10 +4,11 @@ import cn.hutool.core.collection.CollUtil;
|
|||||||
import cn.hutool.core.lang.Assert;
|
import cn.hutool.core.lang.Assert;
|
||||||
import cn.hutool.core.map.MapUtil;
|
import cn.hutool.core.map.MapUtil;
|
||||||
import cn.hutool.core.net.url.UrlBuilder;
|
import cn.hutool.core.net.url.UrlBuilder;
|
||||||
|
import cn.hutool.core.text.StrUtil;
|
||||||
import cn.hutool.core.util.CharsetUtil;
|
import cn.hutool.core.util.CharsetUtil;
|
||||||
import cn.hutool.core.util.ObjUtil;
|
import cn.hutool.core.util.ObjUtil;
|
||||||
import cn.hutool.http.HttpGlobalConfig;
|
import cn.hutool.http.HttpGlobalConfig;
|
||||||
import cn.hutool.http.client.body.RequestBody;
|
import cn.hutool.http.client.body.HttpBody;
|
||||||
import cn.hutool.http.meta.Header;
|
import cn.hutool.http.meta.Header;
|
||||||
import cn.hutool.http.meta.Method;
|
import cn.hutool.http.meta.Method;
|
||||||
|
|
||||||
@ -23,7 +24,7 @@ import java.util.Map;
|
|||||||
* @author looly
|
* @author looly
|
||||||
* @since 6.0.0
|
* @since 6.0.0
|
||||||
*/
|
*/
|
||||||
public class Request implements Headers<Request> {
|
public class Request implements HeaderOperation<Request> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建一个HTTP请求<br>
|
* 构建一个HTTP请求<br>
|
||||||
@ -70,7 +71,7 @@ public class Request implements Headers<Request> {
|
|||||||
/**
|
/**
|
||||||
* 请求方法
|
* 请求方法
|
||||||
*/
|
*/
|
||||||
private Method method = Method.GET;
|
private Method method;
|
||||||
/**
|
/**
|
||||||
* 请求的URL
|
* 请求的URL
|
||||||
*/
|
*/
|
||||||
@ -78,8 +79,24 @@ public class Request implements Headers<Request> {
|
|||||||
/**
|
/**
|
||||||
* 存储头信息
|
* 存储头信息
|
||||||
*/
|
*/
|
||||||
private final Map<String, List<String>> headers = new HashMap<>();
|
private final Map<String, List<String>> headers;
|
||||||
private RequestBody body;
|
/**
|
||||||
|
* 请求体
|
||||||
|
*/
|
||||||
|
private HttpBody body;
|
||||||
|
/**
|
||||||
|
* 最大重定向次数
|
||||||
|
*/
|
||||||
|
private int maxRedirectCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认构造
|
||||||
|
*/
|
||||||
|
public Request() {
|
||||||
|
method = Method.GET;
|
||||||
|
headers = new HashMap<>();
|
||||||
|
maxRedirectCount = HttpGlobalConfig.getMaxRedirectCount();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取Http请求方法
|
* 获取Http请求方法
|
||||||
@ -195,7 +212,7 @@ public class Request implements Headers<Request> {
|
|||||||
*
|
*
|
||||||
* @return this
|
* @return this
|
||||||
*/
|
*/
|
||||||
public RequestBody body() {
|
public HttpBody body() {
|
||||||
return this.body;
|
return this.body;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,8 +222,35 @@ public class Request implements Headers<Request> {
|
|||||||
* @param body 请求体,可以是文本、表单、流、byte[] 或 Multipart
|
* @param body 请求体,可以是文本、表单、流、byte[] 或 Multipart
|
||||||
* @return this
|
* @return this
|
||||||
*/
|
*/
|
||||||
public Request body(final RequestBody body) {
|
public Request body(final HttpBody body) {
|
||||||
this.body = body;
|
this.body = body;
|
||||||
|
|
||||||
|
// 根据内容赋值默认Content-Type
|
||||||
|
if (StrUtil.isBlank(header(Header.CONTENT_TYPE))) {
|
||||||
|
header(Header.CONTENT_TYPE, body.getContentType(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取最大重定向请求次数
|
||||||
|
*
|
||||||
|
* @return 最大重定向请求次数
|
||||||
|
*/
|
||||||
|
public int maxRedirectCount() {
|
||||||
|
return maxRedirectCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置最大重定向次数<br>
|
||||||
|
* 如果次数小于1则表示不重定向,大于等于1表示打开重定向
|
||||||
|
*
|
||||||
|
* @param maxRedirectCount 最大重定向次数
|
||||||
|
* @return this
|
||||||
|
*/
|
||||||
|
public Request setMaxRedirectCount(final int maxRedirectCount) {
|
||||||
|
this.maxRedirectCount = Math.max(maxRedirectCount, 0);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
package cn.hutool.http.client;
|
package cn.hutool.http.client;
|
||||||
|
|
||||||
import cn.hutool.core.convert.Convert;
|
import cn.hutool.core.convert.Convert;
|
||||||
|
import cn.hutool.core.io.IoUtil;
|
||||||
import cn.hutool.core.text.StrUtil;
|
import cn.hutool.core.text.StrUtil;
|
||||||
import cn.hutool.http.meta.Header;
|
|
||||||
import cn.hutool.http.HttpException;
|
import cn.hutool.http.HttpException;
|
||||||
import cn.hutool.http.HttpUtil;
|
import cn.hutool.http.HttpUtil;
|
||||||
|
import cn.hutool.http.client.body.ResponseBody;
|
||||||
|
import cn.hutool.http.meta.Header;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@ -49,14 +51,32 @@ public interface Response extends Closeable {
|
|||||||
*/
|
*/
|
||||||
InputStream bodyStream();
|
InputStream bodyStream();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取响应体,包含服务端返回的内容和Content-Type信息
|
||||||
|
* @return {@link ResponseBody}
|
||||||
|
*/
|
||||||
|
default ResponseBody body(){
|
||||||
|
return new ResponseBody(this, true);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取响应主体
|
* 获取响应主体
|
||||||
*
|
*
|
||||||
* @return String
|
* @return String
|
||||||
* @throws HttpException 包装IO异常
|
* @throws HttpException 包装IO异常
|
||||||
*/
|
*/
|
||||||
default String body() throws HttpException {
|
default String bodyStr() throws HttpException {
|
||||||
return HttpUtil.getString(bodyStream(), charset(), true);
|
return HttpUtil.getString(bodyBytes(), charset(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取响应流字节码<br>
|
||||||
|
* 此方法会转为同步模式
|
||||||
|
*
|
||||||
|
* @return byte[]
|
||||||
|
*/
|
||||||
|
default byte[] bodyBytes() {
|
||||||
|
return IoUtil.readBytes(bodyStream());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -22,7 +22,7 @@ import java.util.Map;
|
|||||||
* @author looly
|
* @author looly
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public abstract class FormBody<T extends FormBody<T>> implements RequestBody {
|
public abstract class FormBody<T extends FormBody<T>> implements HttpBody {
|
||||||
/**
|
/**
|
||||||
* 存储表单数据
|
* 存储表单数据
|
||||||
*/
|
*/
|
||||||
|
@ -9,7 +9,7 @@ import java.io.OutputStream;
|
|||||||
/**
|
/**
|
||||||
* 定义请求体接口
|
* 定义请求体接口
|
||||||
*/
|
*/
|
||||||
public interface RequestBody {
|
public interface HttpBody {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 写出数据,不关闭流
|
* 写出数据,不关闭流
|
||||||
@ -18,6 +18,13 @@ public interface RequestBody {
|
|||||||
*/
|
*/
|
||||||
void write(OutputStream out);
|
void write(OutputStream out);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取Content-Type
|
||||||
|
*
|
||||||
|
* @return Content-Type值
|
||||||
|
*/
|
||||||
|
String getContentType();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 写出并关闭{@link OutputStream}
|
* 写出并关闭{@link OutputStream}
|
||||||
*
|
*
|
@ -59,6 +59,7 @@ public class MultipartBody extends FormBody<MultipartBody> {
|
|||||||
public MultipartBody(final Map<String, Object> form, final Charset charset, final String boundary) {
|
public MultipartBody(final Map<String, Object> form, final Charset charset, final String boundary) {
|
||||||
super(form, charset);
|
super(form, charset);
|
||||||
this.boundary = boundary;
|
this.boundary = boundary;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -66,6 +67,7 @@ public class MultipartBody extends FormBody<MultipartBody> {
|
|||||||
*
|
*
|
||||||
* @return Multipart的Content-Type类型
|
* @return Multipart的Content-Type类型
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public String getContentType() {
|
public String getContentType() {
|
||||||
return CONTENT_TYPE_MULTIPART_PREFIX + boundary;
|
return CONTENT_TYPE_MULTIPART_PREFIX + boundary;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package cn.hutool.http.client.body;
|
package cn.hutool.http.client.body;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.resource.HttpResource;
|
||||||
import cn.hutool.core.io.resource.Resource;
|
import cn.hutool.core.io.resource.Resource;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@ -11,9 +12,9 @@ import java.io.OutputStream;
|
|||||||
* @author looly
|
* @author looly
|
||||||
* @since 6.0.0
|
* @since 6.0.0
|
||||||
*/
|
*/
|
||||||
public class ResourceBody implements RequestBody {
|
public class ResourceBody implements HttpBody {
|
||||||
|
|
||||||
private final Resource resource;
|
private final HttpResource resource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建 Http request body
|
* 创建 Http request body
|
||||||
@ -21,7 +22,7 @@ public class ResourceBody implements RequestBody {
|
|||||||
* @param resource body内容
|
* @param resource body内容
|
||||||
* @return BytesBody
|
* @return BytesBody
|
||||||
*/
|
*/
|
||||||
public static ResourceBody of(final Resource resource) {
|
public static ResourceBody of(final HttpResource resource) {
|
||||||
return new ResourceBody(resource);
|
return new ResourceBody(resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,7 +31,7 @@ public class ResourceBody implements RequestBody {
|
|||||||
*
|
*
|
||||||
* @param resource Body内容
|
* @param resource Body内容
|
||||||
*/
|
*/
|
||||||
public ResourceBody(final Resource resource) {
|
public ResourceBody(final HttpResource resource) {
|
||||||
this.resource = resource;
|
this.resource = resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,4 +53,9 @@ public class ResourceBody implements RequestBody {
|
|||||||
public InputStream getStream() {
|
public InputStream getStream() {
|
||||||
return resource.getStream();
|
return resource.getStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getContentType() {
|
||||||
|
return this.resource.getContentType();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
243
hutool-http/src/main/java/cn/hutool/http/client/body/ResponseBody.java
Executable file
243
hutool-http/src/main/java/cn/hutool/http/client/body/ResponseBody.java
Executable file
@ -0,0 +1,243 @@
|
|||||||
|
package cn.hutool.http.client.body;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.FileUtil;
|
||||||
|
import cn.hutool.core.io.IORuntimeException;
|
||||||
|
import cn.hutool.core.io.IoUtil;
|
||||||
|
import cn.hutool.core.io.StreamProgress;
|
||||||
|
import cn.hutool.core.io.file.FileNameUtil;
|
||||||
|
import cn.hutool.core.lang.Assert;
|
||||||
|
import cn.hutool.core.regex.ReUtil;
|
||||||
|
import cn.hutool.core.text.StrUtil;
|
||||||
|
import cn.hutool.core.util.ObjUtil;
|
||||||
|
import cn.hutool.http.HttpException;
|
||||||
|
import cn.hutool.http.client.Response;
|
||||||
|
import cn.hutool.http.meta.Header;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应体部分封装
|
||||||
|
*
|
||||||
|
* @author looly
|
||||||
|
*/
|
||||||
|
public class ResponseBody implements HttpBody {
|
||||||
|
|
||||||
|
private final Response response;
|
||||||
|
/**
|
||||||
|
* 是否忽略响应读取时可能的EOF异常。<br>
|
||||||
|
* 在Http协议中,对于Transfer-Encoding: Chunked在正常情况下末尾会写入一个Length为0的的chunk标识完整结束。<br>
|
||||||
|
* 如果服务端未遵循这个规范或响应没有正常结束,会报EOF异常,此选项用于是否忽略这个异常。
|
||||||
|
*/
|
||||||
|
private final boolean isIgnoreEOFError;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*
|
||||||
|
* @param response 响应体
|
||||||
|
* @param isIgnoreEOFError 是否忽略EOF错误
|
||||||
|
*/
|
||||||
|
public ResponseBody(final Response response, final boolean isIgnoreEOFError) {
|
||||||
|
this.response = response;
|
||||||
|
this.isIgnoreEOFError = isIgnoreEOFError;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getContentType() {
|
||||||
|
return response.header(Header.CONTENT_TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InputStream getStream() {
|
||||||
|
return response.bodyStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(final OutputStream out) {
|
||||||
|
write(out, false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将响应内容写出到{@link OutputStream}<br>
|
||||||
|
* 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出<br>
|
||||||
|
* 写出后会关闭Http流(异步模式)
|
||||||
|
*
|
||||||
|
* @param out 写出的流
|
||||||
|
* @param isCloseOut 是否关闭输出流
|
||||||
|
* @param streamProgress 进度显示接口,通过实现此接口显示下载进度
|
||||||
|
* @return 写出bytes数
|
||||||
|
* @since 3.3.2
|
||||||
|
*/
|
||||||
|
public long write(final OutputStream out, final boolean isCloseOut, final StreamProgress streamProgress) {
|
||||||
|
Assert.notNull(out, "[out] must be not null!");
|
||||||
|
final long contentLength = response.contentLength();
|
||||||
|
try {
|
||||||
|
return copyBody(getStream(), out, contentLength, streamProgress, isIgnoreEOFError);
|
||||||
|
} finally {
|
||||||
|
if (isCloseOut) {
|
||||||
|
IoUtil.close(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将响应内容写出到文件
|
||||||
|
*
|
||||||
|
* @param targetFileOrDir 写出到的文件或目录的路径
|
||||||
|
* @return 写出的文件
|
||||||
|
*/
|
||||||
|
public File write(final String targetFileOrDir) {
|
||||||
|
return write(FileUtil.file(targetFileOrDir));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将响应内容写出到文件<br>
|
||||||
|
* 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出<br>
|
||||||
|
* 写出后会关闭Http流(异步模式)
|
||||||
|
*
|
||||||
|
* @param targetFileOrDir 写出到的文件或目录
|
||||||
|
* @return 写出的文件
|
||||||
|
*/
|
||||||
|
public File write(final File targetFileOrDir) {
|
||||||
|
return write(targetFileOrDir, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将响应内容写出到文件-避免未完成的文件
|
||||||
|
* 来自:<a href="https://gitee.com/dromara/hutool/pulls/407">https://gitee.com/dromara/hutool/pulls/407</a><br>
|
||||||
|
* 此方法原理是先在目标文件同级目录下创建临时文件,下载之,等下载完毕后重命名,避免因下载错误导致的文件不完整。
|
||||||
|
*
|
||||||
|
* @param targetFileOrDir 写出到的文件或目录
|
||||||
|
* @param streamProgress 进度显示接口,通过实现此接口显示下载进度
|
||||||
|
* @return 写出的文件对象
|
||||||
|
*/
|
||||||
|
public File write(final File targetFileOrDir, final StreamProgress streamProgress) {
|
||||||
|
return write(targetFileOrDir, null, streamProgress);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将响应内容写出到文件-避免未完成的文件
|
||||||
|
* 来自:<a href="https://gitee.com/dromara/hutool/pulls/407">https://gitee.com/dromara/hutool/pulls/407</a><br>
|
||||||
|
* 此方法原理是先在目标文件同级目录下创建临时文件,下载之,等下载完毕后重命名,避免因下载错误导致的文件不完整。
|
||||||
|
*
|
||||||
|
* @param targetFileOrDir 写出到的文件或目录
|
||||||
|
* @param tempFileSuffix 临时文件后缀,默认".temp"
|
||||||
|
* @param streamProgress 进度显示接口,通过实现此接口显示下载进度
|
||||||
|
* @return 写出的文件对象
|
||||||
|
* @since 5.7.12
|
||||||
|
*/
|
||||||
|
public File write(final File targetFileOrDir, final String tempFileSuffix, final StreamProgress streamProgress) {
|
||||||
|
Assert.notNull(targetFileOrDir, "[targetFileOrDir] must be not null!");
|
||||||
|
File outFile = getTargetFile(targetFileOrDir, null);
|
||||||
|
// 目标文件真实名称
|
||||||
|
final String fileName = outFile.getName();
|
||||||
|
|
||||||
|
// 临时文件
|
||||||
|
outFile = new File(outFile.getParentFile(), FileNameUtil.addTempSuffix(fileName, tempFileSuffix));
|
||||||
|
|
||||||
|
try {
|
||||||
|
outFile = writeDirect(outFile, null, streamProgress);
|
||||||
|
// 重命名下载好的临时文件
|
||||||
|
return FileUtil.rename(outFile, fileName, true);
|
||||||
|
} catch (final Throwable e) {
|
||||||
|
// 异常则删除临时文件
|
||||||
|
FileUtil.del(outFile);
|
||||||
|
throw new HttpException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将响应内容直接写出到文件,目标为目录则从Content-Disposition中获取文件名
|
||||||
|
*
|
||||||
|
* @param targetFileOrDir 写出到的文件
|
||||||
|
* @param customParamName 自定义的Content-Disposition中文件名的参数名
|
||||||
|
* @param streamProgress 进度显示接口,通过实现此接口显示下载进度
|
||||||
|
* @return 写出的文件
|
||||||
|
*/
|
||||||
|
public File writeDirect(final File targetFileOrDir, final String customParamName, final StreamProgress streamProgress) {
|
||||||
|
Assert.notNull(targetFileOrDir, "[targetFileOrDir] must be not null!");
|
||||||
|
|
||||||
|
final File outFile = getTargetFile(targetFileOrDir, customParamName);
|
||||||
|
write(FileUtil.getOutputStream(outFile), true, streamProgress);
|
||||||
|
|
||||||
|
return outFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// region ---------------------------------------------------------------------------- Private Methods
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从响应头补全下载文件名,返回补全名称后的文件
|
||||||
|
*
|
||||||
|
* @param targetFileOrDir 目标文件夹或者目标文件
|
||||||
|
* @param customParamName 自定义的参数名称,如果传入{@code null},默认使用"filename"
|
||||||
|
* @return File 保存的文件
|
||||||
|
* @since 5.4.1
|
||||||
|
*/
|
||||||
|
private File getTargetFile(final File targetFileOrDir, final String customParamName) {
|
||||||
|
if (false == targetFileOrDir.isDirectory()) {
|
||||||
|
// 非目录直接返回
|
||||||
|
return targetFileOrDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从头信息中获取文件名
|
||||||
|
final String fileName = getFileNameFromDisposition(ObjUtil.defaultIfNull(customParamName, "filename"));
|
||||||
|
if (StrUtil.isBlank(fileName)) {
|
||||||
|
throw new HttpException("Can`t get file name from [Content-Disposition]!");
|
||||||
|
}
|
||||||
|
return FileUtil.file(targetFileOrDir, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从Content-Disposition头中获取文件名
|
||||||
|
*
|
||||||
|
* @param paramName 文件名的参数名
|
||||||
|
* @return 文件名,empty表示无
|
||||||
|
* @since 5.8.10
|
||||||
|
*/
|
||||||
|
private String getFileNameFromDisposition(final String paramName) {
|
||||||
|
String fileName = null;
|
||||||
|
final String disposition = response.header(Header.CONTENT_DISPOSITION);
|
||||||
|
if (StrUtil.isNotBlank(disposition)) {
|
||||||
|
fileName = ReUtil.get(paramName + "=\"(.*?)\"", disposition, 1);
|
||||||
|
if (StrUtil.isBlank(fileName)) {
|
||||||
|
fileName = StrUtil.subAfter(disposition, paramName + "=", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将响应内容写出到{@link OutputStream}<br>
|
||||||
|
* 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出<br>
|
||||||
|
* 写出后会关闭Http流(异步模式)
|
||||||
|
*
|
||||||
|
* @param in 输入流
|
||||||
|
* @param out 写出的流
|
||||||
|
* @param contentLength 总长度,-1表示未知
|
||||||
|
* @param streamProgress 进度显示接口,通过实现此接口显示下载进度
|
||||||
|
* @param isIgnoreEOFError 是否忽略响应读取时可能的EOF异常
|
||||||
|
* @return 拷贝长度
|
||||||
|
*/
|
||||||
|
private static long copyBody(final InputStream in, final OutputStream out, final long contentLength, final StreamProgress streamProgress, final boolean isIgnoreEOFError) {
|
||||||
|
if (null == out) {
|
||||||
|
throw new NullPointerException("[out] is null!");
|
||||||
|
}
|
||||||
|
|
||||||
|
long copyLength = -1;
|
||||||
|
try {
|
||||||
|
copyLength = IoUtil.copy(in, out, IoUtil.DEFAULT_BUFFER_SIZE, contentLength, streamProgress);
|
||||||
|
} catch (final IORuntimeException e) {
|
||||||
|
//noinspection StatementWithEmptyBody
|
||||||
|
if (isIgnoreEOFError
|
||||||
|
&& (e.getCause() instanceof EOFException || StrUtil.containsIgnoreCase(e.getMessage(), "Premature EOF"))) {
|
||||||
|
// 忽略读取HTTP流中的EOF错误
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return copyLength;
|
||||||
|
}
|
||||||
|
// endregion ---------------------------------------------------------------------------- Private Methods
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package cn.hutool.http.client.body;
|
package cn.hutool.http.client.body;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.resource.HttpResource;
|
||||||
import cn.hutool.core.io.resource.StringResource;
|
import cn.hutool.core.io.resource.StringResource;
|
||||||
import cn.hutool.http.HttpUtil;
|
import cn.hutool.http.HttpUtil;
|
||||||
|
|
||||||
@ -30,6 +31,6 @@ public class StringBody extends ResourceBody {
|
|||||||
* @param charset 自定义编码
|
* @param charset 自定义编码
|
||||||
*/
|
*/
|
||||||
public StringBody(final String body, final String contentType, final Charset charset) {
|
public StringBody(final String body, final String contentType, final Charset charset) {
|
||||||
super(new StringResource(body, contentType, charset));
|
super(new HttpResource(new StringResource(body, contentType, charset), contentType));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package cn.hutool.http.client.body;
|
|||||||
import cn.hutool.core.io.IoUtil;
|
import cn.hutool.core.io.IoUtil;
|
||||||
import cn.hutool.core.net.url.UrlQuery;
|
import cn.hutool.core.net.url.UrlQuery;
|
||||||
import cn.hutool.core.text.StrUtil;
|
import cn.hutool.core.text.StrUtil;
|
||||||
|
import cn.hutool.http.meta.ContentType;
|
||||||
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
@ -42,4 +43,9 @@ public class UrlEncodedFormBody extends FormBody<UrlEncodedFormBody> {
|
|||||||
final byte[] bytes = StrUtil.bytes(UrlQuery.of(form, true).build(charset), charset);
|
final byte[] bytes = StrUtil.bytes(UrlQuery.of(form, true).build(charset), charset);
|
||||||
IoUtil.write(out, false, bytes);
|
IoUtil.write(out, false, bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getContentType() {
|
||||||
|
return ContentType.FORM_URLENCODED.toString(charset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package cn.hutool.http.client.engine.httpclient4;
|
package cn.hutool.http.client.engine.httpclient4;
|
||||||
|
|
||||||
import cn.hutool.http.client.body.BytesBody;
|
import cn.hutool.http.client.body.BytesBody;
|
||||||
import cn.hutool.http.client.body.RequestBody;
|
import cn.hutool.http.client.body.HttpBody;
|
||||||
import org.apache.http.entity.AbstractHttpEntity;
|
import org.apache.http.entity.AbstractHttpEntity;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@ -9,14 +9,14 @@ import java.io.OutputStream;
|
|||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link RequestBody}转换为{@link org.apache.hc.core5.http.HttpEntity}对象
|
* {@link HttpBody}转换为{@link org.apache.hc.core5.http.HttpEntity}对象
|
||||||
*
|
*
|
||||||
* @author looly
|
* @author looly
|
||||||
* @since 6.0.0
|
* @since 6.0.0
|
||||||
*/
|
*/
|
||||||
public class HttpClient4BodyEntity extends AbstractHttpEntity {
|
public class HttpClient4BodyEntity extends AbstractHttpEntity {
|
||||||
|
|
||||||
private final RequestBody body;
|
private final HttpBody body;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造
|
* 构造
|
||||||
@ -24,9 +24,9 @@ public class HttpClient4BodyEntity extends AbstractHttpEntity {
|
|||||||
* @param contentType Content-Type类型
|
* @param contentType Content-Type类型
|
||||||
* @param charset 自定义请求编码
|
* @param charset 自定义请求编码
|
||||||
* @param chunked 是否块模式传输
|
* @param chunked 是否块模式传输
|
||||||
* @param body {@link RequestBody}
|
* @param body {@link HttpBody}
|
||||||
*/
|
*/
|
||||||
public HttpClient4BodyEntity(final String contentType, final Charset charset, final boolean chunked, final RequestBody body) {
|
public HttpClient4BodyEntity(final String contentType, final Charset charset, final boolean chunked, final HttpBody body) {
|
||||||
super();
|
super();
|
||||||
setContentType(contentType);
|
setContentType(contentType);
|
||||||
setContentEncoding(null == charset ? null : charset.name());
|
setContentEncoding(null == charset ? null : charset.name());
|
||||||
|
@ -7,7 +7,7 @@ import cn.hutool.http.HttpException;
|
|||||||
import cn.hutool.http.client.ClientEngine;
|
import cn.hutool.http.client.ClientEngine;
|
||||||
import cn.hutool.http.client.Request;
|
import cn.hutool.http.client.Request;
|
||||||
import cn.hutool.http.client.Response;
|
import cn.hutool.http.client.Response;
|
||||||
import cn.hutool.http.client.body.RequestBody;
|
import cn.hutool.http.client.body.HttpBody;
|
||||||
import org.apache.http.Header;
|
import org.apache.http.Header;
|
||||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
|
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
|
||||||
@ -87,7 +87,7 @@ public class HttpClient4Engine implements ClientEngine {
|
|||||||
request.setHeaders(toHeaderList(message.headers()).toArray(new Header[0]));
|
request.setHeaders(toHeaderList(message.headers()).toArray(new Header[0]));
|
||||||
|
|
||||||
// 填充自定义消息体
|
// 填充自定义消息体
|
||||||
final RequestBody body = message.body();
|
final HttpBody body = message.body();
|
||||||
request.setEntity(new HttpClient4BodyEntity(
|
request.setEntity(new HttpClient4BodyEntity(
|
||||||
// 用户自定义的内容类型
|
// 用户自定义的内容类型
|
||||||
message.header(cn.hutool.http.meta.Header.CONTENT_TYPE),
|
message.header(cn.hutool.http.meta.Header.CONTENT_TYPE),
|
||||||
|
@ -81,7 +81,7 @@ public class HttpClient4Response implements Response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String body() throws HttpException {
|
public String bodyStr() throws HttpException {
|
||||||
try {
|
try {
|
||||||
return EntityUtils.toString(rawRes.getEntity(), charset());
|
return EntityUtils.toString(rawRes.getEntity(), charset());
|
||||||
} catch (final IOException e) {
|
} catch (final IOException e) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package cn.hutool.http.client.engine.httpclient5;
|
package cn.hutool.http.client.engine.httpclient5;
|
||||||
|
|
||||||
import cn.hutool.http.client.body.BytesBody;
|
import cn.hutool.http.client.body.BytesBody;
|
||||||
import cn.hutool.http.client.body.RequestBody;
|
import cn.hutool.http.client.body.HttpBody;
|
||||||
import org.apache.hc.core5.http.io.entity.AbstractHttpEntity;
|
import org.apache.hc.core5.http.io.entity.AbstractHttpEntity;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -10,14 +10,14 @@ import java.io.OutputStream;
|
|||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link RequestBody}转换为{@link org.apache.hc.core5.http.HttpEntity}对象
|
* {@link HttpBody}转换为{@link org.apache.hc.core5.http.HttpEntity}对象
|
||||||
*
|
*
|
||||||
* @author looly
|
* @author looly
|
||||||
* @since 6.0.0
|
* @since 6.0.0
|
||||||
*/
|
*/
|
||||||
public class HttpClient5BodyEntity extends AbstractHttpEntity {
|
public class HttpClient5BodyEntity extends AbstractHttpEntity {
|
||||||
|
|
||||||
private final RequestBody body;
|
private final HttpBody body;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造
|
* 构造
|
||||||
@ -25,9 +25,9 @@ public class HttpClient5BodyEntity extends AbstractHttpEntity {
|
|||||||
* @param contentType Content-Type类型
|
* @param contentType Content-Type类型
|
||||||
* @param charset 自定义请求编码
|
* @param charset 自定义请求编码
|
||||||
* @param chunked 是否块模式传输
|
* @param chunked 是否块模式传输
|
||||||
* @param body {@link RequestBody}
|
* @param body {@link HttpBody}
|
||||||
*/
|
*/
|
||||||
public HttpClient5BodyEntity(final String contentType, final Charset charset, final boolean chunked, final RequestBody body) {
|
public HttpClient5BodyEntity(final String contentType, final Charset charset, final boolean chunked, final HttpBody body) {
|
||||||
super(contentType, null == charset ? null : charset.name(), chunked);
|
super(contentType, null == charset ? null : charset.name(), chunked);
|
||||||
this.body = body;
|
this.body = body;
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import cn.hutool.http.HttpException;
|
|||||||
import cn.hutool.http.client.ClientEngine;
|
import cn.hutool.http.client.ClientEngine;
|
||||||
import cn.hutool.http.client.Request;
|
import cn.hutool.http.client.Request;
|
||||||
import cn.hutool.http.client.Response;
|
import cn.hutool.http.client.Response;
|
||||||
import cn.hutool.http.client.body.RequestBody;
|
import cn.hutool.http.client.body.HttpBody;
|
||||||
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
|
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
|
||||||
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.CloseableHttpResponse;
|
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
|
||||||
@ -83,7 +83,7 @@ public class HttpClient5Engine implements ClientEngine {
|
|||||||
request.setHeaders(toHeaderList(message.headers()).toArray(new Header[0]));
|
request.setHeaders(toHeaderList(message.headers()).toArray(new Header[0]));
|
||||||
|
|
||||||
// 填充自定义消息体
|
// 填充自定义消息体
|
||||||
final RequestBody body = message.body();
|
final HttpBody body = message.body();
|
||||||
request.setEntity(new HttpClient5BodyEntity(
|
request.setEntity(new HttpClient5BodyEntity(
|
||||||
// 用户自定义的内容类型
|
// 用户自定义的内容类型
|
||||||
message.header(cn.hutool.http.meta.Header.CONTENT_TYPE),
|
message.header(cn.hutool.http.meta.Header.CONTENT_TYPE),
|
||||||
|
@ -81,7 +81,7 @@ public class HttpClient5Response implements Response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String body() throws HttpException {
|
public String bodyStr() throws HttpException {
|
||||||
try {
|
try {
|
||||||
return EntityUtils.toString(rawRes.getEntity(), charset());
|
return EntityUtils.toString(rawRes.getEntity(), charset());
|
||||||
} catch (final IOException e) {
|
} catch (final IOException e) {
|
||||||
|
@ -6,7 +6,7 @@ import cn.hutool.core.map.MapUtil;
|
|||||||
import cn.hutool.core.text.StrUtil;
|
import cn.hutool.core.text.StrUtil;
|
||||||
import cn.hutool.core.util.CharsetUtil;
|
import cn.hutool.core.util.CharsetUtil;
|
||||||
import cn.hutool.http.meta.Header;
|
import cn.hutool.http.meta.Header;
|
||||||
import cn.hutool.http.client.Headers;
|
import cn.hutool.http.client.HeaderOperation;
|
||||||
|
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -23,7 +23,7 @@ import java.util.Map.Entry;
|
|||||||
* @author Looly
|
* @author Looly
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public abstract class HttpBase<T extends HttpBase<T>> implements Headers<T> {
|
public abstract class HttpBase<T extends HttpBase<T>> implements HeaderOperation<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 默认的请求编码、URL的encode、decode编码
|
* 默认的请求编码、URL的encode、decode编码
|
||||||
@ -253,10 +253,19 @@ public abstract class HttpBase<T extends HttpBase<T>> implements Headers<T> {
|
|||||||
*
|
*
|
||||||
* @return 字符集
|
* @return 字符集
|
||||||
*/
|
*/
|
||||||
public String charset() {
|
public String charsetName() {
|
||||||
return charset.name();
|
return charset.name();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回字符集
|
||||||
|
*
|
||||||
|
* @return 字符集
|
||||||
|
*/
|
||||||
|
public Charset charset() {
|
||||||
|
return this.charset;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置字符集
|
* 设置字符集
|
||||||
*
|
*
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
package cn.hutool.http.client.engine.jdk;
|
package cn.hutool.http.client.engine.jdk;
|
||||||
|
|
||||||
import cn.hutool.core.map.MapUtil;
|
|
||||||
import cn.hutool.core.net.url.URLUtil;
|
import cn.hutool.core.net.url.URLUtil;
|
||||||
import cn.hutool.core.reflect.FieldUtil;
|
import cn.hutool.core.reflect.FieldUtil;
|
||||||
import cn.hutool.core.text.StrUtil;
|
import cn.hutool.core.text.StrUtil;
|
||||||
import cn.hutool.core.util.ObjUtil;
|
import cn.hutool.core.util.ObjUtil;
|
||||||
import cn.hutool.http.HttpException;
|
import cn.hutool.http.HttpException;
|
||||||
import cn.hutool.http.HttpUtil;
|
import cn.hutool.http.HttpUtil;
|
||||||
import cn.hutool.http.meta.Header;
|
import cn.hutool.http.client.HeaderOperation;
|
||||||
import cn.hutool.http.meta.Method;
|
import cn.hutool.http.meta.Method;
|
||||||
import cn.hutool.http.ssl.DefaultSSLInfo;
|
import cn.hutool.http.ssl.DefaultSSLInfo;
|
||||||
|
|
||||||
@ -21,23 +20,21 @@ import java.net.HttpURLConnection;
|
|||||||
import java.net.ProtocolException;
|
import java.net.ProtocolException;
|
||||||
import java.net.Proxy;
|
import java.net.Proxy;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLConnection;
|
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.charset.UnsupportedCharsetException;
|
import java.nio.charset.UnsupportedCharsetException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* http连接对象,对HttpURLConnection的包装
|
* http连接对象,对HttpURLConnection的包装
|
||||||
*
|
*
|
||||||
* @author Looly
|
* @author Looly
|
||||||
*/
|
*/
|
||||||
public class HttpConnection {
|
public class HttpConnection implements HeaderOperation<HttpConnection> {
|
||||||
|
|
||||||
private final URL url;
|
private final URL url;
|
||||||
private final Proxy proxy;
|
private final Proxy proxy;
|
||||||
private HttpURLConnection conn;
|
private final HttpURLConnection conn;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建HttpConnection
|
* 创建HttpConnection
|
||||||
@ -61,7 +58,7 @@ public class HttpConnection {
|
|||||||
return new HttpConnection(url, proxy);
|
return new HttpConnection(url, proxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------------- Constructor start
|
// region --------------------------------------------------------------- Constructor
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造HttpConnection
|
* 构造HttpConnection
|
||||||
@ -74,31 +71,14 @@ public class HttpConnection {
|
|||||||
this.proxy = proxy;
|
this.proxy = proxy;
|
||||||
|
|
||||||
// 初始化Http连接
|
// 初始化Http连接
|
||||||
initConn();
|
this.conn = HttpUrlConnectionUtil.openHttp(url, proxy);
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------------------- Constructor end
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化连接相关信息
|
|
||||||
*
|
|
||||||
* @return HttpConnection
|
|
||||||
* @since 4.4.1
|
|
||||||
*/
|
|
||||||
public HttpConnection initConn() {
|
|
||||||
try {
|
|
||||||
this.conn = openHttp();
|
|
||||||
} catch (final IOException e) {
|
|
||||||
throw new HttpException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 默认读取响应内容
|
// 默认读取响应内容
|
||||||
this.conn.setDoInput(true);
|
this.conn.setDoInput(true);
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------------- Getters And Setters start
|
// endregion --------------------------------------------------------------- Constructor
|
||||||
|
|
||||||
|
// region --------------------------------------------------------------- Getters And Setters
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取请求方法,GET/POST
|
* 获取请求方法,GET/POST
|
||||||
@ -165,127 +145,15 @@ public class HttpConnection {
|
|||||||
return conn;
|
return conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------------- Getters And Setters end
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------- Headers start
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置请求头<br>
|
* 是否禁用缓存
|
||||||
* 当请求头存在时,覆盖之
|
|
||||||
*
|
|
||||||
* @param header 头名
|
|
||||||
* @param value 头值
|
|
||||||
* @param isOverride 是否覆盖旧值
|
|
||||||
* @return HttpConnection
|
|
||||||
*/
|
|
||||||
public HttpConnection header(final String header, final String value, final boolean isOverride) {
|
|
||||||
if (null != this.conn) {
|
|
||||||
if (isOverride) {
|
|
||||||
this.conn.setRequestProperty(header, value);
|
|
||||||
} else {
|
|
||||||
this.conn.addRequestProperty(header, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置请求头<br>
|
|
||||||
* 当请求头存在时,覆盖之
|
|
||||||
*
|
|
||||||
* @param header 头名
|
|
||||||
* @param value 头值
|
|
||||||
* @param isOverride 是否覆盖旧值
|
|
||||||
* @return HttpConnection
|
|
||||||
*/
|
|
||||||
public HttpConnection header(final Header header, final String value, final boolean isOverride) {
|
|
||||||
return header(header.toString(), value, isOverride);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置请求头<br>
|
|
||||||
* 不覆盖原有请求头
|
|
||||||
*
|
|
||||||
* @param headerMap 请求头
|
|
||||||
* @param isOverride 是否覆盖
|
|
||||||
* @return this
|
|
||||||
*/
|
|
||||||
public HttpConnection header(final Map<String, List<String>> headerMap, final boolean isOverride) {
|
|
||||||
if (MapUtil.isNotEmpty(headerMap)) {
|
|
||||||
String name;
|
|
||||||
for (final Entry<String, List<String>> entry : headerMap.entrySet()) {
|
|
||||||
name = entry.getKey();
|
|
||||||
for (final String value : entry.getValue()) {
|
|
||||||
this.header(name, StrUtil.emptyIfNull(value), isOverride);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取Http请求头
|
|
||||||
*
|
|
||||||
* @param name Header名
|
|
||||||
* @return Http请求头值
|
|
||||||
*/
|
|
||||||
public String header(final String name) {
|
|
||||||
return this.conn.getHeaderField(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取Http请求头
|
|
||||||
*
|
|
||||||
* @param name Header名
|
|
||||||
* @return Http请求头值
|
|
||||||
*/
|
|
||||||
public String header(final Header name) {
|
|
||||||
return header(name.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有Http请求头
|
|
||||||
*
|
|
||||||
* @return Http请求头Map
|
|
||||||
*/
|
|
||||||
public Map<String, List<String>> headers() {
|
|
||||||
return this.conn.getHeaderFields();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------- Headers end
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置https请求参数<br>
|
|
||||||
* 有些时候htts请求会出现com.sun.net.ssl.internal.www.protocol.https.HttpsURLConnectionOldImpl的实现,此为sun内部api,按照普通http请求处理
|
|
||||||
*
|
|
||||||
* @param hostnameVerifier 域名验证器,非https传入null
|
|
||||||
* @param ssf SSLSocketFactory,非https传入null
|
|
||||||
* @return this
|
|
||||||
* @throws HttpException KeyManagementException和NoSuchAlgorithmException异常包装
|
|
||||||
*/
|
|
||||||
public HttpConnection setHttpsInfo(final HostnameVerifier hostnameVerifier, final SSLSocketFactory ssf) throws HttpException {
|
|
||||||
final HttpURLConnection conn = this.conn;
|
|
||||||
|
|
||||||
if (conn instanceof HttpsURLConnection) {
|
|
||||||
// Https请求
|
|
||||||
final HttpsURLConnection httpsConn = (HttpsURLConnection) conn;
|
|
||||||
// 验证域
|
|
||||||
httpsConn.setHostnameVerifier(ObjUtil.defaultIfNull(hostnameVerifier, DefaultSSLInfo.TRUST_ANY_HOSTNAME_VERIFIER));
|
|
||||||
httpsConn.setSSLSocketFactory(ObjUtil.defaultIfNull(ssf, DefaultSSLInfo.DEFAULT_SSF));
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 关闭缓存
|
|
||||||
*
|
*
|
||||||
|
* @param isDisableCache 是否禁用缓存
|
||||||
* @return this
|
* @return this
|
||||||
* @see HttpURLConnection#setUseCaches(boolean)
|
* @see HttpURLConnection#setUseCaches(boolean)
|
||||||
*/
|
*/
|
||||||
public HttpConnection disableCache() {
|
public HttpConnection setDisableCache(final boolean isDisableCache) {
|
||||||
this.conn.setUseCaches(false);
|
this.conn.setUseCaches(!isDisableCache);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -331,15 +199,25 @@ public class HttpConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置Cookie
|
* 设置https请求参数<br>
|
||||||
|
* 有些时候htts请求会出现com.sun.net.ssl.internal.www.protocol.https.HttpsURLConnectionOldImpl的实现,此为sun内部api,按照普通http请求处理
|
||||||
*
|
*
|
||||||
* @param cookie Cookie
|
* @param hostnameVerifier 域名验证器,非https传入null
|
||||||
|
* @param ssf SSLSocketFactory,非https传入null
|
||||||
* @return this
|
* @return this
|
||||||
|
* @throws HttpException KeyManagementException和NoSuchAlgorithmException异常包装
|
||||||
*/
|
*/
|
||||||
public HttpConnection setCookie(final String cookie) {
|
public HttpConnection setHttpsInfo(final HostnameVerifier hostnameVerifier, final SSLSocketFactory ssf) throws HttpException {
|
||||||
if (cookie != null) {
|
final HttpURLConnection conn = this.conn;
|
||||||
header(Header.COOKIE, cookie, true);
|
|
||||||
|
if (conn instanceof HttpsURLConnection) {
|
||||||
|
// Https请求
|
||||||
|
final HttpsURLConnection httpsConn = (HttpsURLConnection) conn;
|
||||||
|
// 验证域
|
||||||
|
httpsConn.setHostnameVerifier(ObjUtil.defaultIfNull(hostnameVerifier, DefaultSSLInfo.TRUST_ANY_HOSTNAME_VERIFIER));
|
||||||
|
httpsConn.setSSLSocketFactory(ObjUtil.defaultIfNull(ssf, DefaultSSLInfo.DEFAULT_SSF));
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -368,6 +246,55 @@ public class HttpConnection {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// endregion --------------------------------------------------------------- Getters And Setters
|
||||||
|
|
||||||
|
// region ---------------------------------------------------------------- Headers
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置请求头<br>
|
||||||
|
* 当请求头存在时,覆盖之
|
||||||
|
*
|
||||||
|
* @param header 头名
|
||||||
|
* @param value 头值
|
||||||
|
* @param isOverride 是否覆盖旧值
|
||||||
|
* @return HttpConnection
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public HttpConnection header(final String header, final String value, final boolean isOverride) {
|
||||||
|
if (null != this.conn) {
|
||||||
|
if (isOverride) {
|
||||||
|
this.conn.setRequestProperty(header, value);
|
||||||
|
} else {
|
||||||
|
this.conn.addRequestProperty(header, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取Http请求头
|
||||||
|
*
|
||||||
|
* @param name Header名
|
||||||
|
* @return Http请求头值
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String header(final String name) {
|
||||||
|
return this.conn.getHeaderField(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有Http请求头
|
||||||
|
*
|
||||||
|
* @return Http请求头Map
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Map<String, List<String>> headers() {
|
||||||
|
return this.conn.getHeaderFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
// endregion---------------------------------------------------------------- Headers
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 连接
|
* 连接
|
||||||
*
|
*
|
||||||
@ -456,7 +383,7 @@ public class HttpConnection {
|
|||||||
// 在sun.net.www.protocol.http.HttpURLConnection.getOutputStream0方法中,会把GET方法
|
// 在sun.net.www.protocol.http.HttpURLConnection.getOutputStream0方法中,会把GET方法
|
||||||
// 修改为POST,而且无法调用setRequestMethod方法修改,因此此处使用反射强制修改字段属性值
|
// 修改为POST,而且无法调用setRequestMethod方法修改,因此此处使用反射强制修改字段属性值
|
||||||
// https://stackoverflow.com/questions/978061/http-get-with-request-body/983458
|
// https://stackoverflow.com/questions/978061/http-get-with-request-body/983458
|
||||||
if(method == Method.GET && method != getMethod()){
|
if (method == Method.GET && method != getMethod()) {
|
||||||
FieldUtil.setFieldValue(this.conn, "method", Method.GET.name());
|
FieldUtil.setFieldValue(this.conn, "method", Method.GET.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -469,7 +396,7 @@ public class HttpConnection {
|
|||||||
* @return 响应码
|
* @return 响应码
|
||||||
* @throws IOException IO异常
|
* @throws IOException IO异常
|
||||||
*/
|
*/
|
||||||
public int responseCode() throws IOException {
|
public int getCode() throws IOException {
|
||||||
if (null != this.conn) {
|
if (null != this.conn) {
|
||||||
return this.conn.getResponseCode();
|
return this.conn.getResponseCode();
|
||||||
}
|
}
|
||||||
@ -521,32 +448,4 @@ public class HttpConnection {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------------- Private Method start
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化http或https请求参数<br>
|
|
||||||
* 有些时候https请求会出现com.sun.net.ssl.internal.www.protocol.https.HttpsURLConnectionOldImpl的实现,此为sun内部api,按照普通http请求处理
|
|
||||||
*
|
|
||||||
* @return {@link HttpURLConnection},https返回{@link HttpsURLConnection}
|
|
||||||
*/
|
|
||||||
private HttpURLConnection openHttp() throws IOException {
|
|
||||||
final URLConnection conn = openConnection();
|
|
||||||
if (false == conn instanceof HttpURLConnection) {
|
|
||||||
// 防止其它协议造成的转换异常
|
|
||||||
throw new HttpException("'{}' of URL [{}] is not a http connection, make sure URL is format for http.", conn.getClass().getName(), this.url);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (HttpURLConnection) conn;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 建立连接
|
|
||||||
*
|
|
||||||
* @return {@link URLConnection}
|
|
||||||
* @throws IOException IO异常
|
|
||||||
*/
|
|
||||||
private URLConnection openConnection() throws IOException {
|
|
||||||
return (null == this.proxy) ? url.openConnection() : url.openConnection(this.proxy);
|
|
||||||
}
|
|
||||||
// --------------------------------------------------------------- Private Method end
|
|
||||||
}
|
}
|
||||||
|
@ -1,56 +0,0 @@
|
|||||||
package cn.hutool.http.client.engine.jdk;
|
|
||||||
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Http拦截器接口,通过实现此接口,完成请求发起前或结束后对请求的编辑工作
|
|
||||||
*
|
|
||||||
* @param <T> 过滤参数类型,HttpRequest或者HttpResponse
|
|
||||||
* @author looly
|
|
||||||
* @since 5.7.16
|
|
||||||
*/
|
|
||||||
@FunctionalInterface
|
|
||||||
public interface HttpInterceptor<T extends HttpBase<T>> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处理请求
|
|
||||||
*
|
|
||||||
* @param httpObj 请求或响应对象
|
|
||||||
*/
|
|
||||||
void process(T httpObj);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 拦截器链
|
|
||||||
*
|
|
||||||
* @param <T> 过滤参数类型,HttpRequest或者HttpResponse
|
|
||||||
* @author looly
|
|
||||||
* @since 5.7.16
|
|
||||||
*/
|
|
||||||
class Chain<T extends HttpBase<T>> implements cn.hutool.core.lang.Chain<HttpInterceptor<T>, Chain<T>> {
|
|
||||||
private final List<HttpInterceptor<T>> interceptors = new LinkedList<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Chain<T> addChain(final HttpInterceptor<T> element) {
|
|
||||||
interceptors.add(element);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Iterator<HttpInterceptor<T>> iterator() {
|
|
||||||
return interceptors.iterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清空
|
|
||||||
*
|
|
||||||
* @return this
|
|
||||||
* @since 5.8.0
|
|
||||||
*/
|
|
||||||
public Chain<T> clear() {
|
|
||||||
interceptors.clear();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,32 +16,30 @@ import cn.hutool.core.net.url.UrlQuery;
|
|||||||
import cn.hutool.core.text.StrUtil;
|
import cn.hutool.core.text.StrUtil;
|
||||||
import cn.hutool.core.util.ArrayUtil;
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
import cn.hutool.core.util.ObjUtil;
|
import cn.hutool.core.util.ObjUtil;
|
||||||
import cn.hutool.http.meta.ContentType;
|
|
||||||
import cn.hutool.http.GlobalHeaders;
|
import cn.hutool.http.GlobalHeaders;
|
||||||
import cn.hutool.http.meta.Header;
|
|
||||||
import cn.hutool.http.HttpConfig;
|
import cn.hutool.http.HttpConfig;
|
||||||
import cn.hutool.http.HttpException;
|
import cn.hutool.http.HttpException;
|
||||||
import cn.hutool.http.HttpGlobalConfig;
|
import cn.hutool.http.HttpGlobalConfig;
|
||||||
import cn.hutool.http.meta.HttpStatus;
|
|
||||||
import cn.hutool.http.HttpUtil;
|
import cn.hutool.http.HttpUtil;
|
||||||
import cn.hutool.http.meta.Method;
|
|
||||||
import cn.hutool.http.client.body.BytesBody;
|
import cn.hutool.http.client.body.BytesBody;
|
||||||
import cn.hutool.http.client.body.UrlEncodedFormBody;
|
|
||||||
import cn.hutool.http.client.body.MultipartBody;
|
import cn.hutool.http.client.body.MultipartBody;
|
||||||
import cn.hutool.http.client.body.RequestBody;
|
import cn.hutool.http.client.body.HttpBody;
|
||||||
|
import cn.hutool.http.client.body.UrlEncodedFormBody;
|
||||||
import cn.hutool.http.client.cookie.GlobalCookieManager;
|
import cn.hutool.http.client.cookie.GlobalCookieManager;
|
||||||
|
import cn.hutool.http.meta.ContentType;
|
||||||
|
import cn.hutool.http.meta.Header;
|
||||||
|
import cn.hutool.http.meta.HttpStatus;
|
||||||
|
import cn.hutool.http.meta.Method;
|
||||||
|
|
||||||
import javax.net.ssl.HostnameVerifier;
|
import javax.net.ssl.HostnameVerifier;
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.CookieManager;
|
import java.net.CookieManager;
|
||||||
import java.net.HttpCookie;
|
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.Proxy;
|
import java.net.Proxy;
|
||||||
import java.net.URLStreamHandler;
|
import java.net.URLStreamHandler;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
@ -352,28 +350,6 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
|||||||
|
|
||||||
// ---------------------------------------------------------------- Http Request Header start
|
// ---------------------------------------------------------------- Http Request Header start
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置contentType
|
|
||||||
*
|
|
||||||
* @param contentType contentType
|
|
||||||
* @return HttpRequest
|
|
||||||
*/
|
|
||||||
public HttpRequest contentType(final String contentType) {
|
|
||||||
header(Header.CONTENT_TYPE, contentType);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置是否为长连接
|
|
||||||
*
|
|
||||||
* @param isKeepAlive 是否长连接
|
|
||||||
* @return HttpRequest
|
|
||||||
*/
|
|
||||||
public HttpRequest keepAlive(final boolean isKeepAlive) {
|
|
||||||
header(Header.CONNECTION, isKeepAlive ? "Keep-Alive" : "Close");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return 获取是否为长连接
|
* @return 获取是否为长连接
|
||||||
*/
|
*/
|
||||||
@ -406,35 +382,6 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置Cookie<br>
|
|
||||||
* 自定义Cookie后会覆盖Hutool的默认Cookie行为
|
|
||||||
*
|
|
||||||
* @param cookies Cookie值数组,如果为{@code null}则设置无效,使用默认Cookie行为
|
|
||||||
* @return this
|
|
||||||
* @since 5.4.1
|
|
||||||
*/
|
|
||||||
public HttpRequest cookie(final Collection<HttpCookie> cookies) {
|
|
||||||
return cookie(CollUtil.isEmpty(cookies) ? null : cookies.toArray(new HttpCookie[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置Cookie<br>
|
|
||||||
* 自定义Cookie后会覆盖Hutool的默认Cookie行为
|
|
||||||
*
|
|
||||||
* @param cookies Cookie值数组,如果为{@code null}则设置无效,使用默认Cookie行为
|
|
||||||
* @return this
|
|
||||||
* @since 3.1.1
|
|
||||||
*/
|
|
||||||
public HttpRequest cookie(final HttpCookie... cookies) {
|
|
||||||
if (ArrayUtil.isEmpty(cookies)) {
|
|
||||||
return disableCookie();
|
|
||||||
}
|
|
||||||
// 名称/值对之间用分号和空格 ('; ')
|
|
||||||
// https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cookie
|
|
||||||
return cookie(ArrayUtil.join(cookies, "; "));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置Cookie<br>
|
* 设置Cookie<br>
|
||||||
* 自定义Cookie后会覆盖Hutool的默认Cookie行为
|
* 自定义Cookie后会覆盖Hutool的默认Cookie行为
|
||||||
@ -447,27 +394,6 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
|||||||
this.cookie = cookie;
|
this.cookie = cookie;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 禁用默认Cookie行为,此方法调用后会将Cookie置为空。<br>
|
|
||||||
* 如果想重新启用Cookie,请调用:{@link #cookie(String)}方法自定义Cookie。<br>
|
|
||||||
* 如果想启动默认的Cookie行为(自动回填服务器传回的Cookie),则调用{@link #enableDefaultCookie()}
|
|
||||||
*
|
|
||||||
* @return this
|
|
||||||
* @since 3.0.7
|
|
||||||
*/
|
|
||||||
public HttpRequest disableCookie() {
|
|
||||||
return cookie(StrUtil.EMPTY);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 打开默认的Cookie行为(自动回填服务器传回的Cookie)
|
|
||||||
*
|
|
||||||
* @return this
|
|
||||||
*/
|
|
||||||
public HttpRequest enableDefaultCookie() {
|
|
||||||
return cookie((String) null);
|
|
||||||
}
|
|
||||||
// ---------------------------------------------------------------- Http Request Header end
|
// ---------------------------------------------------------------- Http Request Header end
|
||||||
|
|
||||||
// ---------------------------------------------------------------- Form start
|
// ---------------------------------------------------------------- Form start
|
||||||
@ -947,42 +873,6 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置拦截器,用于在请求前重新编辑请求
|
|
||||||
*
|
|
||||||
* @param interceptor 拦截器实现
|
|
||||||
* @return this
|
|
||||||
* @see #addRequestInterceptor(HttpInterceptor)
|
|
||||||
* @since 5.7.16
|
|
||||||
*/
|
|
||||||
public HttpRequest addInterceptor(final HttpInterceptor<HttpRequest> interceptor) {
|
|
||||||
return addRequestInterceptor(interceptor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置拦截器,用于在请求前重新编辑请求
|
|
||||||
*
|
|
||||||
* @param interceptor 拦截器实现
|
|
||||||
* @return this
|
|
||||||
* @since 5.8.0
|
|
||||||
*/
|
|
||||||
public HttpRequest addRequestInterceptor(final HttpInterceptor<HttpRequest> interceptor) {
|
|
||||||
config.addRequestInterceptor(interceptor);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置拦截器,用于在请求前重新编辑请求
|
|
||||||
*
|
|
||||||
* @param interceptor 拦截器实现
|
|
||||||
* @return this
|
|
||||||
* @since 5.8.0
|
|
||||||
*/
|
|
||||||
public HttpRequest addResponseInterceptor(final HttpInterceptor<HttpResponse> interceptor) {
|
|
||||||
config.addResponseInterceptor(interceptor);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行Reuqest请求
|
* 执行Reuqest请求
|
||||||
*
|
*
|
||||||
@ -1013,7 +903,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
|||||||
* @return this
|
* @return this
|
||||||
*/
|
*/
|
||||||
public HttpResponse execute(final boolean isAsync) {
|
public HttpResponse execute(final boolean isAsync) {
|
||||||
return doExecute(isAsync, config.requestInterceptors, config.responseInterceptors);
|
return doExecute(isAsync);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1074,41 +964,6 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
|||||||
return proxyAuth(HttpUtil.buildBasicAuth(username, password, charset));
|
return proxyAuth(HttpUtil.buildBasicAuth(username, password, charset));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 令牌验证,生成的头类似于:"Authorization: Bearer XXXXX",一般用于JWT
|
|
||||||
*
|
|
||||||
* @param token 令牌内容
|
|
||||||
* @return HttpRequest
|
|
||||||
* @since 5.5.3
|
|
||||||
*/
|
|
||||||
public HttpRequest bearerAuth(final String token) {
|
|
||||||
return auth("Bearer " + token);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证,简单插入Authorization头
|
|
||||||
*
|
|
||||||
* @param content 验证内容
|
|
||||||
* @return HttpRequest
|
|
||||||
* @since 5.2.4
|
|
||||||
*/
|
|
||||||
public HttpRequest auth(final String content) {
|
|
||||||
header(Header.AUTHORIZATION, content, true);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证,简单插入Authorization头
|
|
||||||
*
|
|
||||||
* @param content 验证内容
|
|
||||||
* @return HttpRequest
|
|
||||||
* @since 5.4.6
|
|
||||||
*/
|
|
||||||
public HttpRequest proxyAuth(final String content) {
|
|
||||||
header(Header.PROXY_AUTHORIZATION, content, true);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
final StringBuilder sb = StrUtil.builder();
|
final StringBuilder sb = StrUtil.builder();
|
||||||
@ -1123,18 +978,9 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
|||||||
* 执行Reuqest请求
|
* 执行Reuqest请求
|
||||||
*
|
*
|
||||||
* @param isAsync 是否异步
|
* @param isAsync 是否异步
|
||||||
* @param requestInterceptors 请求拦截器列表
|
|
||||||
* @param responseInterceptors 响应拦截器列表
|
|
||||||
* @return this
|
* @return this
|
||||||
*/
|
*/
|
||||||
private HttpResponse doExecute(final boolean isAsync, final HttpInterceptor.Chain<HttpRequest> requestInterceptors,
|
private HttpResponse doExecute(final boolean isAsync) {
|
||||||
final HttpInterceptor.Chain<HttpResponse> responseInterceptors) {
|
|
||||||
if (null != requestInterceptors) {
|
|
||||||
for (final HttpInterceptor<HttpRequest> interceptor : requestInterceptors) {
|
|
||||||
interceptor.process(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化URL
|
// 初始化URL
|
||||||
urlWithParamIfGet();
|
urlWithParamIfGet();
|
||||||
// 初始化 connection
|
// 初始化 connection
|
||||||
@ -1147,14 +993,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
|||||||
|
|
||||||
// 获取响应
|
// 获取响应
|
||||||
if (null == httpResponse) {
|
if (null == httpResponse) {
|
||||||
httpResponse = new HttpResponse(this.httpConnection, this.config, this.charset, isAsync, isIgnoreResponseBody());
|
httpResponse = new HttpResponse(this.httpConnection, this.config.isIgnoreEOFError(), this.charset, isAsync, isIgnoreResponseBody());
|
||||||
}
|
|
||||||
|
|
||||||
// 拦截响应
|
|
||||||
if (null != responseInterceptors) {
|
|
||||||
for (final HttpInterceptor<HttpResponse> interceptor : responseInterceptors) {
|
|
||||||
interceptor.process(httpResponse);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return httpResponse;
|
return httpResponse;
|
||||||
@ -1186,16 +1025,14 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
|||||||
|
|
||||||
if (null != this.cookie) {
|
if (null != this.cookie) {
|
||||||
// 当用户自定义Cookie时,全局Cookie自动失效
|
// 当用户自定义Cookie时,全局Cookie自动失效
|
||||||
this.httpConnection.setCookie(this.cookie);
|
this.httpConnection.cookie(this.cookie);
|
||||||
} else {
|
} else {
|
||||||
// 读取全局Cookie信息并附带到请求中
|
// 读取全局Cookie信息并附带到请求中
|
||||||
GlobalCookieManager.add(this.httpConnection);
|
GlobalCookieManager.add(this.httpConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 是否禁用缓存
|
// 是否禁用缓存
|
||||||
if (config.isDisableCache) {
|
this.httpConnection.setDisableCache(config.isDisableCache);
|
||||||
this.httpConnection.disableCache();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1229,17 +1066,17 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
|||||||
private HttpResponse sendRedirectIfPossible(final boolean isAsync) {
|
private HttpResponse sendRedirectIfPossible(final boolean isAsync) {
|
||||||
// 手动实现重定向
|
// 手动实现重定向
|
||||||
if (config.maxRedirectCount > 0) {
|
if (config.maxRedirectCount > 0) {
|
||||||
final int responseCode;
|
final int code;
|
||||||
try {
|
try {
|
||||||
responseCode = httpConnection.responseCode();
|
code = httpConnection.getCode();
|
||||||
} catch (final IOException e) {
|
} catch (final IOException e) {
|
||||||
// 错误时静默关闭连接
|
// 错误时静默关闭连接
|
||||||
this.httpConnection.disconnectQuietly();
|
this.httpConnection.disconnectQuietly();
|
||||||
throw new HttpException(e);
|
throw new HttpException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (responseCode != HttpURLConnection.HTTP_OK) {
|
if (code != HttpURLConnection.HTTP_OK) {
|
||||||
if (HttpStatus.isRedirected(responseCode)) {
|
if (HttpStatus.isRedirected(code)) {
|
||||||
|
|
||||||
final UrlBuilder redirectUrl;
|
final UrlBuilder redirectUrl;
|
||||||
String location = httpConnection.header(Header.LOCATION);
|
String location = httpConnection.header(Header.LOCATION);
|
||||||
@ -1258,9 +1095,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
|||||||
setUrl(redirectUrl);
|
setUrl(redirectUrl);
|
||||||
if (redirectCount < config.maxRedirectCount) {
|
if (redirectCount < config.maxRedirectCount) {
|
||||||
redirectCount++;
|
redirectCount++;
|
||||||
// 重定向不再走过滤器
|
return doExecute(isAsync);
|
||||||
return doExecute(isAsync, config.interceptorOnRedirect ? config.requestInterceptors : null,
|
|
||||||
config.interceptorOnRedirect ? config.responseInterceptors : null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1307,7 +1142,7 @@ public class HttpRequest extends HttpBase<HttpRequest> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write的时候会优先使用body中的内容,write时自动关闭OutputStream
|
// Write的时候会优先使用body中的内容,write时自动关闭OutputStream
|
||||||
final RequestBody body;
|
final HttpBody body;
|
||||||
if (ArrayUtil.isNotEmpty(this.bodyBytes)) {
|
if (ArrayUtil.isNotEmpty(this.bodyBytes)) {
|
||||||
body = BytesBody.of(this.bodyBytes);
|
body = BytesBody.of(this.bodyBytes);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,30 +1,19 @@
|
|||||||
package cn.hutool.http.client.engine.jdk;
|
package cn.hutool.http.client.engine.jdk;
|
||||||
|
|
||||||
import cn.hutool.core.convert.Convert;
|
|
||||||
import cn.hutool.core.io.stream.FastByteArrayOutputStream;
|
|
||||||
import cn.hutool.core.io.FileUtil;
|
|
||||||
import cn.hutool.core.io.IORuntimeException;
|
import cn.hutool.core.io.IORuntimeException;
|
||||||
import cn.hutool.core.io.IoUtil;
|
import cn.hutool.core.io.IoUtil;
|
||||||
import cn.hutool.core.io.StreamProgress;
|
import cn.hutool.core.io.stream.FastByteArrayOutputStream;
|
||||||
import cn.hutool.core.lang.Assert;
|
|
||||||
import cn.hutool.core.net.url.URLEncoder;
|
|
||||||
import cn.hutool.core.regex.ReUtil;
|
|
||||||
import cn.hutool.core.text.StrUtil;
|
import cn.hutool.core.text.StrUtil;
|
||||||
import cn.hutool.core.util.ObjUtil;
|
|
||||||
import cn.hutool.http.meta.Header;
|
|
||||||
import cn.hutool.http.HttpConfig;
|
|
||||||
import cn.hutool.http.HttpException;
|
import cn.hutool.http.HttpException;
|
||||||
import cn.hutool.http.HttpUtil;
|
import cn.hutool.http.client.Response;
|
||||||
|
import cn.hutool.http.client.body.ResponseBody;
|
||||||
import cn.hutool.http.client.cookie.GlobalCookieManager;
|
import cn.hutool.http.client.cookie.GlobalCookieManager;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.EOFException;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.HttpCookie;
|
import java.net.HttpCookie;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -36,12 +25,14 @@ import java.util.Map.Entry;
|
|||||||
*
|
*
|
||||||
* @author Looly
|
* @author Looly
|
||||||
*/
|
*/
|
||||||
public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
|
public class HttpResponse extends HttpBase<HttpResponse> implements Response, Closeable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Http配置
|
* 是否忽略响应读取时可能的EOF异常。<br>
|
||||||
|
* 在Http协议中,对于Transfer-Encoding: Chunked在正常情况下末尾会写入一个Length为0的的chunk标识完整结束。<br>
|
||||||
|
* 如果服务端未遵循这个规范或响应没有正常结束,会报EOF异常,此选项用于是否忽略这个异常。
|
||||||
*/
|
*/
|
||||||
protected HttpConfig config;
|
protected boolean ignoreEOFError;
|
||||||
/**
|
/**
|
||||||
* 持有连接对象
|
* 持有连接对象
|
||||||
*/
|
*/
|
||||||
@ -71,15 +62,14 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
|
|||||||
* 构造
|
* 构造
|
||||||
*
|
*
|
||||||
* @param httpConnection {@link HttpConnection}
|
* @param httpConnection {@link HttpConnection}
|
||||||
* @param config Http配置
|
* @param ignoreEOFError 是否忽略响应读取时可能的EOF异常
|
||||||
* @param charset 编码,从请求编码中获取默认编码
|
* @param charset 编码,从请求编码中获取默认编码
|
||||||
* @param isAsync 是否异步
|
* @param isAsync 是否异步
|
||||||
* @param isIgnoreBody 是否忽略读取响应体
|
* @param isIgnoreBody 是否忽略读取响应体
|
||||||
* @since 3.1.2
|
|
||||||
*/
|
*/
|
||||||
protected HttpResponse(final HttpConnection httpConnection, final HttpConfig config, final Charset charset, final boolean isAsync, final boolean isIgnoreBody) {
|
protected HttpResponse(final HttpConnection httpConnection, final boolean ignoreEOFError, final Charset charset, final boolean isAsync, final boolean isIgnoreBody) {
|
||||||
this.httpConnection = httpConnection;
|
this.httpConnection = httpConnection;
|
||||||
this.config = config;
|
this.ignoreEOFError = ignoreEOFError;
|
||||||
this.charset = charset;
|
this.charset = charset;
|
||||||
this.isAsync = isAsync;
|
this.isAsync = isAsync;
|
||||||
this.ignoreBody = isIgnoreBody;
|
this.ignoreBody = isIgnoreBody;
|
||||||
@ -91,20 +81,11 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
|
|||||||
*
|
*
|
||||||
* @return 状态码
|
* @return 状态码
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public int getStatus() {
|
public int getStatus() {
|
||||||
return this.status;
|
return this.status;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 请求是否成功,判断依据为:状态码范围在200~299内。
|
|
||||||
*
|
|
||||||
* @return 是否成功请求
|
|
||||||
* @since 4.1.9
|
|
||||||
*/
|
|
||||||
public boolean isOk() {
|
|
||||||
return this.status >= 200 && this.status < 300;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 同步<br>
|
* 同步<br>
|
||||||
* 如果为异步状态,则暂时不读取服务器中响应的内容,而是持有Http链接的{@link InputStream}。<br>
|
* 如果为异步状态,则暂时不读取服务器中响应的内容,而是持有Http链接的{@link InputStream}。<br>
|
||||||
@ -118,56 +99,6 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
|
|||||||
|
|
||||||
// ---------------------------------------------------------------- Http Response Header start
|
// ---------------------------------------------------------------- Http Response Header start
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取内容编码
|
|
||||||
*
|
|
||||||
* @return String
|
|
||||||
*/
|
|
||||||
public String contentEncoding() {
|
|
||||||
return header(Header.CONTENT_ENCODING);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取内容长度,以下情况长度无效:
|
|
||||||
* <ul>
|
|
||||||
* <li>Transfer-Encoding: Chunked</li>
|
|
||||||
* <li>Content-Encoding: XXX</li>
|
|
||||||
* </ul>
|
|
||||||
* 参考:<a href="https://blog.csdn.net/jiang7701037/article/details/86304302">https://blog.csdn.net/jiang7701037/article/details/86304302</a>
|
|
||||||
*
|
|
||||||
* @return 长度,-1表示服务端未返回或长度无效
|
|
||||||
* @since 5.7.9
|
|
||||||
*/
|
|
||||||
public long contentLength() {
|
|
||||||
long contentLength = Convert.toLong(header(Header.CONTENT_LENGTH), -1L);
|
|
||||||
if (contentLength > 0 && (isChunked() || StrUtil.isNotBlank(contentEncoding()))) {
|
|
||||||
//按照HTTP协议规范,在 Transfer-Encoding和Content-Encoding设置后 Content-Length 无效。
|
|
||||||
contentLength = -1;
|
|
||||||
}
|
|
||||||
return contentLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否为Transfer-Encoding:Chunked的内容
|
|
||||||
*
|
|
||||||
* @return 是否为Transfer-Encoding:Chunked的内容
|
|
||||||
* @since 4.6.2
|
|
||||||
*/
|
|
||||||
public boolean isChunked() {
|
|
||||||
final String transferEncoding = header(Header.TRANSFER_ENCODING);
|
|
||||||
return "Chunked".equalsIgnoreCase(transferEncoding);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取本次请求服务器返回的Cookie信息
|
|
||||||
*
|
|
||||||
* @return Cookie字符串
|
|
||||||
* @since 3.1.1
|
|
||||||
*/
|
|
||||||
public String getCookieStr() {
|
|
||||||
return header(Header.SET_COOKIE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取Cookie
|
* 获取Cookie
|
||||||
*
|
*
|
||||||
@ -220,6 +151,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
|
|||||||
*
|
*
|
||||||
* @return 响应流
|
* @return 响应流
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public InputStream bodyStream() {
|
public InputStream bodyStream() {
|
||||||
if (isAsync) {
|
if (isAsync) {
|
||||||
return this.in;
|
return this.in;
|
||||||
@ -227,6 +159,11 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
|
|||||||
return new ByteArrayInputStream(this.bodyBytes);
|
return new ByteArrayInputStream(this.bodyBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseBody body() {
|
||||||
|
return new ResponseBody(this, this.ignoreEOFError);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取响应流字节码<br>
|
* 获取响应流字节码<br>
|
||||||
* 此方法会转为同步模式
|
* 此方法会转为同步模式
|
||||||
@ -241,148 +178,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取响应主体
|
* 设置主体字节码,一般用于拦截器修改响应内容<br>
|
||||||
*
|
|
||||||
* @return String
|
|
||||||
* @throws HttpException 包装IO异常
|
|
||||||
*/
|
|
||||||
public String body() throws HttpException {
|
|
||||||
return HttpUtil.getString(bodyBytes(), this.charset, null == this.charsetFromResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将响应内容写出到{@link OutputStream}<br>
|
|
||||||
* 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出<br>
|
|
||||||
* 写出后会关闭Http流(异步模式)
|
|
||||||
*
|
|
||||||
* @param out 写出的流
|
|
||||||
* @param isCloseOut 是否关闭输出流
|
|
||||||
* @param streamProgress 进度显示接口,通过实现此接口显示下载进度
|
|
||||||
* @return 写出bytes数
|
|
||||||
* @since 3.3.2
|
|
||||||
*/
|
|
||||||
public long writeBody(final OutputStream out, final boolean isCloseOut, final StreamProgress streamProgress) {
|
|
||||||
Assert.notNull(out, "[out] must be not null!");
|
|
||||||
final long contentLength = contentLength();
|
|
||||||
try {
|
|
||||||
return copyBody(bodyStream(), out, contentLength, streamProgress, this.config.isIgnoreEOFError());
|
|
||||||
} finally {
|
|
||||||
IoUtil.close(this);
|
|
||||||
if (isCloseOut) {
|
|
||||||
IoUtil.close(out);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将响应内容写出到文件<br>
|
|
||||||
* 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出<br>
|
|
||||||
* 写出后会关闭Http流(异步模式)
|
|
||||||
*
|
|
||||||
* @param targetFileOrDir 写出到的文件或目录
|
|
||||||
* @param streamProgress 进度显示接口,通过实现此接口显示下载进度
|
|
||||||
* @return 写出bytes数
|
|
||||||
* @since 3.3.2
|
|
||||||
*/
|
|
||||||
public long writeBody(final File targetFileOrDir, final StreamProgress streamProgress) {
|
|
||||||
Assert.notNull(targetFileOrDir, "[targetFileOrDir] must be not null!");
|
|
||||||
|
|
||||||
final File outFile = completeFileNameFromHeader(targetFileOrDir, null);
|
|
||||||
return writeBody(FileUtil.getOutputStream(outFile), true, streamProgress);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将响应内容写出到文件-避免未完成的文件<br>
|
|
||||||
* 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出<br>
|
|
||||||
* 写出后会关闭Http流(异步模式)<br>
|
|
||||||
* 来自:<a href="https://gitee.com/dromara/hutool/pulls/407">https://gitee.com/dromara/hutool/pulls/407</a><br>
|
|
||||||
* 此方法原理是先在目标文件同级目录下创建临时文件,下载之,等下载完毕后重命名,避免因下载错误导致的文件不完整。
|
|
||||||
*
|
|
||||||
* @param targetFileOrDir 写出到的文件或目录
|
|
||||||
* @param tempFileSuffix 临时文件后缀,默认".temp"
|
|
||||||
* @param streamProgress 进度显示接口,通过实现此接口显示下载进度
|
|
||||||
* @return 写出bytes数
|
|
||||||
* @since 5.7.12
|
|
||||||
*/
|
|
||||||
public long writeBody(final File targetFileOrDir, String tempFileSuffix, final StreamProgress streamProgress) {
|
|
||||||
Assert.notNull(targetFileOrDir, "[targetFileOrDir] must be not null!");
|
|
||||||
|
|
||||||
File outFile = completeFileNameFromHeader(targetFileOrDir, null);
|
|
||||||
|
|
||||||
if (StrUtil.isBlank(tempFileSuffix)) {
|
|
||||||
tempFileSuffix = ".temp";
|
|
||||||
} else {
|
|
||||||
tempFileSuffix = StrUtil.addPrefixIfNot(tempFileSuffix, StrUtil.DOT);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 目标文件真实名称
|
|
||||||
final String fileName = outFile.getName();
|
|
||||||
// 临时文件名称
|
|
||||||
final String tempFileName = fileName + tempFileSuffix;
|
|
||||||
|
|
||||||
// 临时文件
|
|
||||||
outFile = new File(outFile.getParentFile(), tempFileName);
|
|
||||||
|
|
||||||
final long length;
|
|
||||||
try {
|
|
||||||
length = writeBody(outFile, streamProgress);
|
|
||||||
// 重命名下载好的临时文件
|
|
||||||
FileUtil.rename(outFile, fileName, true);
|
|
||||||
} catch (final Throwable e) {
|
|
||||||
// 异常则删除临时文件
|
|
||||||
FileUtil.del(outFile);
|
|
||||||
throw new HttpException(e);
|
|
||||||
}
|
|
||||||
return length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将响应内容写出到文件<br>
|
|
||||||
* 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出<br>
|
|
||||||
* 写出后会关闭Http流(异步模式)
|
|
||||||
*
|
|
||||||
* @param targetFileOrDir 写出到的文件
|
|
||||||
* @param streamProgress 进度显示接口,通过实现此接口显示下载进度
|
|
||||||
* @return 写出的文件
|
|
||||||
* @since 5.6.4
|
|
||||||
*/
|
|
||||||
public File writeBodyForFile(final File targetFileOrDir, final StreamProgress streamProgress) {
|
|
||||||
Assert.notNull(targetFileOrDir, "[targetFileOrDir] must be not null!");
|
|
||||||
|
|
||||||
final File outFile = completeFileNameFromHeader(targetFileOrDir, null);
|
|
||||||
writeBody(FileUtil.getOutputStream(outFile), true, streamProgress);
|
|
||||||
|
|
||||||
return outFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将响应内容写出到文件<br>
|
|
||||||
* 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出<br>
|
|
||||||
* 写出后会关闭Http流(异步模式)
|
|
||||||
*
|
|
||||||
* @param targetFileOrDir 写出到的文件或目录
|
|
||||||
* @return 写出bytes数
|
|
||||||
* @since 3.3.2
|
|
||||||
*/
|
|
||||||
public long writeBody(final File targetFileOrDir) {
|
|
||||||
return writeBody(targetFileOrDir, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将响应内容写出到文件<br>
|
|
||||||
* 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出<br>
|
|
||||||
* 写出后会关闭Http流(异步模式)
|
|
||||||
*
|
|
||||||
* @param targetFileOrDir 写出到的文件或目录的路径
|
|
||||||
* @return 写出bytes数
|
|
||||||
* @since 3.3.2
|
|
||||||
*/
|
|
||||||
public long writeBody(final String targetFileOrDir) {
|
|
||||||
return writeBody(FileUtil.file(targetFileOrDir));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置主体字节码,一版用于拦截器修改响应内容<br>
|
|
||||||
* 需在此方法调用前使用charset方法设置编码,否则使用默认编码UTF-8
|
* 需在此方法调用前使用charset方法设置编码,否则使用默认编码UTF-8
|
||||||
*
|
*
|
||||||
* @param bodyBytes 主体
|
* @param bodyBytes 主体
|
||||||
@ -420,54 +216,9 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* 从响应头补全下载文件名
|
public String header(final String name) {
|
||||||
*
|
return super.header(name);
|
||||||
* @param targetFileOrDir 目标文件夹或者目标文件
|
|
||||||
* @param customParamName 自定义的参数名称,如果传入{@code null},默认使用"filename"
|
|
||||||
* @return File 保存的文件
|
|
||||||
* @since 5.4.1
|
|
||||||
*/
|
|
||||||
public File completeFileNameFromHeader(final File targetFileOrDir, final String customParamName) {
|
|
||||||
if (false == targetFileOrDir.isDirectory()) {
|
|
||||||
// 非目录直接返回
|
|
||||||
return targetFileOrDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从头信息中获取文件名
|
|
||||||
String fileName = getFileNameFromDisposition(ObjUtil.defaultIfNull(customParamName, "filename"));
|
|
||||||
if (StrUtil.isBlank(fileName)) {
|
|
||||||
final String path = httpConnection.getUrl().getPath();
|
|
||||||
// 从路径中获取文件名
|
|
||||||
fileName = StrUtil.subSuf(path, path.lastIndexOf('/') + 1);
|
|
||||||
if (StrUtil.isBlank(fileName)) {
|
|
||||||
// 编码后的路径做为文件名
|
|
||||||
fileName = URLEncoder.encodeQuery(path, charset);
|
|
||||||
} else {
|
|
||||||
// issue#I4K0FS@Gitee
|
|
||||||
fileName = URLEncoder.encodeQuery(fileName, charset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return FileUtil.file(targetFileOrDir, fileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从Content-Disposition头中获取文件名
|
|
||||||
*
|
|
||||||
* @param paramName 文件名的参数名
|
|
||||||
* @return 文件名,empty表示无
|
|
||||||
* @since 5.8.10
|
|
||||||
*/
|
|
||||||
public String getFileNameFromDisposition(final String paramName) {
|
|
||||||
String fileName = null;
|
|
||||||
final String disposition = header(Header.CONTENT_DISPOSITION);
|
|
||||||
if (StrUtil.isNotBlank(disposition)) {
|
|
||||||
fileName = ReUtil.get(paramName + "=\"(.*?)\"", disposition, 1);
|
|
||||||
if (StrUtil.isBlank(fileName)) {
|
|
||||||
fileName = StrUtil.subAfter(disposition, paramName + "=", true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fileName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------- Private method start
|
// ---------------------------------------------------------------- Private method start
|
||||||
@ -509,7 +260,7 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
|
|||||||
private void init() throws HttpException {
|
private void init() throws HttpException {
|
||||||
// 获取响应状态码
|
// 获取响应状态码
|
||||||
try {
|
try {
|
||||||
this.status = httpConnection.responseCode();
|
this.status = httpConnection.getCode();
|
||||||
} catch (final IOException e) {
|
} catch (final IOException e) {
|
||||||
if (false == (e instanceof FileNotFoundException)) {
|
if (false == (e instanceof FileNotFoundException)) {
|
||||||
throw new HttpException(e);
|
throw new HttpException(e);
|
||||||
@ -529,11 +280,10 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
|
|||||||
// 存储服务端设置的Cookie信息
|
// 存储服务端设置的Cookie信息
|
||||||
GlobalCookieManager.store(httpConnection, this.headers);
|
GlobalCookieManager.store(httpConnection, this.headers);
|
||||||
|
|
||||||
// 获取响应编码
|
// 获取响应编码,如果非空,替换用户定义的编码
|
||||||
final Charset charset = httpConnection.getCharset();
|
final Charset charsetFromResponse = httpConnection.getCharset();
|
||||||
this.charsetFromResponse = charset;
|
if (null != charsetFromResponse) {
|
||||||
if (null != charset) {
|
this.charset = charsetFromResponse;
|
||||||
this.charset = charset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取响应内容流
|
// 获取响应内容流
|
||||||
@ -591,40 +341,8 @@ public class HttpResponse extends HttpBase<HttpResponse> implements Closeable {
|
|||||||
|
|
||||||
final long contentLength = contentLength();
|
final long contentLength = contentLength();
|
||||||
final FastByteArrayOutputStream out = new FastByteArrayOutputStream((int) contentLength);
|
final FastByteArrayOutputStream out = new FastByteArrayOutputStream((int) contentLength);
|
||||||
copyBody(in, out, contentLength, null, this.config.isIgnoreEOFError());
|
body().writeClose(out);
|
||||||
this.bodyBytes = out.toByteArray();
|
this.bodyBytes = out.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 将响应内容写出到{@link OutputStream}<br>
|
|
||||||
* 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出<br>
|
|
||||||
* 写出后会关闭Http流(异步模式)
|
|
||||||
*
|
|
||||||
* @param in 输入流
|
|
||||||
* @param out 写出的流
|
|
||||||
* @param contentLength 总长度,-1表示未知
|
|
||||||
* @param streamProgress 进度显示接口,通过实现此接口显示下载进度
|
|
||||||
* @param isIgnoreEOFError 是否忽略响应读取时可能的EOF异常
|
|
||||||
* @return 拷贝长度
|
|
||||||
*/
|
|
||||||
private static long copyBody(final InputStream in, final OutputStream out, final long contentLength, final StreamProgress streamProgress, final boolean isIgnoreEOFError) {
|
|
||||||
if (null == out) {
|
|
||||||
throw new NullPointerException("[out] is null!");
|
|
||||||
}
|
|
||||||
|
|
||||||
long copyLength = -1;
|
|
||||||
try {
|
|
||||||
copyLength = IoUtil.copy(in, out, IoUtil.DEFAULT_BUFFER_SIZE, contentLength, streamProgress);
|
|
||||||
} catch (final IORuntimeException e) {
|
|
||||||
//noinspection StatementWithEmptyBody
|
|
||||||
if (isIgnoreEOFError
|
|
||||||
&& (e.getCause() instanceof EOFException || StrUtil.containsIgnoreCase(e.getMessage(), "Premature EOF"))) {
|
|
||||||
// 忽略读取HTTP流中的EOF错误
|
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return copyLength;
|
|
||||||
}
|
|
||||||
// ---------------------------------------------------------------- Private method end
|
// ---------------------------------------------------------------- Private method end
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,19 @@
|
|||||||
package cn.hutool.http.client.engine.jdk;
|
package cn.hutool.http.client.engine.jdk;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.IORuntimeException;
|
||||||
import cn.hutool.core.reflect.FieldUtil;
|
import cn.hutool.core.reflect.FieldUtil;
|
||||||
import cn.hutool.core.reflect.ModifierUtil;
|
import cn.hutool.core.reflect.ModifierUtil;
|
||||||
import cn.hutool.core.util.ArrayUtil;
|
import cn.hutool.core.util.ArrayUtil;
|
||||||
import cn.hutool.core.util.SystemUtil;
|
import cn.hutool.core.util.SystemUtil;
|
||||||
import cn.hutool.http.HttpException;
|
import cn.hutool.http.HttpException;
|
||||||
|
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.Proxy;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
import java.security.AccessController;
|
import java.security.AccessController;
|
||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
|
|
||||||
@ -55,4 +61,38 @@ public class HttpUrlConnectionUtil {
|
|||||||
FieldUtil.setStaticFieldValue(methodsField, METHODS);
|
FieldUtil.setStaticFieldValue(methodsField, METHODS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化http或https请求参数<br>
|
||||||
|
* 有些时候https请求会出现com.sun.net.ssl.internal.www.protocol.https.HttpsURLConnectionOldImpl的实现,此为sun内部api,按照普通http请求处理
|
||||||
|
*
|
||||||
|
* @param url 请求的URL,必须为http
|
||||||
|
* @param proxy 代理,无代理传{@code null}
|
||||||
|
* @return {@link HttpURLConnection},https返回{@link HttpsURLConnection}
|
||||||
|
* @throws IORuntimeException IO异常
|
||||||
|
*/
|
||||||
|
public static HttpURLConnection openHttp(final URL url, final Proxy proxy) throws IORuntimeException {
|
||||||
|
final URLConnection conn = openConnection(url, proxy);
|
||||||
|
if (false == conn instanceof HttpURLConnection) {
|
||||||
|
// 防止其它协议造成的转换异常
|
||||||
|
throw new HttpException("'{}' of URL [{}] is not a http connection, make sure URL is format for http.",
|
||||||
|
conn.getClass().getName(), url);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (HttpURLConnection) conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 建立连接
|
||||||
|
*
|
||||||
|
* @return {@link URLConnection}
|
||||||
|
* @throws IORuntimeException IO异常
|
||||||
|
*/
|
||||||
|
private static URLConnection openConnection(final URL url, final Proxy proxy) throws IORuntimeException {
|
||||||
|
try {
|
||||||
|
return (null == proxy) ? url.openConnection() : url.openConnection(proxy);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
throw new IORuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
208
hutool-http/src/main/java/cn/hutool/http/client/engine/jdk/JdkClientEngine.java
Executable file
208
hutool-http/src/main/java/cn/hutool/http/client/engine/jdk/JdkClientEngine.java
Executable file
@ -0,0 +1,208 @@
|
|||||||
|
package cn.hutool.http.client.engine.jdk;
|
||||||
|
|
||||||
|
import cn.hutool.core.io.IoUtil;
|
||||||
|
import cn.hutool.core.net.url.UrlBuilder;
|
||||||
|
import cn.hutool.core.text.StrUtil;
|
||||||
|
import cn.hutool.http.HttpException;
|
||||||
|
import cn.hutool.http.HttpUtil;
|
||||||
|
import cn.hutool.http.client.ClientConfig;
|
||||||
|
import cn.hutool.http.client.ClientEngine;
|
||||||
|
import cn.hutool.http.client.Request;
|
||||||
|
import cn.hutool.http.client.Response;
|
||||||
|
import cn.hutool.http.client.body.HttpBody;
|
||||||
|
import cn.hutool.http.client.cookie.GlobalCookieManager;
|
||||||
|
import cn.hutool.http.meta.Header;
|
||||||
|
import cn.hutool.http.meta.HttpStatus;
|
||||||
|
import cn.hutool.http.meta.Method;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于JDK的UrlConnection的Http客户端引擎实现
|
||||||
|
*
|
||||||
|
* @author looly
|
||||||
|
*/
|
||||||
|
public class JdkClientEngine implements ClientEngine {
|
||||||
|
|
||||||
|
private final ClientConfig config;
|
||||||
|
private HttpConnection conn;
|
||||||
|
/**
|
||||||
|
* 重定向次数计数器,内部使用
|
||||||
|
*/
|
||||||
|
private int redirectCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造
|
||||||
|
*/
|
||||||
|
public JdkClientEngine() {
|
||||||
|
this.config = ClientConfig.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response send(final Request message) {
|
||||||
|
return send(message, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送请求
|
||||||
|
*
|
||||||
|
* @param message 请求消息
|
||||||
|
* @param isAsync 是否异步,异步不会立即读取响应内容
|
||||||
|
* @return {@link Response}
|
||||||
|
*/
|
||||||
|
public HttpResponse send(final Request message, final boolean isAsync) {
|
||||||
|
initConn(message);
|
||||||
|
try {
|
||||||
|
doSend(message);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
// 出错后关闭连接
|
||||||
|
IoUtil.close(this);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sendRedirectIfPossible(message, isAsync);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getRawEngine() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
if (null != conn) {
|
||||||
|
conn.disconnectQuietly();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行发送
|
||||||
|
*
|
||||||
|
* @param message 请求消息
|
||||||
|
* @throws IOException IO异常
|
||||||
|
*/
|
||||||
|
private void doSend(final Request message) throws IOException {
|
||||||
|
final HttpBody body = message.body();
|
||||||
|
if (null != body) {
|
||||||
|
// 带有消息体,一律按照Rest方式发送
|
||||||
|
body.writeClose(this.conn.getOutputStream());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 非Rest简单GET请求
|
||||||
|
this.conn.connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化连接对象
|
||||||
|
*
|
||||||
|
* @param message 请求消息
|
||||||
|
*/
|
||||||
|
private void initConn(final Request message) {
|
||||||
|
// 执行下次请求时自动关闭上次请求(常用于转发)
|
||||||
|
IoUtil.close(this);
|
||||||
|
|
||||||
|
this.conn = buildConn(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建{@link HttpConnection}
|
||||||
|
*
|
||||||
|
* @param message {@link Request}消息
|
||||||
|
* @return {@link HttpConnection}
|
||||||
|
*/
|
||||||
|
private HttpConnection buildConn(final Request message) {
|
||||||
|
final HttpConnection conn = HttpConnection
|
||||||
|
.of(message.url().toURL(), config.proxy)
|
||||||
|
.setConnectTimeout(config.getConnectionTimeout())
|
||||||
|
.setReadTimeout(config.getReadTimeout())
|
||||||
|
.setMethod(message.method())//
|
||||||
|
.setHttpsInfo(config.getHostnameVerifier(), config.getSocketFactory())
|
||||||
|
// 关闭JDK自动转发,采用手动转发方式
|
||||||
|
.setInstanceFollowRedirects(false)
|
||||||
|
.setChunkedStreamingMode(message.isChunked() ? 4096 : -1)
|
||||||
|
.setDisableCache(config.isDisableCache())
|
||||||
|
// 覆盖默认Header
|
||||||
|
.header(message.headers(), true);
|
||||||
|
|
||||||
|
if (null == message.header(Header.COOKIE)) {
|
||||||
|
// 用户没有自定义Cookie,则读取全局Cookie信息并附带到请求中
|
||||||
|
GlobalCookieManager.add(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用转发,如果需要转发返回转发结果,否则返回{@code null}
|
||||||
|
*
|
||||||
|
* @param isAsync 最终请求是否异步
|
||||||
|
* @return {@link HttpResponse},无转发返回 {@code null}
|
||||||
|
*/
|
||||||
|
private HttpResponse sendRedirectIfPossible(final Request message, final boolean isAsync) {
|
||||||
|
final HttpConnection conn = this.conn;
|
||||||
|
// 手动实现重定向
|
||||||
|
if (message.maxRedirectCount() > 0) {
|
||||||
|
final int code;
|
||||||
|
try {
|
||||||
|
code = conn.getCode();
|
||||||
|
} catch (final IOException e) {
|
||||||
|
// 错误时静默关闭连接
|
||||||
|
conn.disconnectQuietly();
|
||||||
|
throw new HttpException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code != HttpURLConnection.HTTP_OK) {
|
||||||
|
if (HttpStatus.isRedirected(code)) {
|
||||||
|
message.url(getLocationUrl(message.url(), conn.header(Header.LOCATION)));
|
||||||
|
if (redirectCount < message.maxRedirectCount()) {
|
||||||
|
redirectCount++;
|
||||||
|
return send(message, isAsync);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最终页面
|
||||||
|
return new HttpResponse(this.conn, true, message.charset(), isAsync,
|
||||||
|
isIgnoreResponseBody(message.method()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取转发的新的URL
|
||||||
|
*
|
||||||
|
* @param parentUrl 上级请求的URL
|
||||||
|
* @param location 获取的Location
|
||||||
|
* @return 新的URL
|
||||||
|
*/
|
||||||
|
private static UrlBuilder getLocationUrl(final UrlBuilder parentUrl, String location) {
|
||||||
|
final UrlBuilder redirectUrl;
|
||||||
|
if (false == HttpUtil.isHttp(location) && false == HttpUtil.isHttps(location)) {
|
||||||
|
// issue#I5TPSY
|
||||||
|
// location可能为相对路径
|
||||||
|
if (false == location.startsWith("/")) {
|
||||||
|
location = StrUtil.addSuffixIfNot(parentUrl.getPathStr(), "/") + location;
|
||||||
|
}
|
||||||
|
redirectUrl = UrlBuilder.of(parentUrl.getScheme(), parentUrl.getHost(), parentUrl.getPort(),
|
||||||
|
location, null, null, parentUrl.getCharset());
|
||||||
|
} else {
|
||||||
|
redirectUrl = UrlBuilder.ofHttpWithoutEncode(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirectUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否忽略读取响应body部分<br>
|
||||||
|
* HEAD、CONNECT、OPTIONS、TRACE方法将不读取响应体
|
||||||
|
*
|
||||||
|
* @return 是否需要忽略响应body部分
|
||||||
|
*/
|
||||||
|
private static boolean isIgnoreResponseBody(final Method method) {
|
||||||
|
return Method.HEAD == method //
|
||||||
|
|| Method.CONNECT == method //
|
||||||
|
|| Method.OPTIONS == method //
|
||||||
|
|| Method.TRACE == method;
|
||||||
|
}
|
||||||
|
}
|
@ -1,24 +1,24 @@
|
|||||||
package cn.hutool.http.client.engine.okhttp;
|
package cn.hutool.http.client.engine.okhttp;
|
||||||
|
|
||||||
import cn.hutool.http.client.body.RequestBody;
|
import cn.hutool.http.client.body.HttpBody;
|
||||||
import okhttp3.MediaType;
|
import okhttp3.MediaType;
|
||||||
import okio.BufferedSink;
|
import okio.BufferedSink;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OkHttp的请求体实现,通过{@link RequestBody}转换实现
|
* OkHttp的请求体实现,通过{@link HttpBody}转换实现
|
||||||
*
|
*
|
||||||
* @author looly
|
* @author looly
|
||||||
*/
|
*/
|
||||||
public class OkHttpRequestBody extends okhttp3.RequestBody {
|
public class OkHttpRequestBody extends okhttp3.RequestBody {
|
||||||
|
|
||||||
private final RequestBody body;
|
private final HttpBody body;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造
|
* 构造
|
||||||
*
|
*
|
||||||
* @param body 请求体{@link RequestBody}
|
* @param body 请求体{@link HttpBody}
|
||||||
*/
|
*/
|
||||||
public OkHttpRequestBody(final RequestBody body) {
|
public OkHttpRequestBody(final HttpBody body) {
|
||||||
this.body = body;
|
this.body = body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,22 +184,6 @@ public class HttpRequestTest {
|
|||||||
Console.log(execute.getStatus(), execute.header(Header.LOCATION));
|
Console.log(execute.getStatus(), execute.header(Header.LOCATION));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
@Ignore
|
|
||||||
public void addInterceptorTest() {
|
|
||||||
HttpUtil.createGet("https://hutool.cn")
|
|
||||||
.addInterceptor(Console::log)
|
|
||||||
.addResponseInterceptor((res)-> Console.log(res.getStatus()))
|
|
||||||
.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Ignore
|
|
||||||
public void addGlobalInterceptorTest() {
|
|
||||||
GlobalInterceptor.INSTANCE.addRequestInterceptor(Console::log);
|
|
||||||
HttpUtil.createGet("https://hutool.cn").execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore
|
@Ignore
|
||||||
public void getWithFormTest(){
|
public void getWithFormTest(){
|
||||||
|
Loading…
x
Reference in New Issue
Block a user