diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/HttpGlobalConfig.java b/hutool-http/src/main/java/org/dromara/hutool/http/HttpGlobalConfig.java index bf6418aa0..f75f21d17 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/HttpGlobalConfig.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/HttpGlobalConfig.java @@ -17,10 +17,8 @@ package org.dromara.hutool.http; import org.dromara.hutool.core.util.RandomUtil; -import org.dromara.hutool.http.client.cookie.GlobalCookieManager; import java.io.Serializable; -import java.net.CookieManager; import java.net.HttpURLConnection; /** @@ -139,38 +137,6 @@ public class HttpGlobalConfig implements Serializable { ignoreEOFError = customIgnoreEOFError; } - /** - * 获取Cookie管理器,用于自定义Cookie管理 - * - * @return {@link CookieManager} - * @see GlobalCookieManager#getCookieManager() - * @since 4.1.0 - */ - public static CookieManager getCookieManager() { - return GlobalCookieManager.getCookieManager(); - } - - /** - * 自定义{@link CookieManager} - * - * @param customCookieManager 自定义的{@link CookieManager} - * @see GlobalCookieManager#setCookieManager(CookieManager) - * @since 4.5.14 - */ - synchronized public static void setCookieManager(final CookieManager customCookieManager) { - GlobalCookieManager.setCookieManager(customCookieManager); - } - - /** - * 关闭Cookie - * - * @see GlobalCookieManager#setCookieManager(CookieManager) - * @since 4.1.9 - */ - synchronized public static void closeCookie() { - GlobalCookieManager.setCookieManager(null); - } - /** * 设置是否从响应正文中的meta标签获取编码信息 * diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/client/HttpClientConfig.java b/hutool-http/src/main/java/org/dromara/hutool/http/client/ApacheHttpClientConfig.java similarity index 85% rename from hutool-http/src/main/java/org/dromara/hutool/http/client/HttpClientConfig.java rename to hutool-http/src/main/java/org/dromara/hutool/http/client/ApacheHttpClientConfig.java index 4a6d1d2f0..d6fbc6e56 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/client/HttpClientConfig.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/client/ApacheHttpClientConfig.java @@ -22,15 +22,15 @@ package org.dromara.hutool.http.client; * @author Looly * @since 6.0.0 */ -public class HttpClientConfig extends ClientConfig { +public class ApacheHttpClientConfig extends ClientConfig { /** * 创建新的 HttpClientConfig * * @return HttpClientConfig */ - public static HttpClientConfig of() { - return new HttpClientConfig(); + public static ApacheHttpClientConfig of() { + return new ApacheHttpClientConfig(); } /** @@ -61,7 +61,7 @@ public class HttpClientConfig extends ClientConfig { * @param maxTotal 最大连接总数 * @return 当前HttpClientConfig实例,用于链式调用 */ - public HttpClientConfig setMaxTotal(final int maxTotal) { + public ApacheHttpClientConfig setMaxTotal(final int maxTotal) { this.maxTotal = maxTotal; return this; } @@ -81,7 +81,7 @@ public class HttpClientConfig extends ClientConfig { * @param maxPerRoute 每个路由的最大连接数 * @return 当前HttpClientConfig实例,用于链式调用 */ - public HttpClientConfig setMaxPerRoute(final int maxPerRoute) { + public ApacheHttpClientConfig setMaxPerRoute(final int maxPerRoute) { this.maxPerRoute = maxPerRoute; return this; } @@ -101,7 +101,7 @@ public class HttpClientConfig extends ClientConfig { * @param maxRedirects 重定向最大次数 * @return this */ - public HttpClientConfig setMaxRedirects(final int maxRedirects) { + public ApacheHttpClientConfig setMaxRedirects(final int maxRedirects) { this.maxRedirects = maxRedirects; return this; } diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/client/ClientConfig.java b/hutool-http/src/main/java/org/dromara/hutool/http/client/ClientConfig.java index a24457602..f8e2a6768 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/client/ClientConfig.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/client/ClientConfig.java @@ -60,6 +60,12 @@ public class ClientConfig { * 是否遇到响应状态码3xx时自动重定向请求 */ private boolean followRedirects; + /** + * 是否使用引擎默认的Cookie管理器,默认为true
+ * 默认情况下每个客户端维护一个自己的Cookie管理器,这个管理器用于在多次请求中记录并自动附带Cookie信息
+ * 如请求登录后,服务器返回Set-Cookie信息,Cookie管理器记录之,后续请求会自动带上这个Cookie信息,从而实现会话保持。 + */ + private boolean useCookieManager = true; /** * 构造 @@ -230,4 +236,28 @@ public class ClientConfig { this.followRedirects = followRedirects; return this; } + + /** + * 是否使用引擎默认的Cookie管理器,默认为true
+ * 默认情况下每个客户端维护一个自己的Cookie管理器,这个管理器用于在多次请求中记录并自动附带Cookie信息
+ * 如请求登录后,服务器返回Set-Cookie信息,Cookie管理器记录之,后续请求会自动带上这个Cookie信息,从而实现会话保持。 + * + * @return 是否使用引擎默认的Cookie管理器 + */ + public boolean isUseCookieManager() { + return useCookieManager; + } + + /** + * 是否使用引擎默认的Cookie管理器,默认为true
+ * 默认情况下每个客户端维护一个自己的Cookie管理器,这个管理器用于在多次请求中记录并自动附带Cookie信息
+ * 如请求登录后,服务器返回Set-Cookie信息,Cookie管理器记录之,后续请求会自动带上这个Cookie信息,从而实现会话保持。 + * + * @param useCookieManager 是否使用引擎默认的Cookie管理器 + * @return this + */ + public ClientConfig setUseCookieManager(final boolean useCookieManager) { + this.useCookieManager = useCookieManager; + return this; + } } diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/client/cookie/GlobalCookieManager.java b/hutool-http/src/main/java/org/dromara/hutool/http/client/cookie/GlobalCookieManager.java deleted file mode 100644 index 34df14a3e..000000000 --- a/hutool-http/src/main/java/org/dromara/hutool/http/client/cookie/GlobalCookieManager.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (c) 2013-2024 Hutool Team and hutool.cn - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.dromara.hutool.http.client.cookie; - -import org.dromara.hutool.core.io.IORuntimeException; -import org.dromara.hutool.core.map.MapUtil; -import org.dromara.hutool.core.net.url.UrlUtil; -import org.dromara.hutool.http.client.engine.jdk.JdkHttpConnection; - -import java.io.IOException; -import java.net.CookieManager; -import java.net.CookiePolicy; -import java.net.HttpCookie; -import java.net.URI; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * 全局Cookie管理器,只针对Hutool请求有效 - * - * @author Looly - * @since 4.5.15 - */ -public class GlobalCookieManager { - - /** Cookie管理 */ - private static CookieManager cookieManager; - static { - cookieManager = new CookieManager(new ThreadLocalCookieStore(), CookiePolicy.ACCEPT_ALL); - } - - /** - * 关闭Cookie - * - * @see GlobalCookieManager#setCookieManager(CookieManager) - * @since 5.6.5 - */ - public static void closeCookie() { - setCookieManager(null); - } - - /** - * 自定义{@link CookieManager} - * - * @param customCookieManager 自定义的{@link CookieManager} - */ - public static void setCookieManager(final CookieManager customCookieManager) { - cookieManager = customCookieManager; - } - - /** - * 获取全局{@link CookieManager} - * - * @return {@link CookieManager} - */ - public static CookieManager getCookieManager() { - return cookieManager; - } - - /** - * 获取指定域名下所有Cookie信息 - * - * @param conn HTTP连接 - * @return Cookie信息列表 - * @since 4.6.9 - */ - public static List getCookies(final JdkHttpConnection conn){ - return cookieManager.getCookieStore().get(getURI(conn)); - } - - /** - * 将本地存储的Cookie信息附带到Http请求中,不覆盖用户定义好的Cookie - * - * @param conn {@link JdkHttpConnection} - */ - public static void add(final JdkHttpConnection conn) { - if(null == cookieManager) { - // 全局Cookie管理器关闭 - return; - } - - final Map> cookieHeader; - try { - cookieHeader = cookieManager.get(getURI(conn), new HashMap<>(0)); - } catch (final IOException e) { - throw new IORuntimeException(e); - } - - - // 不覆盖模式回填Cookie头,这样用户定义的Cookie将优先 - conn.header(cookieHeader, false); - } - - /** - * 存储响应的Cookie信息到本地 - * - * @param conn {@link JdkHttpConnection} - */ - public static void store(final JdkHttpConnection conn) { - store(conn, conn.headers()); - } - - /** - * 存储响应的Cookie信息到本地
- * 通过读取 - * - * @param conn {@link JdkHttpConnection} - * @param responseHeaders 头信息Map - */ - public static void store(final JdkHttpConnection conn, final Map> responseHeaders) { - if(null == cookieManager || MapUtil.isEmpty(responseHeaders)) { - // 全局Cookie管理器关闭或头信息为空 - return; - } - - try { - cookieManager.put(getURI(conn), responseHeaders); - } catch (final IOException e) { - throw new IORuntimeException(e); - } - } - - /** - * 获取连接的URL中URI信息 - * @param conn HttpConnection - * @return URI - */ - private static URI getURI(final JdkHttpConnection conn){ - return UrlUtil.toURI(conn.getUrl()); - } -} diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/ClientEngine.java b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/ClientEngine.java index f30a7325e..3baf5f513 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/ClientEngine.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/ClientEngine.java @@ -16,6 +16,7 @@ package org.dromara.hutool.http.client.engine; +import org.dromara.hutool.http.client.ApacheHttpClientConfig; import org.dromara.hutool.http.client.ClientConfig; import org.dromara.hutool.http.client.Request; import org.dromara.hutool.http.client.Response; @@ -35,7 +36,7 @@ public interface ClientEngine extends Closeable { * 对不同引擎个性化配置,使用对应的{@link ClientConfig} 子类: * *
    - *
  • HttpClient4和HttpClient5使用{@link org.dromara.hutool.http.client.HttpClientConfig}
  • + *
  • HttpClient4和HttpClient5使用{@link ApacheHttpClientConfig}
  • *
  • OkHttp使用{@link org.dromara.hutool.http.client.engine.okhttp.OkHttpClientConfig}
  • *
*

diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/ClientEngineFactory.java b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/ClientEngineFactory.java index 791bc0822..3d2171fcf 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/ClientEngineFactory.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/ClientEngineFactory.java @@ -21,6 +21,7 @@ import org.dromara.hutool.core.spi.ServiceLoader; import org.dromara.hutool.core.spi.SpiUtil; import org.dromara.hutool.core.text.StrUtil; import org.dromara.hutool.http.HttpException; +import org.dromara.hutool.http.client.ApacheHttpClientConfig; import org.dromara.hutool.http.client.ClientConfig; import org.dromara.hutool.log.LogUtil; @@ -47,7 +48,7 @@ public class ClientEngineFactory { * 对不同引擎个性化配置,使用对应的{@link ClientConfig} 子类: * *

    - *
  • HttpClient4和HttpClient5使用{@link org.dromara.hutool.http.client.HttpClientConfig}
  • + *
  • HttpClient4和HttpClient5使用{@link ApacheHttpClientConfig}
  • *
  • OkHttp使用{@link org.dromara.hutool.http.client.engine.okhttp.OkHttpClientConfig}
  • *
*

diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/httpclient4/HttpClient4Engine.java b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/httpclient4/HttpClient4Engine.java index 01591d730..63a731392 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/httpclient4/HttpClient4Engine.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/httpclient4/HttpClient4Engine.java @@ -20,6 +20,7 @@ import org.apache.http.Header; import org.apache.http.HttpHost; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CookieStore; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.RequestBuilder; @@ -34,7 +35,7 @@ import org.dromara.hutool.core.util.ObjUtil; import org.dromara.hutool.http.GlobalHeaders; import org.dromara.hutool.http.HttpException; import org.dromara.hutool.http.client.ClientConfig; -import org.dromara.hutool.http.client.HttpClientConfig; +import org.dromara.hutool.http.client.ApacheHttpClientConfig; import org.dromara.hutool.http.client.Request; import org.dromara.hutool.http.client.Response; import org.dromara.hutool.http.client.body.HttpBody; @@ -59,6 +60,7 @@ import java.util.Map; public class HttpClient4Engine extends AbstractClientEngine { private CloseableHttpClient engine; + private CookieStore cookieStore; /** * 构造 @@ -69,6 +71,15 @@ public class HttpClient4Engine extends AbstractClientEngine { Assert.notNull(CloseableHttpClient.class); } + /** + * 获得Cookie存储器 + * + * @return Cookie存储器 + */ + public CookieStore getCookieStore() { + return this.cookieStore; + } + @Override public Response send(final Request message) { initEngine(); @@ -105,7 +116,7 @@ public class HttpClient4Engine extends AbstractClientEngine { } final HttpClientBuilder clientBuilder = HttpClients.custom(); - final ClientConfig config = ObjUtil.defaultIfNull(this.config, HttpClientConfig::of); + final ClientConfig config = ObjUtil.defaultIfNull(this.config, ApacheHttpClientConfig::of); // SSL配置 final SSLInfo sslInfo = config.getSslInfo(); @@ -113,7 +124,7 @@ public class HttpClient4Engine extends AbstractClientEngine { clientBuilder.setSSLSocketFactory(buildSocketFactory(sslInfo)); } - // 连接配置 + // 连接配置,包括连接池 clientBuilder.setConnectionManager(buildConnectionManager(config)); // 实例级别默认请求配置 @@ -137,6 +148,12 @@ public class HttpClient4Engine extends AbstractClientEngine { // 设置代理 setProxy(clientBuilder, config); + // Cookie管理 + if(config.isUseCookieManager()){ + this.cookieStore = new BasicCookieStore(); + clientBuilder.setDefaultCookieStore(this.cookieStore); + } + this.engine = clientBuilder.build(); } @@ -208,10 +225,10 @@ public class HttpClient4Engine extends AbstractClientEngine { final PoolingHttpClientConnectionManager manager = new PoolingHttpClientConnectionManager(); // 连接池配置 - if (config instanceof HttpClientConfig) { - final HttpClientConfig httpClientConfig = (HttpClientConfig) config; - manager.setMaxTotal(httpClientConfig.getMaxTotal()); - manager.setDefaultMaxPerRoute(httpClientConfig.getMaxPerRoute()); + if (config instanceof ApacheHttpClientConfig) { + final ApacheHttpClientConfig apacheHttpClientConfig = (ApacheHttpClientConfig) config; + manager.setMaxTotal(apacheHttpClientConfig.getMaxTotal()); + manager.setDefaultMaxPerRoute(apacheHttpClientConfig.getMaxPerRoute()); } return manager; @@ -255,8 +272,8 @@ public class HttpClient4Engine extends AbstractClientEngine { if (readTimeout > 0) { requestConfigBuilder.setSocketTimeout(readTimeout); } - if (config instanceof HttpClientConfig) { - requestConfigBuilder.setMaxRedirects(((HttpClientConfig) config).getMaxRedirects()); + if (config instanceof ApacheHttpClientConfig) { + requestConfigBuilder.setMaxRedirects(((ApacheHttpClientConfig) config).getMaxRedirects()); } return requestConfigBuilder.build(); diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/httpclient5/HttpClient5Engine.java b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/httpclient5/HttpClient5Engine.java index dcfce4a06..8729ad73d 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/httpclient5/HttpClient5Engine.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/httpclient5/HttpClient5Engine.java @@ -21,6 +21,8 @@ import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase; import org.apache.hc.client5.http.config.ConnectionConfig; import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.cookie.BasicCookieStore; +import org.apache.hc.client5.http.cookie.CookieStore; import org.apache.hc.client5.http.impl.DefaultRedirectStrategy; import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; @@ -40,7 +42,7 @@ import org.dromara.hutool.core.util.ObjUtil; import org.dromara.hutool.http.GlobalHeaders; import org.dromara.hutool.http.HttpException; import org.dromara.hutool.http.client.ClientConfig; -import org.dromara.hutool.http.client.HttpClientConfig; +import org.dromara.hutool.http.client.ApacheHttpClientConfig; import org.dromara.hutool.http.client.Request; import org.dromara.hutool.http.client.Response; import org.dromara.hutool.http.client.body.HttpBody; @@ -66,6 +68,7 @@ import java.util.concurrent.TimeUnit; public class HttpClient5Engine extends AbstractClientEngine { private CloseableHttpClient engine; + private CookieStore cookieStore; /** * 构造 @@ -76,6 +79,15 @@ public class HttpClient5Engine extends AbstractClientEngine { Assert.notNull(CloseableHttpClient.class); } + /** + * 获得Cookie存储器 + * + * @return Cookie存储器 + */ + public CookieStore getCookieStore() { + return this.cookieStore; + } + @Override public Response send(final Request message) { initEngine(); @@ -112,7 +124,7 @@ public class HttpClient5Engine extends AbstractClientEngine { } final HttpClientBuilder clientBuilder = HttpClients.custom(); - final ClientConfig config = ObjUtil.defaultIfNull(this.config, HttpClientConfig::of); + final ClientConfig config = ObjUtil.defaultIfNull(this.config, ApacheHttpClientConfig::of); // 连接配置,包括SSL配置等 clientBuilder.setConnectionManager(buildConnectionManager(config)); @@ -138,6 +150,12 @@ public class HttpClient5Engine extends AbstractClientEngine { // 设置代理 setProxy(clientBuilder, config); + // Cookie管理 + if(config.isUseCookieManager()){ + this.cookieStore = new BasicCookieStore(); + clientBuilder.setDefaultCookieStore(this.cookieStore); + } + this.engine = clientBuilder.build(); } @@ -211,10 +229,10 @@ public class HttpClient5Engine extends AbstractClientEngine { } // 连接池配置 - if (config instanceof HttpClientConfig) { - final HttpClientConfig httpClientConfig = (HttpClientConfig) config; - final int maxTotal = httpClientConfig.getMaxTotal(); - final int maxPerRoute = httpClientConfig.getMaxPerRoute(); + if (config instanceof ApacheHttpClientConfig) { + final ApacheHttpClientConfig apacheHttpClientConfig = (ApacheHttpClientConfig) config; + final int maxTotal = apacheHttpClientConfig.getMaxTotal(); + final int maxPerRoute = apacheHttpClientConfig.getMaxPerRoute(); if (maxTotal > 0) { connectionManagerBuilder.setMaxConnTotal(maxTotal); } @@ -262,8 +280,8 @@ public class HttpClient5Engine extends AbstractClientEngine { if (readTimeout > 0) { requestConfigBuilder.setResponseTimeout(readTimeout, TimeUnit.MILLISECONDS); } - if (config instanceof HttpClientConfig) { - requestConfigBuilder.setMaxRedirects(((HttpClientConfig) config).getMaxRedirects()); + if (config instanceof ApacheHttpClientConfig) { + requestConfigBuilder.setMaxRedirects(((ApacheHttpClientConfig) config).getMaxRedirects()); } return requestConfigBuilder.build(); diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/jdk/JdkClientEngine.java b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/jdk/JdkClientEngine.java index 2ed5e237e..efe97e278 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/jdk/JdkClientEngine.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/jdk/JdkClientEngine.java @@ -28,7 +28,6 @@ import org.dromara.hutool.http.client.ClientConfig; import org.dromara.hutool.http.client.Request; import org.dromara.hutool.http.client.Response; import org.dromara.hutool.http.client.body.HttpBody; -import org.dromara.hutool.http.client.cookie.GlobalCookieManager; import org.dromara.hutool.http.client.engine.AbstractClientEngine; import org.dromara.hutool.http.meta.HeaderName; import org.dromara.hutool.http.meta.HttpStatus; @@ -45,11 +44,24 @@ import java.util.List; */ public class JdkClientEngine extends AbstractClientEngine { + /** + * Cookie管理 + */ + private JdkCookieManager cookieManager; + /** * 构造 */ public JdkClientEngine() { - // 无需检查类是否存在 + } + + /** + * 获取Cookie管理器 + * + * @return Cookie管理器 + */ + public JdkCookieManager getCookieManager() { + return this.cookieManager; } @Override @@ -84,7 +96,8 @@ public class JdkClientEngine extends AbstractClientEngine { @Override public void close() { - // do nothing + // 关闭Cookie管理器 + this.cookieManager = null; } @Override @@ -94,7 +107,7 @@ public class JdkClientEngine extends AbstractClientEngine { @Override protected void initEngine() { - // do nothing + this.cookieManager = this.config.isUseCookieManager() ? new JdkCookieManager() : new JdkCookieManager(null); } /** @@ -148,8 +161,9 @@ public class JdkClientEngine extends AbstractClientEngine { } if (null == message.header(HeaderName.COOKIE)) { - // 用户没有自定义Cookie,则读取全局Cookie信息并附带到请求中 - GlobalCookieManager.add(conn); + // 用户没有自定义Cookie,则读取Cookie管理器中的信息并附带到请求中 + // 不覆盖模式回填Cookie头,这样用户定义的Cookie将优先 + conn.header(this.cookieManager.loadForRequest(conn), false); } return conn; @@ -195,7 +209,7 @@ public class JdkClientEngine extends AbstractClientEngine { } // 最终页面 - return new JdkHttpResponse(conn, true, message.charset(), isAsync, message.method().isIgnoreBody()); + return new JdkHttpResponse(conn, this.cookieManager, true, message.charset(), isAsync, message.method().isIgnoreBody()); } /** diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/jdk/JdkCookieManager.java b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/jdk/JdkCookieManager.java new file mode 100644 index 000000000..31127067d --- /dev/null +++ b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/jdk/JdkCookieManager.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hutool.http.client.engine.jdk; + +import org.dromara.hutool.core.io.IORuntimeException; +import org.dromara.hutool.core.map.MapUtil; +import org.dromara.hutool.core.net.url.UrlUtil; +import org.dromara.hutool.http.client.cookie.ThreadLocalCookieStore; + +import java.io.Closeable; +import java.io.IOException; +import java.net.CookieManager; +import java.net.CookiePolicy; +import java.net.HttpCookie; +import java.net.URI; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * JDK Cookie管理器,用于管理Cookie信息,包括: + * + *

+ * 1. 存储Cookie信息到本地
+ * 2. 从本地获取Cookie信息,添加到请求头中
+ * 
+ * + * @author Looly + * @since 6.0.0 + */ +public class JdkCookieManager implements Closeable { + + private CookieManager cookieManager; + + /** + * 构造 + */ + public JdkCookieManager() { + this(new CookieManager(new ThreadLocalCookieStore(), CookiePolicy.ACCEPT_ALL)); + } + + /** + * 构造 + * + * @param raw 原始对象 + */ + public JdkCookieManager(final CookieManager raw) { + this.cookieManager = raw; + } + + /** + * 是否启用Cookie管理 + * + * @return 是否启用Cookie管理 + */ + public boolean isEnable() { + return null != this.cookieManager; + } + + @Override + public void close() { + this.cookieManager = null; + } + + /** + * 获取{@link CookieManager} + * + * @return {@link CookieManager} + */ + public CookieManager getCookieManager() { + return this.cookieManager; + } + + /** + * 自定义{@link CookieManager} + * + * @param cookieManager 自定义的{@link CookieManager} + * @return this + */ + public JdkCookieManager setCookieManager(final CookieManager cookieManager) { + this.cookieManager = cookieManager; + return this; + } + + /** + * 获取指定域名下所有Cookie信息 + * + * @param conn HTTP连接 + * @return Cookie信息列表 + * @since 4.6.9 + */ + public List getCookies(final JdkHttpConnection conn) { + if (null == this.cookieManager) { + return null; + } + return this.cookieManager.getCookieStore().get(getURI(conn)); + } + + /** + * 将本地存储的Cookie信息附带到Http请求中,不覆盖用户定义好的Cookie + * + * @param conn {@link JdkHttpConnection} + * @return this + */ + public Map> loadForRequest(final JdkHttpConnection conn) { + if (null == cookieManager) { + // Cookie管理器关闭 + return null; + } + + try { + return this.cookieManager.get(getURI(conn), new HashMap<>(0)); + } catch (final IOException e) { + throw new IORuntimeException(e); + } + } + + /** + * 存储响应的Cookie信息到本地 + * + * @param conn {@link JdkHttpConnection} + * @return this + */ + public JdkCookieManager saveFromResponse(final JdkHttpConnection conn) { + return saveFromResponse(conn, conn.headers()); + } + + /** + * 存储响应的Cookie信息到本地
+ * 通过读取 + * + * @param conn {@link JdkHttpConnection} + * @param responseHeaders 头信息Map + * @return this + */ + public JdkCookieManager saveFromResponse(final JdkHttpConnection conn, final Map> responseHeaders) { + if (null == this.cookieManager || MapUtil.isEmpty(responseHeaders)) { + // Cookie管理器关闭或头信息为空 + return this; + } + + try { + cookieManager.put(getURI(conn), responseHeaders); + } catch (final IOException e) { + throw new IORuntimeException(e); + } + return this; + } + + /** + * 获取连接的URL中URI信息 + * + * @param conn HttpConnection + * @return URI + */ + private static URI getURI(final JdkHttpConnection conn) { + return UrlUtil.toURI(conn.getUrl()); + } +} diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/jdk/JdkHttpResponse.java b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/jdk/JdkHttpResponse.java index 6baeb88e7..326f0c755 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/jdk/JdkHttpResponse.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/jdk/JdkHttpResponse.java @@ -23,7 +23,7 @@ import org.dromara.hutool.http.HttpException; import org.dromara.hutool.http.HttpUtil; import org.dromara.hutool.http.client.Response; import org.dromara.hutool.http.client.body.ResponseBody; -import org.dromara.hutool.http.client.cookie.GlobalCookieManager; +import org.dromara.hutool.http.meta.HeaderName; import java.io.Closeable; import java.io.FileNotFoundException; @@ -46,10 +46,17 @@ public class JdkHttpResponse implements Response, Closeable { * 请求时的默认编码 */ private final Charset requestCharset; + /** + * Cookie管理器 + */ + private final JdkCookieManager cookieManager; /** * 响应内容体,{@code null} 表示无内容 */ private ResponseBody body; + /** + * 响应头 + */ private Map> headers; /** @@ -72,14 +79,20 @@ public class JdkHttpResponse implements Response, Closeable { * 构造 * * @param httpConnection {@link JdkHttpConnection} + * @param cookieManager Cookie管理器 * @param ignoreEOFError 是否忽略响应读取时可能的EOF异常 * @param requestCharset 编码,从请求编码中获取默认编码 * @param isAsync 是否异步 * @param isIgnoreBody 是否忽略读取响应体 */ - protected JdkHttpResponse(final JdkHttpConnection httpConnection, final boolean ignoreEOFError, - final Charset requestCharset, final boolean isAsync, final boolean isIgnoreBody) { + protected JdkHttpResponse(final JdkHttpConnection httpConnection, + final JdkCookieManager cookieManager, + final boolean ignoreEOFError, + final Charset requestCharset, + final boolean isAsync, + final boolean isIgnoreBody) { this.httpConnection = httpConnection; + this.cookieManager = cookieManager; this.ignoreEOFError = ignoreEOFError; this.requestCharset = requestCharset; init(isAsync, isIgnoreBody); @@ -133,13 +146,16 @@ public class JdkHttpResponse implements Response, Closeable { // ---------------------------------------------------------------- Http Response Header start /** - * 获取Cookie + * 获取Cookie
+ * 如果启用Cookie管理器,则返回这个站点相关的所有Cookie信息,否则返回当前的响应的Cookie信息 * * @return Cookie列表 - * @since 3.1.1 */ public List getCookies() { - return GlobalCookieManager.getCookies(this.httpConnection); + if (this.cookieManager.isEnable()) { + return this.cookieManager.getCookies(this.httpConnection); + } + return HttpCookie.parse(this.header(HeaderName.SET_COOKIE)); } /** @@ -256,7 +272,7 @@ public class JdkHttpResponse implements Response, Closeable { } // 存储服务端设置的Cookie信息 - GlobalCookieManager.store(httpConnection, this.headers); + this.cookieManager.saveFromResponse(this.httpConnection, this.headers); // 获取响应内容流 if (!isIgnoreBody) { diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/okhttp/CookieJarImpl.java b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/okhttp/CookieJarImpl.java new file mode 100644 index 000000000..327e4b802 --- /dev/null +++ b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/okhttp/CookieJarImpl.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hutool.http.client.engine.okhttp; + +import okhttp3.Cookie; +import okhttp3.CookieJar; +import okhttp3.HttpUrl; + +import java.util.List; + +/** + * CookieJar实现,用于OkHttp3 + * + * @author Looly + * @since 6.0.0 + */ +public class CookieJarImpl implements CookieJar { + + private final OkCookieStore cookieStore; + + /** + * 构造 + */ + public CookieJarImpl() { + this(new InMemoryOkCookieStore()); + } + + /** + * 获取Cookie存储器 + * + * @return Cookie存储器 + */ + public OkCookieStore getCookieStore() { + return this.cookieStore; + } + + /** + * 构造 + * + * @param cookieStore Cookie存储器,用于自定义Cookie存储实现 + */ + public CookieJarImpl(final OkCookieStore cookieStore) { + this.cookieStore = cookieStore; + } + + @Override + public List loadForRequest(final HttpUrl httpUrl) { + return this.cookieStore.get(httpUrl); + } + + @Override + public void saveFromResponse(final HttpUrl httpUrl, final List list) { + this.cookieStore.add(httpUrl, list); + } +} diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/okhttp/InMemoryOkCookieStore.java b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/okhttp/InMemoryOkCookieStore.java new file mode 100644 index 000000000..909ad5a66 --- /dev/null +++ b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/okhttp/InMemoryOkCookieStore.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hutool.http.client.engine.okhttp; + +import okhttp3.Cookie; +import okhttp3.HttpUrl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 内存CookieStore实现,用于OkHttp3 + * + * @author Looly + * @since 6.0.0 + */ +public class InMemoryOkCookieStore implements OkCookieStore { + + private final HashMap> cookies; + + /** + * 构造 + */ + public InMemoryOkCookieStore() { + this.cookies = new HashMap<>(); + } + + @Override + public void add(final HttpUrl httpUrl, final Cookie cookie) { + if (!cookie.persistent()) { + return; + } + + final String name = this.cookieName(cookie); + final String hostKey = httpUrl.host(); + + if (!this.cookies.containsKey(hostKey)) { + this.cookies.put(hostKey, new ConcurrentHashMap<>()); + } + cookies.get(hostKey).put(name, cookie); + } + + @Override + public void add(final HttpUrl httpUrl, final List cookies) { + for (final Cookie cookie : cookies) { + if (isCookieExpired(cookie)) { + continue; + } + this.add(httpUrl, cookie); + } + } + + @Override + public List get(final HttpUrl httpUrl) { + return this.get(httpUrl.host()); + } + + @Override + public List getCookies() { + final ArrayList result = new ArrayList<>(); + + for (final String hostKey : this.cookies.keySet()) { + result.addAll(this.get(hostKey)); + } + + return result; + } + + /** + * 获取cookie集合 + */ + private List get(final String hostKey) { + final ArrayList result = new ArrayList<>(); + + if (this.cookies.containsKey(hostKey)) { + final Collection cookies = this.cookies.get(hostKey).values(); + for (final Cookie cookie : cookies) { + if (isCookieExpired(cookie)) { + this.remove(hostKey, cookie); + } else { + result.add(cookie); + } + } + } + return result; + } + + @Override + public boolean remove(final HttpUrl httpUrl, final Cookie cookie) { + return this.remove(httpUrl.host(), cookie); + } + + /** + * 从缓存中移除cookie + * + * @param hostKey hostKey + * @param cookie cookie + */ + private boolean remove(final String hostKey, final Cookie cookie) { + final String name = this.cookieName(cookie); + if (this.cookies.containsKey(hostKey) && this.cookies.get(hostKey).containsKey(name)) { + // 从内存中移除httpUrl对应的cookie + this.cookies.get(hostKey).remove(name); + return true; + } + return false; + } + + @Override + public boolean removeAll() { + this.cookies.clear(); + return true; + } + + /** + * 判断cookie是否失效 + */ + private boolean isCookieExpired(final Cookie cookie) { + return cookie.expiresAt() < System.currentTimeMillis(); + } + + /** + * 获取cookie的key
+ * 格式:name + domain + * + * @param cookie cookie + * @return cookie的key + */ + private String cookieName(final Cookie cookie) { + return cookie == null ? null : cookie.name() + cookie.domain(); + } +} diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/okhttp/OkCookieStore.java b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/okhttp/OkCookieStore.java new file mode 100644 index 000000000..9585dedd9 --- /dev/null +++ b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/okhttp/OkCookieStore.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hutool.http.client.engine.okhttp; + +import okhttp3.Cookie; +import okhttp3.HttpUrl; + +import java.util.List; + +/** + * OkHttp3 CookieStore接口 + * + * @author Looly + * @since 6.0.0 + */ +public interface OkCookieStore { + + /** + * 添加cookie + * + * @param httpUrl HTTP url 地址 + * @param cookie cookie + */ + void add(HttpUrl httpUrl, Cookie cookie); + + /** + * 添加指定 http url cookie集合 + * + * @param httpUrl HTTP url 地址 + * @param cookies cookie列表 + */ + void add(HttpUrl httpUrl, List cookies); + + /** + * 根据HttpUrl从缓存中读取cookie集合 + * + * @param httpUrl HTTP url 地址 + * @return cookie集合 + */ + List get(HttpUrl httpUrl); + + /** + * 获取全部缓存cookie + * + * @return cookie集合 + */ + List getCookies(); + + /** + * 移除指定http url cookie集合 + * + * @param httpUrl HTTP url 地址 + * @param cookie cookie + * @return 是否移除成功 + */ + boolean remove(HttpUrl httpUrl, Cookie cookie); + + /** + * 移除所有cookie + * + * @return 是否移除成功 + */ + boolean removeAll(); +} diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/okhttp/OkHttpEngine.java b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/okhttp/OkHttpEngine.java index 1a330adb4..67b80b281 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/okhttp/OkHttpEngine.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/client/engine/okhttp/OkHttpEngine.java @@ -43,6 +43,7 @@ import java.util.concurrent.TimeUnit; public class OkHttpEngine extends AbstractClientEngine { private OkHttpClient client; + private OkCookieStore cookieStore; /** * 构造 @@ -53,6 +54,15 @@ public class OkHttpEngine extends AbstractClientEngine { Assert.notNull(OkHttpClient.class); } + /** + * 获得Cookie存储器 + * + * @return Cookie存储器 + */ + public OkCookieStore getCookieStore() { + return this.cookieStore; + } + @Override public OkHttpEngine init(final ClientConfig config) { this.config = config; @@ -96,13 +106,24 @@ public class OkHttpEngine extends AbstractClientEngine { } final OkHttpClient.Builder builder = new OkHttpClient.Builder(); - final ClientConfig config = ObjUtil.defaultIfNull(this.config, ClientConfig::of); + + // SSL + final SSLInfo sslInfo = config.getSslInfo(); + if (null != sslInfo) { + final SSLSocketFactory socketFactory = sslInfo.getSocketFactory(); + final X509TrustManager trustManager = sslInfo.getTrustManager(); + if (null != socketFactory && null != trustManager) { + builder.sslSocketFactory(socketFactory, trustManager); + } + } + // 连接超时 final int connectionTimeout = config.getConnectionTimeout(); if (connectionTimeout > 0) { builder.connectTimeout(connectionTimeout, TimeUnit.MILLISECONDS); } + // 读写超时 final int readTimeout = config.getReadTimeout(); if (readTimeout > 0) { @@ -112,25 +133,21 @@ public class OkHttpEngine extends AbstractClientEngine { } // 连接池 - if(config instanceof OkHttpClientConfig){ + if (config instanceof OkHttpClientConfig) { builder.connectionPool(((OkHttpClientConfig) config).getConnectionPool()); } - // SSL - final SSLInfo sslInfo = config.getSslInfo(); - if (null != sslInfo){ - final SSLSocketFactory socketFactory = sslInfo.getSocketFactory(); - final X509TrustManager trustManager = sslInfo.getTrustManager(); - if(null != socketFactory && null != trustManager){ - builder.sslSocketFactory(socketFactory, trustManager); - } - } + // 重定向 + builder.followRedirects(config.isFollowRedirects()); // 设置代理 setProxy(builder, config); - // 重定向 - builder.followRedirects(config.isFollowRedirects()); + // Cookie管理 + if (this.config.isUseCookieManager()) { + this.cookieStore = new InMemoryOkCookieStore(); + builder.cookieJar(new CookieJarImpl(this.cookieStore)); + } this.client = builder.build(); } @@ -156,7 +173,7 @@ public class OkHttpEngine extends AbstractClientEngine { } // 填充头信息 - message.headers().forEach((key, values)-> values.forEach(value-> builder.addHeader(key, value))); + message.headers().forEach((key, values) -> values.forEach(value -> builder.addHeader(key, value))); return builder.build(); } diff --git a/hutool-http/src/test/java/org/dromara/hutool/http/webservice/SoapClientTest.java b/hutool-http/src/test/java/org/dromara/hutool/http/webservice/SoapClientTest.java index 13a61fb35..676773c56 100644 --- a/hutool-http/src/test/java/org/dromara/hutool/http/webservice/SoapClientTest.java +++ b/hutool-http/src/test/java/org/dromara/hutool/http/webservice/SoapClientTest.java @@ -21,9 +21,6 @@ import org.dromara.hutool.core.util.CharsetUtil; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import jakarta.xml.soap.SOAPException; -import jakarta.xml.soap.SOAPMessage; - /** * SOAP相关单元测试 * @@ -42,17 +39,16 @@ public class SoapClientTest { Console.log(client.getMsgStr(true)); - Console.log(client.send(true)); + Console.log(client.send().getResponseStr(true)); } @Test @Disabled - public void requestForMessageTest() throws SOAPException { + public void requestForMessageTest() { final SoapClient client = SoapClient.of("http://www.webxml.com.cn/WebServices/IpAddressSearchWebService.asmx") .setMethod("web:getCountryCityByIp", "http://WebXml.com.cn/") .setParam("theIpAddress", "218.21.240.106"); - final SOAPMessage message = client.sendForMessage(); - Console.log(message.getSOAPBody().getTextContent()); + Console.log(client.send().getBodyText()); } }