
com.github.joekerouac.common.tools.net.http.IHttpClient Maven / Gradle / Ivy
The newest version!
// Generated by delombok at Fri Mar 14 11:41:38 CST 2025
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
* to You 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 com.github.joekerouac.common.tools.net.http;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import javax.net.ssl.SSLContext;
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder;
import org.apache.hc.client5.http.impl.async.HttpAsyncClients;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.Message;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.nio.AsyncEntityProducer;
import org.apache.hc.core5.http.nio.AsyncRequestProducer;
import org.apache.hc.core5.http.nio.DataStreamChannel;
import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
import org.apache.hc.core5.ssl.SSLContexts;
import com.github.joekerouac.common.tools.collection.CollectionUtil;
import com.github.joekerouac.common.tools.concurrent.FutureCallback;
import com.github.joekerouac.common.tools.concurrent.ResultConvertFuture;
import com.github.joekerouac.common.tools.constant.ExceptionProviderConst;
import com.github.joekerouac.common.tools.io.InMemoryFile;
import com.github.joekerouac.common.tools.log.Logger;
import com.github.joekerouac.common.tools.net.http.config.IHttpClientConfig;
import com.github.joekerouac.common.tools.net.http.config.IHttpConfig;
import com.github.joekerouac.common.tools.net.http.cookie.Cookie;
import com.github.joekerouac.common.tools.net.http.cookie.CookieStore;
import com.github.joekerouac.common.tools.net.http.cookie.impl.CookieStoreImpl;
import com.github.joekerouac.common.tools.net.http.entity.StreamAsyncEntityConsumer;
import com.github.joekerouac.common.tools.net.http.exception.UnknownException;
import com.github.joekerouac.common.tools.net.http.request.IHttpGenericRequest;
import com.github.joekerouac.common.tools.net.http.request.IHttpMethod;
import com.github.joekerouac.common.tools.net.http.request.IHttpPost;
import com.github.joekerouac.common.tools.net.http.request.UploadFile;
import com.github.joekerouac.common.tools.net.http.response.IHttpResponse;
import com.github.joekerouac.common.tools.string.StringUtils;
import com.github.joekerouac.common.tools.util.Assert;
/**
* HttpClient,默认用户代理为火狐
*
* @author JoeKerouac
* @date 2022-10-14 14:37:00
* @since 1.0.0
*/
public final class IHttpClient implements AutoCloseable {
@java.lang.SuppressWarnings("all")
@lombok.Generated
private static final com.github.joekerouac.common.tools.log.Logger LOGGER = com.github.joekerouac.common.tools.log.LoggerFactory.getLogger(IHttpClient.class.getName());
/**
* 回调空实现
*/
private static final FutureCallback EMPTY_CALLBACK = new FutureCallback() {
@Override
public void success(IHttpResponse result) {
}
@Override
public void failed(Throwable ex) {
}
@Override
public void cancelled() {
}
};
private static final Supplier HTTP_ASYNC_CLIENT_BUILDER_SUPPLIER = HttpAsyncClients::custom;
/**
* client ID,用来唯一区分HttpClient
*/
private final String id;
/**
* HttpClient
*/
private final CloseableHttpAsyncClient httpClient;
/**
* cookie store ,存储cookie
*/
private final CookieStore cookieStore;
/**
* 配置
*/
private final IHttpClientConfig config;
/**
* 日志
*/
private final Logger logger;
/**
* 指定client配置和cookieStore
*
* @param config
* client配置
* @param cookieStore
* cookieStore
* @param sslcontext
* sslcontext
* @param logger
* 日志
* @param noRedirect
* 如果为true表示客户端将不会自动重定向
*/
private IHttpClient(IHttpClientConfig config, CookieStore cookieStore, SSLContext sslcontext, boolean noRedirect, Logger logger, Supplier httpAsyncClientBuilderSupplier) {
this.config = config == null ? new IHttpClientConfig() : config;
this.cookieStore = cookieStore == null ? new CookieStoreImpl() : cookieStore;
SSLContext sslContextNotNull = sslcontext == null ? SSLContexts.createSystemDefault() : sslcontext;
this.logger = logger == null ? LOGGER : logger;
Supplier builderSupplier = httpAsyncClientBuilderSupplier == null ? HTTP_ASYNC_CLIENT_BUILDER_SUPPLIER : httpAsyncClientBuilderSupplier;
this.id = String.valueOf(System.currentTimeMillis());
this.httpClient = HttpClientUtil.buildCloseableHttpAsyncClient(this.config, this.cookieStore, sslContextNotNull, noRedirect, builderSupplier);
this.httpClient.start();
}
/**
* 执行HTTP请求
*
* @param request
* 请求体
* @param futureCallback
* 请求回调
* @return 请求结果future
*/
Future execute(AbstractIHttpRequest request, FutureCallback futureCallback) {
Assert.argNotNull(request, "request");
if (LOGGER.isDebugEnabled()) {
logger.debug("要发送的请求为:[{}]", request);
}
FutureCallback callback = futureCallback == null ? EMPTY_CALLBACK : futureCallback;
AsyncRequestProducer requestProducer = buildSimpleHttpRequest(request);
IHttpConfig config = request.getHttpConfig() == null ? this.config.getHttpConfig() : request.getHttpConfig();
BasicResponseConsumer responseConsumer = new BasicResponseConsumer<>(new StreamAsyncEntityConsumer(config.getInitBufferSize(), config.getWriteFileOnLarge(), config.getFilter()));
// 发起请求
Future> future = httpClient.execute(requestProducer, responseConsumer, new org.apache.hc.core5.concurrent.FutureCallback>() {
@Override
public void completed(Message message) {
IHttpResponse response = new IHttpResponse(message);
try {
callback.success(response);
} catch (Throwable throwable) {
LOGGER.warn(throwable, "异步回调[success]异常");
}
callback.complete(response, null, 0);
}
@Override
public void failed(Exception ex) {
try {
callback.failed(ex);
} catch (Throwable throwable) {
LOGGER.warn(throwable, "异步回调[failed]异常");
}
callback.complete(null, ex, 1);
}
@Override
public void cancelled() {
try {
callback.cancelled();
} catch (Throwable throwable) {
LOGGER.warn(throwable, "异步回调[failed]异常");
}
callback.complete(null, null, 2);
}
});
return new ResultConvertFuture<>(future, IHttpResponse::new);
}
public CookieStore getCookieManager() {
return this.cookieStore;
}
/**
* 获取所有cookie
*
* @return 返回cookie列表
*/
public List getCookies() {
return cookieStore.getCookies();
}
/**
* 获取指定cookie
*
* @param name
* cookie名
* @return cookie存在时返回cookie,不存在时返回null
*/
public Cookie getCookie(String name) {
List cookies = cookieStore.getCookies();
for (Cookie cookie : cookies) {
if (cookie.getName().equals(name)) {
return cookie;
}
}
return null;
}
/**
* 添加cookie
*
* @param cookie
* 要添加的cookie
*/
public void addCookie(Cookie cookie) {
cookieStore.addCookie(cookie);
}
/**
* 获取httpClient的ID
*
* @return client的ID
*/
public String getId() {
return this.id;
}
@Override
public void close() throws IOException {
httpClient.close();
}
private AsyncRequestProducer buildSimpleHttpRequest(AbstractIHttpRequest request) {
String method = request.getMethod();
HttpUriRequestBase httpRequest = new HttpUriRequestBase(method, URI.create(request.getUrl()));
// 请求通用配置
IHttpConfig config = request.getHttpConfig() == null ? this.config.getHttpConfig() : request.getHttpConfig();
httpRequest.setConfig(buildRequestConfig(config));
request.getHeaders().forEach(httpRequest::addHeader);
httpRequest.addHeader("Content-Type", request.getContentType());
List files = null;
String body = null;
// 如果是post请求,则需要设置请求体
if (request instanceof IHttpPost) {
IHttpPost httpPost = (IHttpPost) request;
files = httpPost.getFiles();
body = httpPost.getBody();
} else if (request instanceof IHttpGenericRequest) {
if (Objects.equals(method, IHttpMethod.POST.name()) || Objects.equals(method, IHttpMethod.PUT.name()) || Objects.equals(method, IHttpMethod.PATCH.name())) {
IHttpGenericRequest genericRequest = (IHttpGenericRequest) request;
files = genericRequest.getFiles();
body = genericRequest.getBody();
}
}
if (CollectionUtil.isNotEmpty(files)) {
// 如果要上传的文件不为空,则使用MultipartEntity
MultipartEntityBuilder entityBuilder = MultipartEntityBuilder.create().setCharset(Charset.forName(request.getCharset()));
files.forEach(file -> entityBuilder.addBinaryBody(file.getName(), file.getFile(), ContentType.create(file.getContentType(), StringUtils.getOrDefault(file.getCharset(), Charset.defaultCharset().name())), file.getFileName()));
// 将body解析到form表单
if (StringUtils.isNotBlank(body)) {
final String[] split = body.split("&");
String finalBody = body;
Arrays.stream(split).forEach(kv -> {
final String[] kvArr = kv.split("=");
Assert.assertTrue(kvArr.length == 2, StringUtils.format("当前上传文件不为空,body必须是form表单,当前body不符合要求:[{}]", finalBody), ExceptionProviderConst.IllegalArgumentExceptionProvider);
entityBuilder.addTextBody(kvArr[0], kvArr[1]);
});
}
httpRequest.setEntity(entityBuilder.build());
} else if (StringUtils.isNotBlank(body)) {
// 使用普通StringEntity
httpRequest.setEntity(new StringEntity(body, ContentType.create(request.getContentType(), request.getCharset())));
}
final ByteBuffer buffer;
final HttpEntity entity = httpRequest.getEntity();
if (entity != null) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
try {
entity.writeTo(stream);
} catch (IOException e) {
throw new UnknownException("请求体读取失败,可能是上传文件的文件流读取失败", e);
}
final byte[] data = stream.toByteArray();
buffer = ByteBuffer.wrap(data);
} else {
buffer = null;
}
return new BasicRequestProducer(httpRequest, entity == null ? null : new AsyncEntityProducer() {
@Override
public boolean isRepeatable() {
return entity.isRepeatable();
}
@Override
public void failed(final Exception cause) {
}
@Override
public long getContentLength() {
return entity.getContentLength();
}
@Override
public String getContentType() {
return entity.getContentType();
}
@Override
public String getContentEncoding() {
return entity.getContentEncoding();
}
@Override
public boolean isChunked() {
return entity.isChunked();
}
@Override
public Set getTrailerNames() {
return entity.getTrailerNames();
}
@Override
public int available() {
return Integer.MAX_VALUE;
}
@Override
public void produce(final DataStreamChannel channel) throws IOException {
channel.write(buffer);
// 写出完毕
if (buffer.remaining() <= 0) {
channel.endStream();
}
}
@Override
public void releaseResources() {
buffer.flip();
}
});
}
/**
* 构建HttpClient请求配置
*
* @param config
* 请求配置
* @return requestConfig
*/
private RequestConfig buildRequestConfig(IHttpConfig config) {
logger.debug("构建请求配置");
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(config.getConnectTimeout(), TimeUnit.MILLISECONDS).setResponseTimeout(config.getSocketTimeout(), TimeUnit.MILLISECONDS).setConnectionRequestTimeout(config.getConnectionRequestTimeout(), TimeUnit.MILLISECONDS).build();
logger.debug("请求配置为:[{}]", requestConfig);
return requestConfig;
}
@java.lang.SuppressWarnings("all")
@lombok.Generated
public static class IHttpClientBuilder {
@java.lang.SuppressWarnings("all")
@lombok.Generated
private IHttpClientConfig config;
@java.lang.SuppressWarnings("all")
@lombok.Generated
private CookieStore cookieStore;
@java.lang.SuppressWarnings("all")
@lombok.Generated
private SSLContext sslcontext;
@java.lang.SuppressWarnings("all")
@lombok.Generated
private boolean noRedirect;
@java.lang.SuppressWarnings("all")
@lombok.Generated
private Logger logger;
@java.lang.SuppressWarnings("all")
@lombok.Generated
private Supplier httpAsyncClientBuilderSupplier;
@java.lang.SuppressWarnings("all")
@lombok.Generated
IHttpClientBuilder() {
}
@java.lang.SuppressWarnings("all")
@lombok.Generated
public IHttpClient.IHttpClientBuilder config(final IHttpClientConfig config) {
this.config = config;
return this;
}
@java.lang.SuppressWarnings("all")
@lombok.Generated
public IHttpClient.IHttpClientBuilder cookieStore(final CookieStore cookieStore) {
this.cookieStore = cookieStore;
return this;
}
@java.lang.SuppressWarnings("all")
@lombok.Generated
public IHttpClient.IHttpClientBuilder sslcontext(final SSLContext sslcontext) {
this.sslcontext = sslcontext;
return this;
}
@java.lang.SuppressWarnings("all")
@lombok.Generated
public IHttpClient.IHttpClientBuilder noRedirect(final boolean noRedirect) {
this.noRedirect = noRedirect;
return this;
}
@java.lang.SuppressWarnings("all")
@lombok.Generated
public IHttpClient.IHttpClientBuilder logger(final Logger logger) {
this.logger = logger;
return this;
}
@java.lang.SuppressWarnings("all")
@lombok.Generated
public IHttpClient.IHttpClientBuilder httpAsyncClientBuilderSupplier(final Supplier httpAsyncClientBuilderSupplier) {
this.httpAsyncClientBuilderSupplier = httpAsyncClientBuilderSupplier;
return this;
}
@java.lang.SuppressWarnings("all")
@lombok.Generated
public IHttpClient build() {
return new IHttpClient(this.config, this.cookieStore, this.sslcontext, this.noRedirect, this.logger, this.httpAsyncClientBuilderSupplier);
}
@java.lang.Override
@java.lang.SuppressWarnings("all")
@lombok.Generated
public java.lang.String toString() {
return "IHttpClient.IHttpClientBuilder(config=" + this.config + ", cookieStore=" + this.cookieStore + ", sslcontext=" + this.sslcontext + ", noRedirect=" + this.noRedirect + ", logger=" + this.logger + ", httpAsyncClientBuilderSupplier=" + this.httpAsyncClientBuilderSupplier + ")";
}
}
@java.lang.SuppressWarnings("all")
@lombok.Generated
public static IHttpClient.IHttpClientBuilder builder() {
return new IHttpClient.IHttpClientBuilder();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy