add jdk11+ HttpClient support

This commit is contained in:
Looly 2025-08-18 20:38:39 +08:00
parent 10d660fb06
commit ef6fc50fbb
9 changed files with 282 additions and 6 deletions

View File

@ -29,7 +29,7 @@ public class SimpleWrapper<T> implements Wrapper<T> {
/**
* 原始对象
*/
protected final T raw;
protected T raw;
/**
* 构造

View File

@ -66,7 +66,7 @@ public class ClientEngineFactory {
/**
* 创建自定义引擎
*
* @param engineName 引擎名称忽略大小写`HttpClient4``HttpClient5``OkHttp``JdkClient`
* @param engineName 引擎名称忽略大小写`HttpClient4``HttpClient5``OkHttp``JdkClient``Jdk11Client`
* @return 引擎
* @throws HttpException 无对应名称的引擎
*/

View File

@ -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<Registry<ConnectionSocketFactory>> {
@Serial
private static final long serialVersionUID = 1L;
/**

View File

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

View File

@ -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<HttpResponse<InputStream>> implements Response {
/**
* 响应头
*/
private final Map<String, List<String>> headers;
/**
* 响应内容体{@code null} 表示无内容
*/
private final ResponseBody body;
/**
* 构造
*
* @param rawRes 原始响应
* @param message 请求信息
*/
public Jdk11HttpResponse(final HttpResponse<InputStream> 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<String, List<String>> 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
}
}

View File

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

View File

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

View File

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

View File

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