1. 前言
因为本人是 JustAuth 的主要贡献者之一,JustAuth 里需要和各大平台做 HTTP 交互来换取 Token、用户信息等数据,使用的是 hutool-http来实现 HTTP 请求的发送。前段时间,有朋友提出一个需求:能否使用 OkHttp3 等更优秀的 HTTP 请求工具来替换默认的 hutool-http ?于是写一个 simple-http 的想法就诞生了。
2. 设计思路
2.1. 解耦
simple-http 设计出来就是为了解决 JustAuth 中对 hutool-http 的强耦合,所以需要先找到 JustAuth 中到底使用到了需要哪些 HTTP 请求,找到它们的共性,才能解耦。具体参见下表:
| 请求类型 | 请求参数类型 | 响应数据类型 | 其他 |
|---|---|---|---|
| GET | query | JSON、TEXT | Header、URL Encode |
| POST | Form、JSON | JSON、TEXT | Header、URL Encode |
响应的数据类型,有些是格式化的 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 等等。哎呀呀,再说吧,再说吧~