diff --git a/hutool-core/src/main/java/cn/hutool/v7/core/lang/wrapper/SimpleWrapper.java b/hutool-core/src/main/java/cn/hutool/v7/core/lang/wrapper/SimpleWrapper.java index 3d8fd52a3..cf979ed30 100644 --- a/hutool-core/src/main/java/cn/hutool/v7/core/lang/wrapper/SimpleWrapper.java +++ b/hutool-core/src/main/java/cn/hutool/v7/core/lang/wrapper/SimpleWrapper.java @@ -29,7 +29,7 @@ public class SimpleWrapper implements Wrapper { /** * 原始对象 */ - protected final T raw; + protected T raw; /** * 构造 diff --git a/hutool-http/src/main/java/cn/hutool/v7/http/client/engine/ClientEngineFactory.java b/hutool-http/src/main/java/cn/hutool/v7/http/client/engine/ClientEngineFactory.java index c4da8f150..69f95d085 100644 --- a/hutool-http/src/main/java/cn/hutool/v7/http/client/engine/ClientEngineFactory.java +++ b/hutool-http/src/main/java/cn/hutool/v7/http/client/engine/ClientEngineFactory.java @@ -66,7 +66,7 @@ public class ClientEngineFactory { /** * 创建自定义引擎 * - * @param engineName 引擎名称,忽略大小写,如`HttpClient4`、`HttpClient5`、`OkHttp`、`JdkClient` + * @param engineName 引擎名称,忽略大小写,如`HttpClient4`、`HttpClient5`、`OkHttp`、`JdkClient`、`Jdk11Client` * @return 引擎 * @throws HttpException 无对应名称的引擎 */ diff --git a/hutool-http/src/main/java/cn/hutool/v7/http/client/engine/httpclient4/ConnectionSocketFactoryRegistryBuilder.java b/hutool-http/src/main/java/cn/hutool/v7/http/client/engine/httpclient4/ConnectionSocketFactoryRegistryBuilder.java index 8a44224a0..e473cabad 100644 --- a/hutool-http/src/main/java/cn/hutool/v7/http/client/engine/httpclient4/ConnectionSocketFactoryRegistryBuilder.java +++ b/hutool-http/src/main/java/cn/hutool/v7/http/client/engine/httpclient4/ConnectionSocketFactoryRegistryBuilder.java @@ -24,6 +24,8 @@ import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import cn.hutool.v7.core.lang.builder.Builder; import cn.hutool.v7.http.ssl.SSLInfo; +import java.io.Serial; + /** * HttpClient4连接工厂注册器构建器 * @@ -31,6 +33,7 @@ import cn.hutool.v7.http.ssl.SSLInfo; * @since 6.0.0 */ public class ConnectionSocketFactoryRegistryBuilder implements Builder> { + @Serial private static final long serialVersionUID = 1L; /** diff --git a/hutool-http/src/main/java/cn/hutool/v7/http/client/engine/jdk11/Jdk11ClientEngine.java b/hutool-http/src/main/java/cn/hutool/v7/http/client/engine/jdk11/Jdk11ClientEngine.java new file mode 100644 index 000000000..f5164b2b3 --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/v7/http/client/engine/jdk11/Jdk11ClientEngine.java @@ -0,0 +1,126 @@ +package cn.hutool.v7.http.client.engine.jdk11; + +import cn.hutool.v7.http.HttpException; +import cn.hutool.v7.http.client.ClientConfig; +import cn.hutool.v7.http.client.Request; +import cn.hutool.v7.http.client.Response; +import cn.hutool.v7.http.client.cookie.InMemoryCookieStore; +import cn.hutool.v7.http.client.engine.AbstractClientEngine; +import cn.hutool.v7.http.client.engine.jdk.JdkCookieManager; +import cn.hutool.v7.http.proxy.ProxyInfo; +import cn.hutool.v7.http.ssl.SSLInfo; + +import java.io.IOException; +import java.io.InputStream; +import java.net.ProxySelector; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; + +/** + * JDK11 HttpClient引擎实现 + * + * @author looly + * @since 7.0.0 + */ +public class Jdk11ClientEngine extends AbstractClientEngine { + + /** + * HttpClient + */ + private HttpClient client; + /** + * Cookie管理 + */ + private JdkCookieManager cookieManager; + + /** + * 获取Cookie管理器 + * + * @return Cookie管理器 + */ + public JdkCookieManager getCookieManager() { + return this.cookieManager; + } + + @Override + public Response send(final Request message) { + initEngine(); + + final HttpRequest.Builder builder = HttpRequest.newBuilder() + .uri(message.url().toURI()) + .method(message.method().name(), null == message.body() ? + HttpRequest.BodyPublishers.noBody() : + HttpRequest.BodyPublishers.ofInputStream(message::bodyStream)); + + // Read超时 + if(null != this.config){ + builder.timeout(Duration.ofMillis(this.config.getReadTimeout())); + } + + // 自定义Headers + message.headers().forEach((k, v1) -> v1.forEach(v2 -> builder.header(k, v2))); + + final HttpResponse response; + try { + response = this.client.send(builder.build(), HttpResponse.BodyHandlers.ofInputStream()); + } catch (final Exception e) { + throw new HttpException(e); + } + + return new Jdk11HttpResponse(response, message); + } + + @Override + public Object getRawEngine() { + return this.client; + } + + @Override + public void close() throws IOException { + // 关闭Cookie管理器 + this.cookieManager = null; + } + + @Override + protected void reset() { + // do nothing + } + + @Override + protected void initEngine() { + if (null != this.client) { + return; + } + + final HttpClient.Builder builder = HttpClient.newBuilder() + .version(HttpClient.Version.HTTP_1_1); + + final ClientConfig config = this.config; + if(null != config){ + builder.connectTimeout(Duration.ofMillis(config.getConnectionTimeout())); + + // SSL + final SSLInfo sslInfo = config.getSslInfo(); + if(null != sslInfo){ + builder.sslContext(sslInfo.getSslContext()); + } + + // Cookie + if (config.isUseCookieManager()) { + this.cookieStore = new InMemoryCookieStore(); + this.cookieManager = new JdkCookieManager(this.cookieStore); + builder.cookieHandler(this.cookieManager.getCookieManager()); + } + + // Proxy + final ProxyInfo proxy = config.getProxy(); + if (null != proxy) { + builder.proxy(proxy.getProxySelector()); + } + } + + this.client = builder.build(); + } +} diff --git a/hutool-http/src/main/java/cn/hutool/v7/http/client/engine/jdk11/Jdk11HttpResponse.java b/hutool-http/src/main/java/cn/hutool/v7/http/client/engine/jdk11/Jdk11HttpResponse.java new file mode 100644 index 000000000..8b159c810 --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/v7/http/client/engine/jdk11/Jdk11HttpResponse.java @@ -0,0 +1,87 @@ +package cn.hutool.v7.http.client.engine.jdk11; + +import cn.hutool.v7.core.lang.wrapper.SimpleWrapper; +import cn.hutool.v7.http.GlobalCompressStreamRegister; +import cn.hutool.v7.http.HttpUtil; +import cn.hutool.v7.http.client.Request; +import cn.hutool.v7.http.client.Response; +import cn.hutool.v7.http.client.body.ResponseBody; +import cn.hutool.v7.http.meta.HeaderName; + +import java.io.IOException; +import java.io.InputStream; +import java.net.http.HttpResponse; +import java.util.List; +import java.util.Map; + +/** + * JDK11 Http响应包装类 + * + * @author looly + * @since 7.0.0 + */ +public class Jdk11HttpResponse extends SimpleWrapper> implements Response { + + /** + * 响应头 + */ + private final Map> headers; + /** + * 响应内容体,{@code null} 表示无内容 + */ + private final ResponseBody body; + + /** + * 构造 + * + * @param rawRes 原始响应 + * @param message 请求信息 + */ + public Jdk11HttpResponse(final HttpResponse rawRes, final Request message) { + super(rawRes); + this.headers = this.raw.headers().map(); + this.body = message.method().isIgnoreBody() ? null : new ResponseBody(this, bodyStream()); + } + + @Override + public int getStatus() { + return this.raw.statusCode(); + } + + @Override + public String header(final String name) { + return HttpUtil.header(this.headers, name); + } + + @Override + public Map> headers() { + return this.headers; + } + + @Override + public InputStream bodyStream() { + // 直接取得解压后的原始流 + return GlobalCompressStreamRegister.INSTANCE.wrapStream(this.raw.body(), + header(HeaderName.CONTENT_ENCODING.getValue())); + } + + @Override + public Response sync() { + final ResponseBody body = this.body; + if(null != body){ + body.sync(); + } + this.raw = null; + return this; + } + + @Override + public ResponseBody body() { + return this.body; + } + + @Override + public void close() throws IOException { + // ignore + } +} diff --git a/hutool-http/src/main/java/cn/hutool/v7/http/client/engine/jdk11/package-info.java b/hutool-http/src/main/java/cn/hutool/v7/http/client/engine/jdk11/package-info.java new file mode 100644 index 000000000..d1033406d --- /dev/null +++ b/hutool-http/src/main/java/cn/hutool/v7/http/client/engine/jdk11/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2013-2025 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. + */ + +/** + * 基于JDK17+的HttpClient 封装的HTTP客户端 + * + * @author Looly + * @since 7.0.0 + */ +package cn.hutool.v7.http.client.engine.jdk11; diff --git a/hutool-http/src/main/java/cn/hutool/v7/http/client/engine/okhttp/OkHttpResponse.java b/hutool-http/src/main/java/cn/hutool/v7/http/client/engine/okhttp/OkHttpResponse.java index 7e974cb82..fe4f87ca8 100644 --- a/hutool-http/src/main/java/cn/hutool/v7/http/client/engine/okhttp/OkHttpResponse.java +++ b/hutool-http/src/main/java/cn/hutool/v7/http/client/engine/okhttp/OkHttpResponse.java @@ -78,10 +78,6 @@ public class OkHttpResponse implements Response { @Override public InputStream bodyStream() { final okhttp3.ResponseBody body = rawRes.body(); - if(null == body){ - return EmptyInputStream.INSTANCE; - } - return GlobalCompressStreamRegister.INSTANCE.wrapStream(body.byteStream(), this.rawRes.header(HeaderName.CONTENT_ENCODING.getValue())); } diff --git a/hutool-http/src/main/resources/META-INF/services/cn.hutool.v7.http.client.engine.ClientEngine b/hutool-http/src/main/resources/META-INF/services/cn.hutool.v7.http.client.engine.ClientEngine index bd1b8b2b4..20a7b92b8 100644 --- a/hutool-http/src/main/resources/META-INF/services/cn.hutool.v7.http.client.engine.ClientEngine +++ b/hutool-http/src/main/resources/META-INF/services/cn.hutool.v7.http.client.engine.ClientEngine @@ -18,3 +18,4 @@ cn.hutool.v7.http.client.engine.httpclient5.HttpClient5Engine cn.hutool.v7.http.client.engine.httpclient4.HttpClient4Engine cn.hutool.v7.http.client.engine.okhttp.OkHttpEngine cn.hutool.v7.http.client.engine.jdk.JdkClientEngine +cn.hutool.v7.http.client.engine.jdk11.Jdk11ClientEngine diff --git a/hutool-http/src/test/java/cn/hutool/v7/http/client/Jdk11HttpClientEngineTest.java b/hutool-http/src/test/java/cn/hutool/v7/http/client/Jdk11HttpClientEngineTest.java new file mode 100644 index 000000000..a3474dc01 --- /dev/null +++ b/hutool-http/src/test/java/cn/hutool/v7/http/client/Jdk11HttpClientEngineTest.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2013-2025 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 cn.hutool.v7.http.client; + +import cn.hutool.v7.core.lang.Console; +import cn.hutool.v7.http.HttpUtil; +import cn.hutool.v7.http.client.engine.ClientEngine; +import cn.hutool.v7.http.meta.Method; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +public class Jdk11HttpClientEngineTest { + + @SuppressWarnings("resource") + @Test + @Disabled + public void getTest() { + final ClientEngine engine = HttpUtil.createClient("jdk11Client"); + + final Request req = Request.of("https://www.hutool.cn/").method(Method.GET); + final Response res = engine.send(req); + + Console.log(res.getStatus()); + Console.log(res.bodyStr()); + } +}