简单 HTTP 工具

1. 前言

因为本人是 JustAuth 的主要贡献者之一,JustAuth 里需要和各大平台做 HTTP 交互来换取 Token、用户信息等数据,使用的是 hutool-http来实现 HTTP 请求的发送。前段时间,有朋友提出一个需求:能否使用 OkHttp3 等更优秀的 HTTP 请求工具来替换默认的 hutool-http ?于是写一个 simple-http 的想法就诞生了。

2. 设计思路

2.1. 解耦

simple-http 设计出来就是为了解决 JustAuth 中对 hutool-http 的强耦合,所以需要先找到 JustAuth 中到底使用到了需要哪些 HTTP 请求,找到它们的共性,才能解耦。具体参见下表:

请求类型请求参数类型响应数据类型其他
GETqueryJSON、TEXTHeader、URL Encode
POSTForm、JSONJSON、TEXTHeader、URL Encode

响应的数据类型,有些是格式化的 JSON 数据,有些只是简单的文本信息,但总的来说都是字符串数据,具体的解析,交给使用方去处理。所以,此时通用HTTP的接口就已经可以设计出来了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
/**
* <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 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/**
* <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 的 HttpClientOkHttp3Apache 的 HttpClienthutool-http,如果项目中同时存在这几个 HTTP 工具的依赖,那么 simple-http 会根据优先级顺序从左往右,自行选择具体执行 HTTP 请求的工具。源码比较多,就不贴在博客里了,关于这 4 种默认实现的详细代码,请自行前往 GitHub 阅读源码实现,源码传送门👉

3. 使用方式

引入 simple-http 依赖

1
2
3
4
5
<dependency>
<groupId>com.xkcoding.http</groupId>
<artifactId>simple-http</artifactId>
<version>1.0</version>
</dependency>

再引用具体实现

  • JDK11 的 HttpClient(使用 JDK11 即可)
  • OkHttp3
1
2
3
4
5
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp3.version}</version>
</dependency>
  • Apache HttpClient
1
2
3
4
5
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpclient.version}</version>
</dependency>
  • Hutool 的 HTTP
1
2
3
4
5
<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. 不足与改进

  1. 目前 simple-http 仅支持 GET、POST 方式,其余方式,未来有机会的话,会考虑扩展,目前对 JustAuth 来说,已经够用啦~
  2. 目前返回值都是 String,后续要不要加一个自动解析返回类型的接口?如果响应是 JSON 内容的, 就默认返回 JSON,如果是 html 结构的,就默认返回 Document 等等。哎呀呀,再说吧,再说吧~

5. 其他

  • simple-http 预计会在 JustAuth1.4.x 版本正式上线,目前整合已完毕(2019.12.25),已提交 PR,待小组成员仔细测试之后就会发布。届时欢迎小伙伴们尝试~
  • simple-http 的灵感来自 @春哥 的 jfinal-weixin 的 HttpUtils
  • JDK 11 的 HttpClient 实现,本人菜的一逼,所以主要也是 @春哥 的贡献,膜拜就完事了~
-------------本文结束  感谢您的阅读-------------
xkcoding wechat
欢迎来我的公众号「xkcoding小凯扣丁」逛逛
o(╯□╰)o 赞助一杯咖啡 ~~