add CookieManager

This commit is contained in:
Looly 2024-09-09 14:27:53 +08:00
parent 56db497a0b
commit 426d3acd9c
16 changed files with 639 additions and 240 deletions

View File

@ -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标签获取编码信息
*

View File

@ -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;
}

View File

@ -60,6 +60,12 @@ public class ClientConfig {
* 是否遇到响应状态码3xx时自动重定向请求
*/
private boolean followRedirects;
/**
* 是否使用引擎默认的Cookie管理器默认为true<br>
* 默认情况下每个客户端维护一个自己的Cookie管理器这个管理器用于在多次请求中记录并自动附带Cookie信息<br>
* 如请求登录后服务器返回Set-Cookie信息Cookie管理器记录之后续请求会自动带上这个Cookie信息从而实现会话保持
*/
private boolean useCookieManager = true;
/**
* 构造
@ -230,4 +236,28 @@ public class ClientConfig {
this.followRedirects = followRedirects;
return this;
}
/**
* 是否使用引擎默认的Cookie管理器默认为true<br>
* 默认情况下每个客户端维护一个自己的Cookie管理器这个管理器用于在多次请求中记录并自动附带Cookie信息<br>
* 如请求登录后服务器返回Set-Cookie信息Cookie管理器记录之后续请求会自动带上这个Cookie信息从而实现会话保持
*
* @return 是否使用引擎默认的Cookie管理器
*/
public boolean isUseCookieManager() {
return useCookieManager;
}
/**
* 是否使用引擎默认的Cookie管理器默认为true<br>
* 默认情况下每个客户端维护一个自己的Cookie管理器这个管理器用于在多次请求中记录并自动附带Cookie信息<br>
* 如请求登录后服务器返回Set-Cookie信息Cookie管理器记录之后续请求会自动带上这个Cookie信息从而实现会话保持
*
* @param useCookieManager 是否使用引擎默认的Cookie管理器
* @return this
*/
public ClientConfig setUseCookieManager(final boolean useCookieManager) {
this.useCookieManager = useCookieManager;
return this;
}
}

View File

@ -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<HttpCookie> 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<String, List<String>> 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信息到本地<br>
* 通过读取
*
* @param conn {@link JdkHttpConnection}
* @param responseHeaders 头信息Map
*/
public static void store(final JdkHttpConnection conn, final Map<String, List<String>> 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());
}
}

View File

@ -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} 子类
*
* <ul>
* <li>HttpClient4和HttpClient5使用{@link org.dromara.hutool.http.client.HttpClientConfig}</li>
* <li>HttpClient4和HttpClient5使用{@link ApacheHttpClientConfig}</li>
* <li>OkHttp使用{@link org.dromara.hutool.http.client.engine.okhttp.OkHttpClientConfig}</li>
* </ul>
* <p>

View File

@ -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} 子类
*
* <ul>
* <li>HttpClient4和HttpClient5使用{@link org.dromara.hutool.http.client.HttpClientConfig}</li>
* <li>HttpClient4和HttpClient5使用{@link ApacheHttpClientConfig}</li>
* <li>OkHttp使用{@link org.dromara.hutool.http.client.engine.okhttp.OkHttpClientConfig}</li>
* </ul>
* <p>

View File

@ -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();

View File

@ -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();

View File

@ -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());
}
/**

View File

@ -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信息包括
*
* <pre>
* 1. 存储Cookie信息到本地
* 2. 从本地获取Cookie信息添加到请求头中
* </pre>
*
* @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<HttpCookie> 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<String, List<String>> 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信息到本地<br>
* 通过读取
*
* @param conn {@link JdkHttpConnection}
* @param responseHeaders 头信息Map
* @return this
*/
public JdkCookieManager saveFromResponse(final JdkHttpConnection conn, final Map<String, List<String>> 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());
}
}

View File

@ -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<String, List<String>> 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<br>
* 如果启用Cookie管理器则返回这个站点相关的所有Cookie信息否则返回当前的响应的Cookie信息
*
* @return Cookie列表
* @since 3.1.1
*/
public List<HttpCookie> 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) {

View File

@ -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<Cookie> loadForRequest(final HttpUrl httpUrl) {
return this.cookieStore.get(httpUrl);
}
@Override
public void saveFromResponse(final HttpUrl httpUrl, final List<Cookie> list) {
this.cookieStore.add(httpUrl, list);
}
}

View File

@ -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<String, ConcurrentHashMap<String, Cookie>> 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<Cookie> cookies) {
for (final Cookie cookie : cookies) {
if (isCookieExpired(cookie)) {
continue;
}
this.add(httpUrl, cookie);
}
}
@Override
public List<Cookie> get(final HttpUrl httpUrl) {
return this.get(httpUrl.host());
}
@Override
public List<Cookie> getCookies() {
final ArrayList<Cookie> result = new ArrayList<>();
for (final String hostKey : this.cookies.keySet()) {
result.addAll(this.get(hostKey));
}
return result;
}
/**
* 获取cookie集合
*/
private List<Cookie> get(final String hostKey) {
final ArrayList<Cookie> result = new ArrayList<>();
if (this.cookies.containsKey(hostKey)) {
final Collection<Cookie> 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<br>
* 格式name + domain
*
* @param cookie cookie
* @return cookie的key
*/
private String cookieName(final Cookie cookie) {
return cookie == null ? null : cookie.name() + cookie.domain();
}
}

View File

@ -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<Cookie> cookies);
/**
* 根据HttpUrl从缓存中读取cookie集合
*
* @param httpUrl HTTP url 地址
* @return cookie集合
*/
List<Cookie> get(HttpUrl httpUrl);
/**
* 获取全部缓存cookie
*
* @return cookie集合
*/
List<Cookie> getCookies();
/**
* 移除指定http url cookie集合
*
* @param httpUrl HTTP url 地址
* @param cookie cookie
* @return 是否移除成功
*/
boolean remove(HttpUrl httpUrl, Cookie cookie);
/**
* 移除所有cookie
*
* @return 是否移除成功
*/
boolean removeAll();
}

View File

@ -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();
}

View File

@ -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());
}
}