1. 工具介绍&请求示例
1.1 工具介绍
由于平时开发中习惯使用postman进行接口测试,在接触后端直接请求时,市面上未找到符合使用习惯的请求工具,故此在apache http请求工具的基础上做了一些封装,能够覆盖日常物联网开发中的大部分使用场景,支持多种身份认证、请求连接管理、多种响应接收等。
1.2 请求示例
1.2.1 GET请求
EasyHttp easyHttp = EasyHttp.builder();
AjaxResult ajaxResult = easyHttp.setUrl("127.0.0.1:9402/company")
.setParam("companyName","测试")
.GET()
.getJsonBody(AjaxResult.class);
System.out.println(ajaxResult);
1.2.2 DELETE请求
EasyHttp easyHttp = EasyHttp.builder();
AjaxResult ajaxResult = easyHttp.setUrl("127.0.0.1:9402/company/2")
.setHeader("Authorization","Bearer eyJhbGciOiJIUzUxMiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VyX2tleSI6ImUwNGViMzc3LTE3MTMtNDg3MC1hM2MxLTliMTI4OGEyYTEyOSIsInVzZXJuYW1lIjoiYWRtaW4ifQ.ph8c8c4QZ0fuNlqACX-OpoObWX2cqIdioFhlDZuY4sbKN6aro-9P_FnllXg2rwZjUR6j9dn-mMBhbIYmy0zLbg")
.DELETE()
.getJsonBody(AjaxResult.class);
System.out.println(ajaxResult);
1.2.3 PUT请求
ToolsCompany toolsCompany = new ToolsCompany();
toolsCompany.setId(1L);
toolsCompany.setCompanyCode("cc");
EasyHttp easyHttp = EasyHttp.builder();
AjaxResult ajaxResult = easyHttp.setUrl("127.0.0.1:9402/company")
.setHeader("Authorization","Bearer eyJhbGciOiJIUzUxMiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VyX2tleSI6ImUwNGViMzc3LTE3MTMtNDg3MC1hM2MxLTliMTI4OGEyYTEyOSIsInVzZXJuYW1lIjoiYWRtaW4ifQ.ph8c8c4QZ0fuNlqACX-OpoObWX2cqIdioFhlDZuY4sbKN6aro-9P_FnllXg2rwZjUR6j9dn-mMBhbIYmy0zLbg")
.setBody(JSONUtil.toJsonStr(toolsCompany),ContentType.APPLICATION_JSON)
.PUT()
.getJsonBody(AjaxResult.class);
System.out.println(ajaxResult);
1.2.4 POST请求
ToolsCompany company = new ToolsCompany();
company.setCompanyName("测试公司");
company.setCompanyCode("aaa");
company.setStatus("1");
company.setCreateTime(new Date());
EasyHttp easyHttp = EasyHttp.builder();
AjaxResult ajaxResult = easyHttp.setUrl("127.0.0.1:9402/company")
.setBody(JSONUtil.toJsonStr(company), ContentType.APPLICATION_JSON)
.setHeader("Authorization","Bearer eyJhbGciOiJIUzUxMiJ9.eyJ1c2VyxxxxJ1c2VyX2tleSI6ImUwNGViMzc3LTE3MTMtNDg3MC1hM2MxLTliMTI4OGEyYTEyOSIsInVzZXJuYW1lIjoiYWRtaW4ifQ.ph8c8c4QZ0fuNlqACX-OpoObWX2cqIdioFhlDZuY4sbKN6aro-9P_FnllXg2rwZjUR6j9dn-mMBhbIYmy0zLbg")
.POST()
.getJsonBody(AjaxResult.class);
System.out.println(ajaxResult);
2. 相关依赖及源码
2.1 maven依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
</dependency>
<dependency>
<groupId>javax.mail</groupId>
<artifactId>javax.mail-api</artifactId>
<version>1.6.2</version>
</dependency>
2.2 核心工具类 EasyHttp
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONUtil;
import com.middlewares.common.http.constant.Method;
import com.middlewares.common.http.constant.Protocol;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.*;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import javax.mail.internet.MimeUtility;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* 这些方法都是用于创建CloseableHttpClient对象的工厂方法,但它们在创建HttpClient实例时使用了不同的配置选项,因此具有不同的行为和特点。
* <p>
* 1. `createDefault()`: 这个方法创建了一个默认的CloseableHttpClient实例。它使用了默认的HttpClientBuilder配置,包括连接池管理器、请求重试机制、连接超时时间、连接保持时间等。这是创建HttpClient的常用方法。
* <p>
* 2. `createSystem()`: 这个方法也创建了一个默认的CloseableHttpClient实例,但它使用了系统属性来配置HttpClient。例如,它会使用系统属性来设置代理服务器、SSL证书、连接池大小等。这个方法适用于在系统级别配置了HttpClient的情况下使用。
* <p>
* 3. `createMinimal()`: 这个方法创建了一个最小化的CloseableHttpClient实例。它只包含了最基本的配置,没有连接池管理器和请求重试机制。这个方法适用于简单的、不需要复杂配置的场景。
* <p>
* 4. `createMinimal(HttpClientConnectionManager connManager)`: 这个方法也创建了一个最小化的CloseableHttpClient实例,但它使用了自定义的连接管理器。你可以传入一个自定义的HttpClientConnectionManager对象,用于管理连接池和连接的创建与释放。
* <p>
* 根据你的需求和场景,你可以选择适合的方法来创建CloseableHttpClient对象。如果你需要更多的配置选项,可以使用HttpClientBuilder类的其他方法来自定义HttpClient的行为。
*/
/**
* http 请求工具类
* 参数位置 路径 params
*
* @author shawn
* @date 2023年 09月 22日 23:56 23:56:27
*/
public class EasyHttp {
private static CloseableHttpClient httpClient = HttpClients.createDefault();
private final List<NameValuePair> params = new ArrayList<>();
private final List<Header> headers = new ArrayList<>();
/**
* 表单实体
*/
private MultipartEntityBuilder builder;
// 创建请求配置
private RequestConfig requestConfig;
/**
* todo 待使用
*/
private String charSet;
private Protocol defaultProtocol = Protocol.HTTP;
/**
* 请求体
*/
private HttpEntity httpEntity;
/**
* 响应
*/
private CloseableHttpResponse response;
private String url;
public EasyHttp(RequestConfig requestConfig) {
this.requestConfig = requestConfig;
}
/**
* 请求连接管理
*
* @param connectTimeout 连接超时时间
* @param RequestTimeout 请求超时时间
* @param responseTimeout 响应超时时间
* @param unit 时间单位
*/
public EasyHttp(Integer connectTimeout, Integer RequestTimeout, Integer responseTimeout, TimeUnit unit) {
this.requestConfig = RequestConfig.custom()
.setConnectTimeout((int) unit.toMillis(connectTimeout)) // 设置连接超时时间为5秒
.setConnectionRequestTimeout((int) unit.toMillis(RequestTimeout)) // 设置请求超时时间为5秒
.setSocketTimeout((int) unit.toMillis(responseTimeout)) // 设置响应超时时间为5秒
.build();
}
public EasyHttp() {
}
/**
* 建造者
*
* @return {@link EasyHttp}
*/
public static EasyHttp builder() {
return new EasyHttp();
}
public static EasyHttp builder(RequestConfig requestConfig) {
return new EasyHttp(requestConfig);
}
public static EasyHttp builder(Integer connectTimeout, Integer RequestTimeout, Integer responseTimeout, TimeUnit unit) {
return new EasyHttp(connectTimeout, RequestTimeout, responseTimeout, unit);
}
private RequestConfig getRequestConfig() {
if (requestConfig == null) {
requestConfig = RequestConfig.custom()
.setConnectTimeout(5000) // 设置连接超时时间为5秒
.setConnectionRequestTimeout(5000) // 设置请求超时时间为5秒
.setSocketTimeout(5000) // 设置响应超时时间为5秒
.build();
}
return requestConfig;
}
/**
* 设置用户digest加密验证信息
*
* @param username
* @param password
* @return {@link EasyHttp}
*/
public EasyHttp setDigestAuth(String username, String password) {
// 创建CredentialsProvider对象,用于提供用户名和密码
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY,
new UsernamePasswordCredentials(username, password));
// 创建HttpClient对象,并设置CredentialsProvider
httpClient = HttpClients.custom()
.setDefaultCredentialsProvider(credentialsProvider)
.build();
return this;
}
/**
* 1. 设定地址
*
* @param url
* @return {@link EasyHttp}
*/
public EasyHttp setUrl(String url) {
this.url = url.contains("?") ? url.substring(0, url.indexOf("?")) : url;
List<NameValuePair> pairs = urlParamsParse(url);
this.params.addAll(pairs);
return this;
}
private List<NameValuePair> urlParamsParse(String url) {
List<NameValuePair> params = new ArrayList<>();
if (url.contains("?")) {
String[] items = url.substring(url.indexOf("?") + 1).split("&");
for (String item : items) {
params.add(new BasicNameValuePair(item.split("=")[0], item.split("=")[1]));
}
}
return params;
}
/**
* 2. 选择协议
*
* @param protocol HTTP OR HTTPS
* @return {@link EasyHttp}
*/
public EasyHttp setProtocol(Protocol protocol) {
this.defaultProtocol = protocol;
return this;
}
/**
* 3. params传递参数
*
* @param key
* @param value
* @return {@link EasyHttp}
*/
public EasyHttp setParam(String key, String value) {
if (key != null && !"".equals(key) && value != null && !value.equals("")) {
this.params.add(new BasicNameValuePair(key, value));
}
return this;
}
/**
* 3. params传递参数
*
* @param key
* @param value
* @return {@link EasyHttp}
*/
public EasyHttp setParam(String key, Integer value) {
if (key != null && !"".equals(key)) {
setParam(key, Integer.toString(value));
}
return this;
}
/**
* 3. params传递参数
*
* @param params
* @return {@link EasyHttp}
*/
public EasyHttp setParams(Map<String, Object> params) {
if (!params.isEmpty()) {
params.forEach((k, v) -> setParam(k, v.toString()));
}
return this;
}
/**
* 4. header 传递参数
*
* @param key
* @param value
* @return {@link EasyHttp}
*/
public EasyHttp setHeader(String key, String value) {
if (key != null && !"".equals(key)) {
this.headers.add(new BasicHeader(key, value));
}
return this;
}
/**
* 4. header 传递参数
*
* @param headers
* @return {@link EasyHttp}
*/
public EasyHttp setHeaders(Map<String, Object> headers) {
if (!headers.isEmpty()) {
headers.forEach((k, v) -> {
this.headers.add(new BasicHeader(k, v.toString()));
});
}
return this;
}
/**
* 5. 表单数据
*
* @param key
* @param o
* @return {@link EasyHttp}
*/
public EasyHttp setForm(String key, Object o, ContentType contentType) {
if (builder == null) {
builder = MultipartEntityBuilder.create().setCharset(StandardCharsets.UTF_8);
}
if (o instanceof String) {
builder.addPart(key, new StringBody((String) o, ContentType.TEXT_PLAIN));
return this;
} else if (o instanceof File) {
File file = (File) o;
try {
String encodedFileName = MimeUtility.encodeText(file.getName(), "UTF-8", "B");
FileBody fileBody = new FileBody(file, ContentType.create(contentTypeTransform(contentType), StandardCharsets.UTF_8), encodedFileName);
builder.addPart(key, fileBody);
return this;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
throw new RuntimeException("文件名编码失败");
}
} else {
throw new RuntimeException("未知表单类型:" + o.getClass());
}
}
/**
* 类型转换
* application/json:用于传输JSON数据格式的数据。
* <p>
* application/x-www-form-urlencoded:用于传输表单数据,数据会被编码为key-value形式。
* <p>
* multipart/form-data:用于传输文件以及表单数据,数据会被分割成多个部分进行传输。
* <p>
* text/plain:用于传输纯文本数据。
* <p>
* application/xml:用于传输XML数据格式的数据。
* <p>
* image/jpeg:用于传输JPEG格式的图片数据。
* <p>
* application/octet-stream:用于传输二进制数据,如文件下载。
*
* @param contentType
* @return {@link String}
*/
private String contentTypeTransform(ContentType contentType) {
if (ContentType.APPLICATION_JSON.equals(contentType)) {
return "application/json";
} else if (ContentType.APPLICATION_XML.equals(contentType)) {
return "application/xml";
} else if (ContentType.TEXT_XML.equals(contentType)) {
return "text/xml";
} else if (ContentType.TEXT_PLAIN.equals(contentType)) {
return "text/plain";
} else if (ContentType.APPLICATION_FORM_URLENCODED.equals(contentType)) {
return "application/x-www-form-urlencoded";
} else if (ContentType.APPLICATION_OCTET_STREAM.equals(contentType)) {
return "application/octet-stream";
} else if (ContentType.IMAGE_JPEG.equals(contentType)) {
return "image/jpeg";
} else if (ContentType.IMAGE_PNG.equals(contentType)) {
return "image/png";
} else {
throw new RuntimeException("未定义的请求类型!");
}
}
/**
* 6. 请求体传递参数 与setForms 互斥
*
* @param value
* @param contentType
* @return {@link EasyHttp}
*/
public EasyHttp setBody(String value, ContentType contentType) {
httpEntity = new StringEntity(value, contentType);
setHeader("Content-Type", contentTypeTransform(contentType));
return this;
}
/**
* 方法转换
*
* @param method
* @return {@link HttpRequestBase}
*/
public HttpRequestBase methodTransform(Method method) {
//设定请求协议
if ((!this.url.contains(Protocol.HTTP.getProtocol())) && (!this.url.contains(Protocol.HTTPS.getProtocol()))) {
this.url = defaultProtocol.getProtocol() + this.url;
}
// 添加params参数
if (!this.params.isEmpty()) {
// 将参数编码为URL编码格式
String paramString = URLEncodedUtils.format(this.params, StandardCharsets.UTF_8);
this.url += "?" + paramString;
}
switch (method) {
case GET: {
return new HttpGet(this.url);
}
case POST: {
return new HttpPost(this.url);
}
case PUT: {
return new HttpPut(this.url);
}
case DELETE: {
return new HttpDelete(this.url);
}
default:
throw new RuntimeException("未知请求方法:" + method.getMethod());
}
}
/**
* GET请求
*
* @throws IOException
*/
public EasyHttp GET() throws IOException {
HttpGet request = (HttpGet) methodTransform(Method.GET);
// 将参数添加到请求中
execute(request);
return this;
}
/**
* POST请求
*
* @throws IOException
*/
public EasyHttp POST() throws IOException {
HttpPost request = (HttpPost) methodTransform(Method.POST);
// 将参数添加到请求中
if (builder != null) {
request.setHeader("Content-Type", "multipart/form-data");
request.setEntity(builder.build());
}
if (httpEntity != null) {
request.setEntity(httpEntity);
}
// 请求体数据
execute(request);
return this;
}
/**
* PUT请求
*
* @throws IOException
*/
public EasyHttp PUT() throws IOException {
HttpPut request = (HttpPut) methodTransform(Method.PUT);
// 将参数添加到请求中
if (builder != null) {
request.setHeader("Content-Type", "multipart/form-data");
request.setEntity(builder.build());
}
if (httpEntity != null) {
request.setEntity(httpEntity);
}
// 请求体数据
execute(request);
return this;
}
/**
* DELETE请求
*
* @throws IOException
*/
public EasyHttp DELETE() throws IOException {
HttpDelete request = (HttpDelete) methodTransform(Method.DELETE);
// 请求体数据
execute(request);
return this;
}
/**
* 获取指定编码字符串响应
*
* @param charSet
* @return {@link String}
* @throws IOException
*/
public String getStringBody(String charSet) throws IOException {
if (response == null) {
throw new RuntimeException("请在发送请求后调用该方法!");
}
HttpEntity entity = response.getEntity();
String data = EntityUtils.toString(entity, charSet);
closeConnection();
return data;
}
/**
* 获取字符串响应
*
* @return {@link String}
* @throws IOException
*/
public String getStringBody() throws IOException {
if (response == null) {
throw new RuntimeException("请在发送请求后调用该方法!");
}
HttpEntity entity = response.getEntity();
String data = EntityUtils.toString(entity);
closeConnection();
return data;
}
/**
* 获取Json对象响应
*
* @param objectClass
* @return {@link T}
* @throws IOException
*/
public <T> T getJsonBody(Class<T> objectClass) throws IOException {
return JSONUtil.toBean(getStringBody(), objectClass);
}
/**
* 获取指定编码Json对象响应
*
* @param objectClass
* @return {@link T}
* @throws IOException
*/
public <T> T getJsonBody(String charSet, Class<T> objectClass) throws IOException {
return JSONUtil.toBean(getStringBody(charSet), objectClass);
}
/**
* 获取指定json配置的Json对象响应
*
* @param objectClass
* @return {@link T}
* @throws IOException
*/
public <T> T getJsonBody(JSONConfig jsonConfig, Class<T> objectClass) throws IOException {
return JSONUtil.toBean(getStringBody(), jsonConfig, objectClass);
}
/**
* 获取指定编码及指定json配置的Json对象响应
*
* @param objectClass
* @return {@link T}
* @throws IOException
*/
public <T> T getJsonBody(String charSet, JSONConfig jsonConfig, Class<T> objectClass) throws IOException {
return JSONUtil.toBean(getStringBody(charSet), jsonConfig, objectClass);
}
/**
* 获取响应流
*
* @return {@link InputStream}
* @throws IOException
*/
public StreamEntity getResponseAsStream() throws IOException {
if (response == null) {
throw new RuntimeException("请在发送请求后调用该方法!");
}
return new StreamEntity(response, httpClient);
}
/**
* 最后调用
*
* @throws IOException
*/
public void closeConnection() throws IOException {
if (response != null) {
response.close();
}
}
/**
* 发送请求
*
* @param request
* @throws IOException
*/
private void execute(HttpRequestBase request) throws IOException {
if (!this.headers.isEmpty()) {
request.setHeaders(headers.toArray(new Header[0]));
}
protocolCheck();
//设置请求配置
request.setConfig(getRequestConfig());
response = httpClient.execute(request);
}
/**
* 请求地址校验
*/
private void protocolCheck() {
if ((!this.url.contains(Protocol.HTTPS.getProtocol())) && (!this.url.contains(Protocol.HTTP.getProtocol()))) {
throw new RuntimeException("请检查url(" + this.url + ")正确使用方式!");
}
}
}
2.2 相关依赖类
2.2.1 常量
/**
* @author shawn
* @date 2023年 09月 22日 23:58 23:58:14
*/
public enum Method {
GET("GET"),
POST("POST"),
PUT("PUT"),
DELETE("DELETE");
private String method;
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
Method(String method) {
this.method = method;
}
Method() {
}
}
/**
* @author shawn
* @date 2023年 09月 23日 17:11 17:11:30
*/
public enum Protocol {
HTTP("http://"),
HTTPS("https://");
private String protocol;
public void setProtocol(String protocol) {
this.protocol = protocol;
}
public String getProtocol() {
return protocol;
}
Protocol(String protocol) {
this.protocol = protocol;
}
Protocol() {
}
}
2.2.2 数据承载实体
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpEntity;
import org.apache.http.entity.AbstractHttpEntity;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.SequenceInputStream;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
public class MultipartRequestEntity extends AbstractHttpEntity implements Cloneable {
private static final Log log = LogFactory.getLog(MultipartRequestEntity.class);
private List<HttpEntity> httpEntities;
private Long contentLength = -1L;
@Override
public boolean isRepeatable() {
return httpEntities.stream().allMatch(HttpEntity::isRepeatable);
}
@Override
public long getContentLength() {
for (HttpEntity httpEntity : httpEntities) {
contentLength += httpEntity.getContentLength();
}
return contentLength;
}
@Override
public InputStream getContent() throws IOException, UnsupportedOperationException {
if (httpEntities.isEmpty()) {
throw new UnsupportedOperationException("No entities are available for this multipart request");
} else if (httpEntities.size() == 1) {
return httpEntities.get(0).getContent();
} else {
return new SequenceInputStream(new MultiPartInputStreamEnumerator(httpEntities));
}
}
@Override
public void writeTo(OutputStream outputStream) throws IOException {
for (HttpEntity httpEntity : httpEntities) {
httpEntity.writeTo(outputStream);
}
}
@Override
public boolean isStreaming() {
return false;
}
private static class MultiPartInputStreamEnumerator implements Enumeration<InputStream> {
private final Iterator<? extends HttpEntity> iterator;
private InputStream currentStream;
public MultiPartInputStreamEnumerator(List<? extends HttpEntity> httpEntities) {
this.iterator = httpEntities.iterator();
}
@Override
public boolean hasMoreElements() {
if (currentStream != null) {
try {
if (currentStream.available() > 0) {
return true;
}
} catch (IOException e) {
log.error("Error checking if there is more data available in the current stream", e);
}
}
currentStream = null;
while (iterator.hasNext()) {
HttpEntity httpEntity = iterator.next();
try {
InputStream inputStream = httpEntity.getContent();
if (inputStream.available() > 0) {
currentStream = inputStream;
break;
}
} catch (IOException e) {
log.error("Error getting content stream for entity", e);
}
}
return currentStream != null;
}
@Override
public InputStream nextElement() {
if (hasMoreElements()) {
InputStream inputStream = currentStream;
currentStream = null;
return inputStream;
} else {
throw new NoSuchElementException("No more input streams available");
}
}
}
}
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.impl.client.CloseableHttpClient;
import java.io.IOException;
/**
* @author shawn
* @date 2023年 09月 25日 10:02 10:02:48
*/
public class StreamEntity {
private CloseableHttpResponse response;
private CloseableHttpClient httpClient;
public void close() throws IOException {
if (response != null){
response.close();
}
if (httpClient != null){
httpClient.close();
}
}
public StreamEntity(CloseableHttpResponse response, CloseableHttpClient httpClient) {
this.response = response;
this.httpClient = httpClient;
}
public CloseableHttpResponse getResponse() {
return response;
}
}