我们想要一个什么样的?
理想型的HTTP库应当具备以下能力:
- 提供分析记录请求各个阶段的能力,如在什么时候发送数据、在什么接收数据、各阶段的耗时
- 支持响应缓存减少重复的网络请求
- 支持重试
- 支持连接池能复用连接
- 简单易用
- 支持HTTP/1.0、HTTP/1.1、HTTP/2.0
- 支持同步和异步请求
- 支持取消请求
- 支持设置超时
目前有哪些可选的,哪一个和我们匹配度最高的?
可以发起http请求的组件:
- JDK's URLConnection uses traditional thread-blocking I/O.
- Apache HTTP Client uses traditional thread-blocking I/O with thread-pools.
- Apache Async HTTP Client uses NIO.
- Jersey is a RESTful client/server framework; the client API can use several HTTP client backends including URLConnection and Apache HTTP Client.
- OkHttp uses traditional thread-blocking I/O with thread-pools.
- Retrofit turns your HTTP API into a Java interface and can use several HTTP client backends including Apache HTTP Client.
- Grizzly is network framework with low-level HTTP support; it was using NIO but it switched to AIO .
- Netty is a network framework with HTTP support (low-level), multi-transport, includes NIO and native (the latter uses epoll on Linux).
- Jetty Async HTTP Client uses NIO.
- Async HTTP Client wraps either Netty, Grizzly or JDK's HTTP support.
- clj-http wraps the Apache HTTP Client.is an async subset of clj-http implemented partially in Java directly on top of NIO.
- http async client wraps the Async HTTP Client for Java.
下面挑选主流且大家熟悉的做个对比:
结论
HttpURLConnection封装层次太低,支持的特性也比较少,因此不在我们的选择范围内;对比Apache HttpClient跟OkHttp,在功能跟性能上来看是不相上下的;
- 在稳定性上Apache HttpClient历史悠久,稳定性更好;
- 在社区活跃度上OkHttp更胜一筹,GitHub上星标达到30000+,可以说是当下最流行的Http库了;从安卓4.4版本开始就使用okhttp来处理http请求。
- 在扩展性上OkHttp更加方便用户定制个性化的扩展功能,如我们可以很方便的监控Http请求,在无需客户端配合的情况下,尽可能的保留更多的信息;
- 在易用性上,流式的构建者模式和不可变模式使得OkHttp更胜一筹;
在最终权衡下,决定使用OkHttp。
OkHttp:
官方文档:http://square.github.io/okhttp/
git源码:https://github.com/square/okhttp
-
-
官方示例
package okhttp3.guide; import java.io.IOException; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; public class GetExample { OkHttpClient client = new OkHttpClient(); String run(String url) throws IOException { Request request = new Request.Builder() .url(url) .build(); try (Response response = client.newCall(request).execute()) { //string()方法只能调用一次,因为调用后会关闭流 return response.body().string(); } } public static void main(String[] args) throws IOException { GetExample example = new GetExample(); String response = example.run("https://raw.github.com/square/okhttp/master/README.md"); System.out.println(response); } }
package okhttp3.guide; import java.io.IOException; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; public class PostExample { public static final MediaType JSON = MediaType.get("application/json; charset=utf-8"); OkHttpClient client = new OkHttpClient(); String post(String url, String json) throws IOException { RequestBody body = RequestBody.create(JSON, json); Request request = new Request.Builder() .url(url) .post(body) .build(); try (Response response = client.newCall(request).execute()) { //string()方法只能调用一次,因为调用后会关闭流 return response.body().string(); } } String bowlingJson(String player1, String player2) { return "{'winCondition':'HIGH_SCORE'," + "'name':'Bowling'," + "'round':4," + "'lastSaved':1367702411696," + "'dateStarted':1367702378785," + "'players':[" + "{'name':'" + player1 + "','history':[10,8,6,7,8],'color':-13388315,'total':39}," + "{'name':'" + player2 + "','history':[6,10,5,10,10],'color':-48060,'total':41}" + "]}"; } public static void main(String[] args) throws IOException { PostExample example = new PostExample(); String json = example.bowlingJson("Jesse", "Jake"); String response = example.post("http://www.roundsapp.com/post", json); System.out.println(response); } }
OkHttpClient client = new OkHttpClient.Builder().readTimeout(5, TimeUnit.SECONDS).build(); Request request = new Request.Builder().url("http://www.baidu.com").get().build(); Call call = client.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { System.out.println("Fail"); } @Override public void onResponse(Call call, Response response) throws IOException { System.out.println(response.body().string()); } });
-
注意点
①每次new OkHttpClient(); 都会创建一个连接池和线程池,所以要保证只new一次OkHttpClient()。可以使用newBuilder()来为不同的目标地址配置参数。
②异步请求:最多并发64个,同一个host的访问不能超过5个,否则进入等待队列。
③连接池:默认空闲连接数是5,每个连接最多存活时间是5分钟,连接池大小取决于内存大小(无限量)。
④超时:默认的connectTimeout,readTimeout,writeTimeout都是10s。
⑤缓存:OKHttp的网络缓存是基于http协议,使用lru策略;目前只支持GET,其他请求方式需要自己实现。需要服务器配合,通过head设置相关头来控制缓存,创建OkHttpClient时候需要配置Cache。
⑥Cookie:OKHttp默认是没有提供Cookie管理功能的,所以如果想增加Cookie管理需要重写CookieJar里面的方法。
⑦服务器主动断开的链接如何处理?OkHttp则不会进行连接判断,直接发送请求,如果服务端在接收请求后回复RST消息,OkHttp会重新建立连接后再发送请求。OkHttp由于没有检查连接状态,在服务端主动断开连接时,会发送无效请求,对于服务端来说,就降低了请求成功率。可以通过设置ConnectionPool的Keep-Alive时间来解决这个问题,默认值为5min。只要配置时间短于服务端对应的时间,就可以保证由客户端主动断开连接,就不会出现无效请求的问题。
⑧过滤器:官网写的非常清楚,传送门:https://github.com/square/okhttp/wiki/Interceptors#choosing-between-application-and-network-interceptors
⑨重试策略:
/**
* Report and attempt to recover from a failure to communicate with a server. Returns true if
* {@code e} is recoverable, or false if the failure is permanent. Requests with a body can only
* be recovered if the body is buffered or if the failure occurred before the request has been
* sent.
*/
private boolean recover(IOException e, boolean requestSendStarted, Request userRequest) {
streamAllocation.streamFailed(e);
// 1\. 应用层配置不在连接,默认为true
// The application layer has forbidden retries.
if (!client.retryOnConnectionFailure()) return false;
// 2\. 请求Request出错不能继续使用
// We can't send the request body again.
if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;
// 是否可以恢复的
// This exception is fatal.
if (!isRecoverable(e, requestSendStarted)) return false;
// 4\. 没有更多线路可供选择
// No more routes to attempt.
if (!streamAllocation.hasMoreRoutes()) return false;
// For failure recovery, use the same route selector with a new connection.
return true;
}
若应用层配置的是不再连接(默认为true),则不可恢复。(可通过设置retryOnConnectionFailure来改变)
请求Request是不可重复使用的Request,则不可恢复
根据Exception的类型判断是否可以恢复的 (isRecoverable()方法)
3.1、如果是协议错误(ProtocolException)则不可恢复
3.2、如果是中断异常(InterruptedIOException)则不可恢复
3.3、如果是SSL握手错误(SSLHandshakeException && CertificateException)则不可恢复
3.4、certificate pinning错误(SSLPeerUnverifiedException)则不可恢复没有更多线路可供选择 则不可恢复
-
如果上述条件都不满足,则这个request可以恢复,最大重试20次。
举例:UnknownHostException不会重试;读写超时导致的SocketTimeoutException,要看是否已经发送了request,若已发送则不重试。连接超时导致的SocketTimeoutException,若没有其他路由则不重试,有则重试。
- 请求响应流程如下:
- 核心类
-
调用流程说明
如果有自定义的过滤器,先执行自定义的。