diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a2656941..95daf4a7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### ❌不兼容特性 * 【extra 】 【可能兼容问题】BeanCopierCache的key结构变更 +* 【http 】 【可能兼容问题】HttpInterceptor增加泛型标识,HttpRequest中配置汇总于HttpConfig ### 🐣新特性 * 【core 】 MapUtil增加entry、ofEntries方法 @@ -13,6 +14,7 @@ * 【core 】 IterUtil增加filtered,增加FilterIter(issue#2228) * 【core 】 增加NodeListIter、ResettableIter * 【crypto 】 HmacAlgorithm增加SM4CMAC(issue#2206@Github) +* 【http 】 增加HttpConfig,响应支持拦截(issue#2217@Github) ### 🐞Bug修复 * 【core 】 IdcardUtil#getCityCodeByIdCard位数问题(issue#2224@Github) diff --git a/hutool-http/src/main/java/cn/hutool/http/GlobalInterceptor.java b/hutool-http/src/main/java/cn/hutool/http/GlobalInterceptor.java index 6fc518efe..98def3fc2 100644 --- a/hutool-http/src/main/java/cn/hutool/http/GlobalInterceptor.java +++ b/hutool-http/src/main/java/cn/hutool/http/GlobalInterceptor.java @@ -1,7 +1,8 @@ package cn.hutool.http; /** - * 全局的拦截器 + * 全局的拦截器
+ * 包括请求拦截器和响应拦截器 * * @author looly * @since 5.8.0 @@ -9,34 +10,81 @@ package cn.hutool.http; public enum GlobalInterceptor { INSTANCE; - private final HttpInterceptor.Chain interceptors = new HttpInterceptor.Chain(); + private final HttpInterceptor.Chain requestInterceptors = new HttpInterceptor.Chain<>(); + private final HttpInterceptor.Chain responseInterceptors = new HttpInterceptor.Chain<>(); /** * 设置拦截器,用于在请求前重新编辑请求 * * @param interceptor 拦截器实现 */ - synchronized public GlobalInterceptor addInterceptor(HttpInterceptor interceptor) { - this.interceptors.addChain(interceptor); + synchronized public GlobalInterceptor addRequestInterceptor(HttpInterceptor interceptor) { + this.requestInterceptors.addChain(interceptor); return this; } /** - * 清空 + * 设置拦截器,用于在响应读取后完成编辑或读取 + * + * @param interceptor 拦截器实现 + */ + synchronized public GlobalInterceptor addResponseInterceptor(HttpInterceptor interceptor) { + this.responseInterceptors.addChain(interceptor); + return this; + } + + /** + * 清空请求和响应拦截器 + * * @return this */ - synchronized public GlobalInterceptor clear(){ - interceptors.clear(); + 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 cn.hutool.http.HttpInterceptor.Chain} */ - HttpInterceptor.Chain getCopied(){ - final HttpInterceptor.Chain copied = new HttpInterceptor.Chain(); - for (HttpInterceptor interceptor : this.interceptors) { + HttpInterceptor.Chain getCopiedRequestInterceptor() { + final HttpInterceptor.Chain copied = new HttpInterceptor.Chain<>(); + for (HttpInterceptor interceptor : this.requestInterceptors) { + copied.addChain(interceptor); + } + return copied; + } + + /** + * 复制响应过滤器列表 + * + * @return {@link cn.hutool.http.HttpInterceptor.Chain} + */ + HttpInterceptor.Chain getCopiedResponseInterceptor() { + final HttpInterceptor.Chain copied = new HttpInterceptor.Chain<>(); + for (HttpInterceptor interceptor : this.responseInterceptors) { copied.addChain(interceptor); } return copied; diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpConfig.java b/hutool-http/src/main/java/cn/hutool/http/HttpConfig.java new file mode 100755 index 000000000..0b1a05bea --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/http/HttpConfig.java @@ -0,0 +1,296 @@ +package cn.hutool.http; + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.net.SSLUtil; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSocketFactory; +import java.net.InetSocketAddress; +import java.net.Proxy; + +/** + * Http配置项 + * + * @author looly + * @since 5.8.0 + */ +public class HttpConfig { + + /** + * 创建默认Http配置信息 + * + * @return HttpConfig + */ + public static HttpConfig create() { + return new HttpConfig(); + } + + /** + * 默认连接超时 + */ + int connectionTimeout = HttpGlobalConfig.getTimeout(); + /** + * 默认读取超时 + */ + int readTimeout = HttpGlobalConfig.getTimeout(); + + /** + * 是否禁用缓存 + */ + boolean isDisableCache; + + /** + * 最大重定向次数 + */ + int maxRedirectCount = HttpGlobalConfig.getMaxRedirectCount(); + + /** + * 代理 + */ + Proxy proxy; + + /** + * HostnameVerifier,用于HTTPS安全连接 + */ + HostnameVerifier hostnameVerifier; + /** + * SSLSocketFactory,用于HTTPS安全连接 + */ + SSLSocketFactory ssf; + + /** + * Chuncked块大小,0或小于0表示不设置Chuncked模式 + */ + int blockSize; + + /** + * 获取是否忽略响应读取时可能的EOF异常。
+ * 在Http协议中,对于Transfer-Encoding: Chunked在正常情况下末尾会写入一个Length为0的的chunk标识完整结束。
+ * 如果服务端未遵循这个规范或响应没有正常结束,会报EOF异常,此选项用于是否忽略这个异常。 + */ + boolean ignoreEOFError = HttpGlobalConfig.isIgnoreEOFError(); + /** + * 获取是否忽略解码URL,包括URL中的Path部分和Param部分。
+ * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数,
+ * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + */ + boolean decodeUrl = HttpGlobalConfig.isDecodeUrl(); + + /** + * 请求前的拦截器,用于在请求前重新编辑请求 + */ + final HttpInterceptor.Chain requestInterceptors = GlobalInterceptor.INSTANCE.getCopiedRequestInterceptor(); + /** + * 响应后的拦截器,用于在响应后处理逻辑 + */ + final HttpInterceptor.Chain responseInterceptors = GlobalInterceptor.INSTANCE.getCopiedResponseInterceptor(); + + /** + * 重定向时是否使用拦截器 + */ + boolean interceptorOnRedirect; + + /** + * 设置超时,单位:毫秒
+ * 超时包括: + * + *
+	 * 1. 连接超时
+	 * 2. 读取响应超时
+	 * 
+ * + * @param milliseconds 超时毫秒数 + * @return this + * @see #setConnectionTimeout(int) + * @see #setReadTimeout(int) + */ + public HttpConfig timeout(int milliseconds) { + setConnectionTimeout(milliseconds); + setReadTimeout(milliseconds); + return this; + } + + /** + * 设置连接超时,单位:毫秒 + * + * @param milliseconds 超时毫秒数 + * @return this + */ + public HttpConfig setConnectionTimeout(int milliseconds) { + this.connectionTimeout = milliseconds; + return this; + } + + /** + * 设置连接超时,单位:毫秒 + * + * @param milliseconds 超时毫秒数 + * @return this + */ + public HttpConfig setReadTimeout(int milliseconds) { + this.readTimeout = milliseconds; + return this; + } + + /** + * 禁用缓存 + * + * @return this + */ + public HttpConfig disableCache() { + this.isDisableCache = true; + return this; + } + + /** + * 设置最大重定向次数
+ * 如果次数小于1则表示不重定向,大于等于1表示打开重定向 + * + * @param maxRedirectCount 最大重定向次数 + * @return this + */ + public HttpConfig setMaxRedirectCount(int maxRedirectCount) { + this.maxRedirectCount = Math.max(maxRedirectCount, 0); + return this; + } + + /** + * 设置域名验证器
+ * 只针对HTTPS请求,如果不设置,不做验证,所有域名被信任 + * + * @param hostnameVerifier HostnameVerifier + * @return this + */ + public HttpConfig setHostnameVerifier(HostnameVerifier hostnameVerifier) { + // 验证域 + this.hostnameVerifier = hostnameVerifier; + return this; + } + + /** + * 设置Http代理 + * + * @param host 代理 主机 + * @param port 代理 端口 + * @return this + */ + public HttpConfig setHttpProxy(String host, int port) { + final Proxy proxy = new Proxy(Proxy.Type.HTTP, + new InetSocketAddress(host, port)); + return setProxy(proxy); + } + + /** + * 设置代理 + * + * @param proxy 代理 {@link Proxy} + * @return this + */ + public HttpConfig setProxy(Proxy proxy) { + this.proxy = proxy; + return this; + } + + /** + * 设置SSLSocketFactory
+ * 只针对HTTPS请求,如果不设置,使用默认的SSLSocketFactory
+ * 默认SSLSocketFactory为:SSLSocketFactoryBuilder.create().build(); + * + * @param ssf SSLScketFactory + * @return this + */ + public HttpConfig setSSLSocketFactory(SSLSocketFactory ssf) { + this.ssf = ssf; + return this; + } + + /** + * 设置HTTPS安全连接协议,只针对HTTPS请求,可以使用的协议包括:
+ * 此方法调用后{@link #setSSLSocketFactory(SSLSocketFactory)} 将被覆盖。 + * + *
+	 * 1. TLSv1.2
+	 * 2. TLSv1.1
+	 * 3. SSLv3
+	 * ...
+	 * 
+ * + * @param protocol 协议 + * @return this + * @see SSLUtil#createSSLContext(String) + * @see #setSSLSocketFactory(SSLSocketFactory) + */ + public HttpConfig setSSLProtocol(String protocol) { + Assert.notBlank(protocol, "protocol must be not blank!"); + setSSLSocketFactory(SSLUtil.createSSLContext(protocol).getSocketFactory()); + return this; + } + + /** + * 采用流方式上传数据,无需本地缓存数据。
+ * HttpUrlConnection默认是将所有数据读到本地缓存,然后再发送给服务器,这样上传大文件时就会导致内存溢出。 + * + * @param blockSize 块大小(bytes数),0或小于0表示不设置Chuncked模式 + * @return this + */ + public HttpConfig setBlockSize(int blockSize) { + this.blockSize = blockSize; + return this; + } + + /** + * 设置是否忽略响应读取时可能的EOF异常。
+ * 在Http协议中,对于Transfer-Encoding: Chunked在正常情况下末尾会写入一个Length为0的的chunk标识完整结束。
+ * 如果服务端未遵循这个规范或响应没有正常结束,会报EOF异常,此选项用于是否忽略这个异常。 + * + * @param ignoreEOFError 是否忽略响应读取时可能的EOF异常。 + * @since 5.7.20 + */ + public HttpConfig setIgnoreEOFError(boolean ignoreEOFError) { + this.ignoreEOFError = ignoreEOFError; + return this; + } + + /** + * 设置是否忽略解码URL,包括URL中的Path部分和Param部分。
+ * 在构建Http请求时,用户传入的URL可能有编码后和未编码的内容混合在一起,如果此参数为{@code true},则会统一解码编码后的参数,
+ * 按照RFC3986规范,在发送请求时,全部编码之。如果为{@code false},则不会解码已经编码的内容,在请求时只编码需要编码的部分。 + * + * @param decodeUrl 是否忽略解码URL + */ + public HttpConfig setDecodeUrl(boolean decodeUrl) { + this.decodeUrl = decodeUrl; + return this; + } + + /** + * 设置拦截器,用于在请求前重新编辑请求 + * + * @param interceptor 拦截器实现 + */ + public HttpConfig addRequestInterceptor(HttpInterceptor interceptor) { + this.requestInterceptors.addChain(interceptor); + return this; + } + + /** + * 设置拦截器,用于在请求前重新编辑请求 + * + * @param interceptor 拦截器实现 + */ + public HttpConfig addResponseInterceptor(HttpInterceptor interceptor) { + this.responseInterceptors.addChain(interceptor); + return this; + } + + /** + * 重定向时是否使用拦截器 + * + * @param interceptorOnRedirect 重定向时是否使用拦截器 + * @return this + */ + public HttpConfig setInterceptorOnRedirect(boolean interceptorOnRedirect) { + this.interceptorOnRedirect = interceptorOnRedirect; + return this; + } +} diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpInterceptor.java b/hutool-http/src/main/java/cn/hutool/http/HttpInterceptor.java index 6e754f9cf..246d7f37d 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpInterceptor.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpInterceptor.java @@ -5,38 +5,40 @@ import java.util.LinkedList; import java.util.List; /** - * Http拦截器接口,通过实现此接口,完成请求发起前对请求的编辑工作 + * Http拦截器接口,通过实现此接口,完成请求发起前或结束后对请求的编辑工作 * + * @param 过滤参数类型,HttpRequest或者HttpResponse * @author looly * @since 5.7.16 */ @FunctionalInterface -public interface HttpInterceptor { +public interface HttpInterceptor> { /** * 处理请求 * - * @param request 请求 + * @param httpObj 请求或响应对象 */ - void process(HttpRequest request); + void process(T httpObj); /** * 拦截器链 * + * @param 过滤参数类型,HttpRequest或者HttpResponse * @author looly * @since 5.7.16 */ - class Chain implements cn.hutool.core.lang.Chain { - private final List interceptors = new LinkedList<>(); + class Chain> implements cn.hutool.core.lang.Chain, Chain> { + private final List> interceptors = new LinkedList<>(); @Override - public Chain addChain(HttpInterceptor element) { + public Chain addChain(HttpInterceptor element) { interceptors.add(element); return this; } @Override - public Iterator iterator() { + public Iterator> iterator() { return interceptors.iterator(); } @@ -46,7 +48,7 @@ public interface HttpInterceptor { * @return this * @since 5.8.0 */ - public Chain clear() { + public Chain clear() { interceptors.clear(); return this; } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java index aefce9c3b..cb56e4b00 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpRequest.java @@ -28,7 +28,6 @@ import java.io.IOException; import java.net.CookieManager; import java.net.HttpCookie; import java.net.HttpURLConnection; -import java.net.InetSocketAddress; import java.net.Proxy; import java.net.URLStreamHandler; import java.nio.charset.Charset; @@ -200,43 +199,27 @@ public class HttpRequest extends HttpBase { } // ---------------------------------------------------------------- static Http Method end + private HttpConfig config = HttpConfig.create(); private UrlBuilder url; private URLStreamHandler urlHandler; private Method method = Method.GET; /** - * 请求前的拦截器,用于在请求前重新编辑请求 + * 连接对象 */ - private final HttpInterceptor.Chain interceptors = GlobalInterceptor.INSTANCE.getCopied(); + private HttpConnection httpConnection; - /** - * 默认连接超时 - */ - private int connectionTimeout = HttpGlobalConfig.getTimeout(); - /** - * 默认读取超时 - */ - private int readTimeout = HttpGlobalConfig.getTimeout(); /** * 存储表单数据 */ private Map form; - /** - * 是否为Multipart表单 - */ - private boolean isMultiPart; /** * Cookie */ private String cookie; - /** - * 连接对象 + * 是否为Multipart表单 */ - private HttpConnection httpConnection; - /** - * 是否禁用缓存 - */ - private boolean isDisableCache; + private boolean isMultiPart; /** * 是否是REST请求模式 */ @@ -245,27 +228,6 @@ public class HttpRequest extends HttpBase { * 重定向次数计数器,内部使用 */ private int redirectCount; - /** - * 最大重定向次数 - */ - private int maxRedirectCount = HttpGlobalConfig.getMaxRedirectCount(); - /** - * Chuncked块大小,0或小于0表示不设置Chuncked模式 - */ - private int blockSize; - /** - * 代理 - */ - private Proxy proxy; - - /** - * HostnameVerifier,用于HTTPS安全连接 - */ - private HostnameVerifier hostnameVerifier; - /** - * SSLSocketFactory,用于HTTPS安全连接 - */ - private SSLSocketFactory ssf; /** * 构造,URL编码默认使用UTF-8 @@ -784,6 +746,18 @@ public class HttpRequest extends HttpBase { } // ---------------------------------------------------------------- Body end + /** + * 将新的配置加入
+ * 注意加入的配置可能被修改 + * + * @param config 配置 + * @return this + */ + public HttpRequest setConfig(HttpConfig config){ + this.config = config; + return this; + } + /** * 设置超时,单位:毫秒
* 超时包括: @@ -799,8 +773,7 @@ public class HttpRequest extends HttpBase { * @see #setReadTimeout(int) */ public HttpRequest timeout(int milliseconds) { - setConnectionTimeout(milliseconds); - setReadTimeout(milliseconds); + config.timeout(milliseconds); return this; } @@ -812,7 +785,7 @@ public class HttpRequest extends HttpBase { * @since 4.5.6 */ public HttpRequest setConnectionTimeout(int milliseconds) { - this.connectionTimeout = milliseconds; + config.setConnectionTimeout(milliseconds); return this; } @@ -824,7 +797,7 @@ public class HttpRequest extends HttpBase { * @since 4.5.6 */ public HttpRequest setReadTimeout(int milliseconds) { - this.readTimeout = milliseconds; + config.setReadTimeout(milliseconds); return this; } @@ -834,7 +807,7 @@ public class HttpRequest extends HttpBase { * @return this */ public HttpRequest disableCache() { - this.isDisableCache = true; + config.disableCache(); return this; } @@ -858,7 +831,7 @@ public class HttpRequest extends HttpBase { * @since 3.3.0 */ public HttpRequest setMaxRedirectCount(int maxRedirectCount) { - this.maxRedirectCount = Math.max(maxRedirectCount, 0); + config.setMaxRedirectCount(maxRedirectCount); return this; } @@ -870,8 +843,7 @@ public class HttpRequest extends HttpBase { * @return this */ public HttpRequest setHostnameVerifier(HostnameVerifier hostnameVerifier) { - // 验证域 - this.hostnameVerifier = hostnameVerifier; + config.setHostnameVerifier(hostnameVerifier); return this; } @@ -884,9 +856,8 @@ public class HttpRequest extends HttpBase { * @since 5.4.5 */ public HttpRequest setHttpProxy(String host, int port) { - final Proxy proxy = new Proxy(Proxy.Type.HTTP, - new InetSocketAddress(host, port)); - return setProxy(proxy); + config.setHttpProxy(host, port); + return this; } /** @@ -896,7 +867,7 @@ public class HttpRequest extends HttpBase { * @return this */ public HttpRequest setProxy(Proxy proxy) { - this.proxy = proxy; + config.setProxy(proxy); return this; } @@ -909,7 +880,7 @@ public class HttpRequest extends HttpBase { * @return this */ public HttpRequest setSSLSocketFactory(SSLSocketFactory ssf) { - this.ssf = ssf; + config.setSSLSocketFactory(ssf); return this; } @@ -930,8 +901,7 @@ public class HttpRequest extends HttpBase { * @see #setSSLSocketFactory(SSLSocketFactory) */ public HttpRequest setSSLProtocol(String protocol) { - Assert.notBlank(protocol, "protocol must be not blank!"); - setSSLSocketFactory(SSLUtil.createSSLContext(protocol).getSocketFactory()); + config.setSSLProtocol(protocol); return this; } @@ -957,7 +927,7 @@ public class HttpRequest extends HttpBase { * @since 4.6.5 */ public HttpRequest setChunkedStreamingMode(int blockSize) { - this.blockSize = blockSize; + config.setBlockSize(blockSize); return this; } @@ -966,9 +936,31 @@ public class HttpRequest extends HttpBase { * * @param interceptor 拦截器实现 * @since 5.7.16 + * @see #addRequestInterceptor(HttpInterceptor) */ - public HttpRequest addInterceptor(HttpInterceptor interceptor) { - this.interceptors.addChain(interceptor); + public HttpRequest addInterceptor(HttpInterceptor interceptor) { + return addRequestInterceptor(interceptor); + } + + /** + * 设置拦截器,用于在请求前重新编辑请求 + * + * @param interceptor 拦截器实现 + * @since 5.8.0 + */ + public HttpRequest addRequestInterceptor(HttpInterceptor interceptor) { + config.addRequestInterceptor(interceptor); + return this; + } + + /** + * 设置拦截器,用于在请求前重新编辑请求 + * + * @param interceptor 拦截器实现 + * @since 5.8.0 + */ + public HttpRequest addResponseInterceptor(HttpInterceptor interceptor) { + config.addResponseInterceptor(interceptor); return this; } @@ -1002,7 +994,7 @@ public class HttpRequest extends HttpBase { * @return this */ public HttpResponse execute(boolean isAsync) { - return doExecute(isAsync, this.interceptors); + return doExecute(isAsync, config.requestInterceptors, config.responseInterceptors); } /** @@ -1096,12 +1088,14 @@ public class HttpRequest extends HttpBase { * 执行Reuqest请求 * * @param isAsync 是否异步 - * @param interceptors 拦截器列表 + * @param requestInterceptors 请求拦截器列表 + * @param responseInterceptors 响应拦截器列表 * @return this */ - private HttpResponse doExecute(boolean isAsync, HttpInterceptor.Chain interceptors) { - if (null != interceptors) { - for (HttpInterceptor interceptor : interceptors) { + private HttpResponse doExecute(boolean isAsync, HttpInterceptor.Chain requestInterceptors, + HttpInterceptor.Chain responseInterceptors) { + if (null != requestInterceptors) { + for (HttpInterceptor interceptor : requestInterceptors) { interceptor.process(this); } } @@ -1118,7 +1112,14 @@ public class HttpRequest extends HttpBase { // 获取响应 if (null == httpResponse) { - httpResponse = new HttpResponse(this.httpConnection, this.charset, isAsync, isIgnoreResponseBody()); + httpResponse = new HttpResponse(this.httpConnection, this.config, this.charset, isAsync, isIgnoreResponseBody()); + } + + // 拦截响应 + if (null != responseInterceptors) { + for (HttpInterceptor interceptor : responseInterceptors) { + interceptor.process(httpResponse); + } } return httpResponse; @@ -1134,15 +1135,15 @@ public class HttpRequest extends HttpBase { } this.httpConnection = HttpConnection - .create(this.url.toURL(this.urlHandler), this.proxy)// - .setConnectTimeout(this.connectionTimeout)// - .setReadTimeout(this.readTimeout)// + .create(this.url.toURL(this.urlHandler), config.proxy)// + .setConnectTimeout(config.connectionTimeout)// + .setReadTimeout(config.readTimeout)// .setMethod(this.method)// - .setHttpsInfo(this.hostnameVerifier, this.ssf)// + .setHttpsInfo(config.hostnameVerifier, config.ssf)// // 关闭JDK自动转发,采用手动转发方式 .setInstanceFollowRedirects(false) // 流方式上传数据 - .setChunkedStreamingMode(this.blockSize) + .setChunkedStreamingMode(config.blockSize) // 覆盖默认Header .header(this.headers, true); @@ -1155,7 +1156,7 @@ public class HttpRequest extends HttpBase { } // 是否禁用缓存 - if (this.isDisableCache) { + if (config.isDisableCache) { this.httpConnection.disableCache(); } } @@ -1184,7 +1185,7 @@ public class HttpRequest extends HttpBase { */ private HttpResponse sendRedirectIfPossible(boolean isAsync) { // 手动实现重定向 - if (this.maxRedirectCount > 0) { + if (config.maxRedirectCount > 0) { int responseCode; try { responseCode = httpConnection.responseCode(); @@ -1197,10 +1198,11 @@ public class HttpRequest extends HttpBase { if (responseCode != HttpURLConnection.HTTP_OK) { if (HttpStatus.isRedirected(responseCode)) { setUrl(UrlBuilder.ofHttpWithoutEncode(httpConnection.header(Header.LOCATION))); - if (redirectCount < this.maxRedirectCount) { + if (redirectCount < config.maxRedirectCount) { redirectCount++; // 重定向不再走过滤器 - return doExecute(isAsync, null); + return doExecute(isAsync, config.interceptorOnRedirect ? config.requestInterceptors : null, + config.interceptorOnRedirect ? config.responseInterceptors : null); } } } diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java b/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java index d43ae1e6c..9bbeff7c3 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java @@ -33,6 +33,10 @@ import java.util.Map.Entry; */ public class HttpResponse extends HttpBase implements Closeable { + /** + * Http配置 + */ + protected HttpConfig config; /** * 持有连接对象 */ @@ -62,13 +66,15 @@ public class HttpResponse extends HttpBase implements Closeable { * 构造 * * @param httpConnection {@link HttpConnection} + * @param config Http配置 * @param charset 编码,从请求编码中获取默认编码 * @param isAsync 是否异步 * @param isIgnoreBody 是否忽略读取响应体 * @since 3.1.2 */ - protected HttpResponse(HttpConnection httpConnection, Charset charset, boolean isAsync, boolean isIgnoreBody) { + protected HttpResponse(HttpConnection httpConnection, HttpConfig config, Charset charset, boolean isAsync, boolean isIgnoreBody) { this.httpConnection = httpConnection; + this.config = config; this.charset = charset; this.isAsync = isAsync; this.ignoreBody = isIgnoreBody; @@ -273,7 +279,7 @@ public class HttpResponse extends HttpBase implements Closeable { Assert.notNull(out, "[out] must be not null!"); final long contentLength = contentLength(); try { - return copyBody(bodyStream(), out, contentLength, streamProgress); + return copyBody(bodyStream(), out, contentLength, streamProgress, this.config.ignoreEOFError); } finally { IoUtil.close(this); if (isCloseOut) { @@ -563,7 +569,7 @@ public class HttpResponse extends HttpBase implements Closeable { final long contentLength = contentLength(); final FastByteArrayOutputStream out = new FastByteArrayOutputStream((int) contentLength); - copyBody(in, out, contentLength, null); + copyBody(in, out, contentLength, null, this.config.ignoreEOFError); this.bodyBytes = out.toByteArray(); } @@ -572,13 +578,14 @@ public class HttpResponse extends HttpBase implements Closeable { * 异步模式下直接读取Http流写出,同步模式下将存储在内存中的响应内容写出
* 写出后会关闭Http流(异步模式) * - * @param in 输入流 - * @param out 写出的流 - * @param contentLength 总长度,-1表示未知 - * @param streamProgress 进度显示接口,通过实现此接口显示下载进度 + * @param in 输入流 + * @param out 写出的流 + * @param contentLength 总长度,-1表示未知 + * @param streamProgress 进度显示接口,通过实现此接口显示下载进度 + * @param isIgnoreEOFError 是否忽略响应读取时可能的EOF异常 * @return 拷贝长度 */ - private static long copyBody(InputStream in, OutputStream out, long contentLength, StreamProgress streamProgress) { + private static long copyBody(InputStream in, OutputStream out, long contentLength, StreamProgress streamProgress, boolean isIgnoreEOFError) { if (null == out) { throw new NullPointerException("[out] is null!"); } @@ -588,7 +595,7 @@ public class HttpResponse extends HttpBase implements Closeable { copyLength = IoUtil.copy(in, out, IoUtil.DEFAULT_BUFFER_SIZE, contentLength, streamProgress); } catch (IORuntimeException e) { //noinspection StatementWithEmptyBody - if (HttpGlobalConfig.isIgnoreEOFError() + if (isIgnoreEOFError && (e.getCause() instanceof EOFException || StrUtil.containsIgnoreCase(e.getMessage(), "Premature EOF"))) { // 忽略读取HTTP流中的EOF错误 } else { diff --git a/hutool-http/src/test/java/cn/hutool/http/HttpRequestTest.java b/hutool-http/src/test/java/cn/hutool/http/HttpRequestTest.java index d60a25f2a..4483e1e28 100644 --- a/hutool-http/src/test/java/cn/hutool/http/HttpRequestTest.java +++ b/hutool-http/src/test/java/cn/hutool/http/HttpRequestTest.java @@ -171,13 +171,16 @@ public class HttpRequestTest { @Test @Ignore public void addInterceptorTest() { - HttpUtil.createGet("https://hutool.cn").addInterceptor(Console::log).execute(); + HttpUtil.createGet("https://hutool.cn") + .addInterceptor(Console::log) + .addResponseInterceptor((res)-> Console.log(res.getStatus())) + .execute(); } @Test @Ignore public void addGlobalInterceptorTest() { - GlobalInterceptor.INSTANCE.addInterceptor(Console::log); + GlobalInterceptor.INSTANCE.addRequestInterceptor(Console::log); HttpUtil.createGet("https://hutool.cn").execute(); }