1. 前言
因为本人是 JustAuth 的主要贡献者之一,JustAuth
里需要和各大平台做 HTTP 交互来换取 Token、用户信息等数据,使用的是 hutool-http
来实现 HTTP 请求的发送。前段时间,有朋友提出一个需求:能否使用 OkHttp3
等更优秀的 HTTP 请求工具来替换默认的 hutool-http
?于是写一个 simple-http
的想法就诞生了。
2. 设计思路
2.1. 解耦
simple-http
设计出来就是为了解决 JustAuth
中对 hutool-http
的强耦合,所以需要先找到 JustAuth
中到底使用到了需要哪些 HTTP 请求,找到它们的共性,才能解耦。具体参见下表:
响应的数据类型,有些是格式化的 JSON 数据,有些只是简单的文本信息,但总的来说都是字符串数据,具体的解析,交给使用方去处理。所以,此时通用HTTP的接口就已经可以设计出来了。
/**
* <p>
* HTTP 接口
* </p>
*
* @author yangkai.shen
* @date Created in 2019/12/24 18:21
*/
public interface Http {
/**
* GET 请求
*
* @param url URL
* @return 结果
*/
String get(String url);
/**
* GET 请求
*
* @param url URL
* @param params 参数
* @param encode 是否需要 url encode
* @return 结果
*/
String get(String url, Map<String, String> params, boolean encode);
/**
* GET 请求
*
* @param url URL
* @param params 参数
* @param header 请求头
* @param encode 是否需要 url encode
* @return 结果
*/
String get(String url, Map<String, String> params, HttpHeader header, boolean encode);
/**
* POST 请求
*
* @param url URL
* @return 结果
*/
String post(String url);
/**
* POST 请求
*
* @param url URL
* @param data JSON 参数
* @return 结果
*/
String post(String url, String data);
/**
* POST 请求
*
* @param url URL
* @param data JSON 参数
* @param header 请求头
* @return 结果
*/
String post(String url, String data, HttpHeader header);
/**
* POST 请求
*
* @param url URL
* @param params form 参数
* @param encode 是否需要 url encode
* @return 结果
*/
String post(String url, Map<String, String> params, boolean encode);
/**
* POST 请求
*
* @param url URL
* @param params form 参数
* @param header 请求头
* @param encode 是否需要 url encode
* @return 结果
*/
String post(String url, Map<String, String> params, HttpHeader header, boolean encode);
}
2.2. 雏形
既然通用接口已经设计出来了,下一步就是需要选择具体发送 HTTP 的工具包。这一步有 2 个要求,既需要根据常见的 HTTP 工具依赖来自动决定,也需要提供用户自定义的配置的便捷性。
- 根据引入的依赖判断使用哪一个 HTTP 工具,可以使用
Class.forName()
来判断 - 用户自定义,简单的说就是提供
set
方法
/**
* <p>
* 请求工具类
* </p>
*
* @author yangkai.shen
* @date Created in 2019/12/24 18:15
*/
@UtilityClass
public class HttpUtil {
private static Http proxy;
static {
Http defaultProxy = null;
ClassLoader classLoader = HttpUtil.class.getClassLoader();
// 基于 java 11 HttpClient
if (ClassUtil.isPresent("java.net.http.HttpClient", classLoader)) {
defaultProxy = new com.xkcoding.http.support.java11.HttpClientImpl();
}
// 基于 okhttp3
else if (ClassUtil.isPresent("okhttp3.OkHttpClient", classLoader)) {
defaultProxy = new OkHttp3Impl();
}
// 基于 httpclient
else if (ClassUtil.isPresent("org.apache.http.impl.client.HttpClients", classLoader)) {
defaultProxy = new HttpClientImpl();
}
// 基于 hutool
else if (ClassUtil.isPresent("cn.hutool.http.HttpRequest", classLoader)) {
defaultProxy = new HutoolImpl();
}
proxy = defaultProxy;
}
public void setHttp(Http http) {
proxy = http;
}
private void checkHttpNotNull(Http proxy) {
if (null == proxy) {
throw new SimpleHttpException("HTTP 实现类未指定!");
}
}
/**
* GET 请求
*
* @param url URL
* @return 结果
*/
public String get(String url) {
checkHttpNotNull(proxy);
return proxy.get(url);
}
/**
* GET 请求
*
* @param url URL
* @param params 参数
* @param encode 是否需要 url encode
* @return 结果
*/
public String get(String url, Map<String, String> params, boolean encode) {
checkHttpNotNull(proxy);
return proxy.get(url, params, encode);
}
/**
* GET 请求
*
* @param url URL
* @param params 参数
* @param header 请求头
* @param encode 是否需要 url encode
* @return 结果
*/
public String get(String url, Map<String, String> params, HttpHeader header, boolean encode) {
checkHttpNotNull(proxy);
return proxy.get(url, params, header, encode);
}
/**
* POST 请求
*
* @param url URL
* @return 结果
*/
public String post(String url) {
checkHttpNotNull(proxy);
return proxy.post(url);
}
/**
* POST 请求
*
* @param url URL
* @param data JSON 参数
* @return 结果
*/
public String post(String url, String data) {
checkHttpNotNull(proxy);
return proxy.post(url, data);
}
/**
* POST 请求
*
* @param url URL
* @param data JSON 参数
* @param header 请求头
* @return 结果
*/
public String post(String url, String data, HttpHeader header) {
checkHttpNotNull(proxy);
return proxy.post(url, data, header);
}
/**
* POST 请求
*
* @param url URL
* @param params form 参数
* @param encode 是否需要 url encode
* @return 结果
*/
public String post(String url, Map<String, String> params, boolean encode) {
checkHttpNotNull(proxy);
return proxy.post(url, params, encode);
}
/**
* POST 请求
*
* @param url URL
* @param params form 参数
* @param header 请求头
* @param encode 是否需要 url encode
* @return 结果
*/
public String post(String url, Map<String, String> params, HttpHeader header, boolean encode) {
checkHttpNotNull(proxy);
return proxy.post(url, params, header, encode);
}
}
2.3. 实现
如果看了前面的设计模式系列文章的话,应该知道上面其实可以用到 3 种设计模式:
代理模式、委派模式、策略模式
,没看出来的小伙伴,可以去学习学习哦~
设计模式传送门
simple-http
提供了 4 种默认实现,分别是 JDK11 的 HttpClient
、OkHttp3
、Apache 的 HttpClient
、hutool-http
,如果项目中同时存在这几个 HTTP 工具的依赖,那么 simple-http
会根据优先级顺序从左往右,自行选择具体执行 HTTP 请求的工具。源码比较多,就不贴在博客里了,关于这 4 种默认实现的详细代码,请自行前往 GitHub 阅读源码实现,源码传送门 。
3. 使用方式
引入 simple-http
依赖
<dependency>
<groupId>com.xkcoding.http</groupId>
<artifactId>simple-http</artifactId>
<version>1.0</version>
</dependency>
再引用具体实现
- JDK11 的 HttpClient(使用 JDK11 即可)
- OkHttp3
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp3.version}</version>
</dependency>
- Apache HttpClient
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpclient.version}</version>
</dependency>
- Hutool 的 HTTP
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-http</artifactId>
<version>${hutool.version}</version>
</dependency>
- 如果不想使用默认的,可以自定实现
com.xkcoding.http.support.Http
接口,然后通过com.xkcoding.http.HttpUtil#setHttp(Http http)
配置
然后就可以使用 HttpUtil.xxx
开心的耍起来了~
4. 不足与改进
- 目前
simple-http
仅支持GET、POST
方式,其余方式,未来有机会的话,会考虑扩展,目前对JustAuth
来说,已经够用啦~ - 目前返回值都是
String
,后续要不要加一个自动解析返回类型的接口?如果响应是 JSON 内容的, 就默认返回 JSON,如果是 html 结构的,就默认返回 Document 等等。哎呀呀,再说吧,再说吧~
5. 其他
simple-http
预计会在JustAuth
的1.4.x
版本正式上线,目前整合已完毕(2019.12.25),已提交 PR,待小组成员仔细测试之后就会发布。届时欢迎小伙伴们尝试~simple-http
的灵感来自 @春哥 的 jfinal-weixin 的 HttpUtils。- JDK 11 的
HttpClient
实现,本人菜的一逼,所以主要也是 @春哥 的贡献,膜拜就完事了~