1 介绍 在咱们所处的互联网世界中,HTTP协议算得上是使用最普遍的网络协议。OKHttp是一款高效的HTTP客户端,支持同一地址的连接共享同一个socket,经过链接池来减少响应延迟,还有透明的GZIP压缩,请求缓存等优点。若是您的服务器配置了多个IP地址,当第一个IP链接失败的时候,OkHttp会自动尝试下一个IP。OkHttp还处理了代理服务器问题和SSL握手失败问题。 值得一提的是:Android4.4原生的HttpUrlConnection底层已经替换成了okhttp实现了。java
public final class URL implemen ts Serializable { public URLConnection openC onnection() throws IOException { return This.handler.openConnection(this); }}
复制代码
这个handler,在源码中判断到若是是HTTP协议,就会创HtppHandler:android
public final class HttpHandler extends URLStreamHandler {
@Override protected URLConnection openConnection(URL url) throws IOException {
// 调用了OKHttpClient()的方法
return new OkHttpClient().open(url); }
@Override protected URLConnection openConnection(URL url, Proxy proxy) throws IOException {
if (url == null || proxy == null) {
throw new IllegalArgumentException("url == null || proxy == null");
}
return new OkHttpClient().setProxy(proxy).open(url); }
@Override protected int getDefaultPort() {
return 80;
}
}
复制代码
2 基本使用方式 在OKHttp,每次网络请求就是一个Request ,咱们在Request里填写咱们须要的url,header等其余参数,再经过Request构造出Call ,Call内部去请求服务器,获得回复,并将结果告诉调用者。同时okhttp提供了同步和异步两种方式进行网络操做。 2.1 同步编程
OkHttpClient client = new OkHttpClient();String run(String url) throws IOException {
Request request = new Request.Builder() .url(url) .build();
Response response = client.newCall(request).execute();
return response.body().string();
}
复制代码
直接execute执行获得Response,经过Response能够获得code,message等信息。android自己是不容许在UI线程作网络请求操做,须要在子线程中执行。 2.2 异步数组
Request request = new Request.Builder() .url("http://www.baidu.com") .build();
client.newCall(request).enqueue(new Callback() {
@Override public void onFailure(Request request, IOException e) {}
@Override public void onResponse(Response response) throws IOException {
//NOT UI Thread
if(response.isSuccessful()){
System.out.println(response.code());
System.out.println(response.body().string());
}
}
});
复制代码
在同步的基础上讲execute改为enqueue,而且传入回调接口,但接口回调回来的代码是在非UI线程的,所以若是有更新UI的操做必须切到主线程。 3 总体结构 3.1 处理网络响应的拦截器机制 不管是同步的call.execute() 仍是异步的call.enqueue() ,最后都是异曲同工地走到浏览器
call.getResponseWithInterceptorChain(boolean forWebSocket)方法。
private Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!retryAndFollowUpInterceptor.isForWebSocket()) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor( retryAndFollowUpInterceptor.isForWebSocket()));
Interceptor.Chain chain = new RealInterceptorChain( interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest); }
复制代码
能够发现okhttp在处理网络响应时采用的是拦截器机制。okhttp用ArrayList对interceptors进行管理,interceptors将依次被调用。 缓存
如上图: 橙色框内是okhttp自带的Interceptors的实现类,它们都是在call.getResponseWithInterceptorChain() 中被添加入 InterceptorChain中,实际上这几个Interceptor都是在okhttp3后才被引入,它们很是重要,负责了重连、组装请求头部、读/写缓存、创建socket链接、向服务器发送请求/接收响应的所有过程。 在okhttp3以前,这些行为都封装在HttpEngine类中。okhttp3以后,HttpEngine已经被删去,取而代之的是这5个Interceptor,能够说一次网络请求中的细节被解耦放在不一样的Interceptor中,不一样Interceptor只负责本身的那一环节工做(对Request或者Response进行获取/处理),使得拦截器模式彻底贯穿整个网络请求。 用户能够添加自定义的Interceptor,okhttp把拦截器分为应用拦截器和网络拦截器:安全
public class OkHttpClient implements Cloneable, Call.Factory { final List<Interceptor> interceptors; final List<Interceptor> networkInterceptors; ...... }
复制代码
调用OkHttpClient.Builder的addInterceptor() 能够添加应用拦截器,只会被调用一次,能够处理网络请求回来的最终Response 调用addNetworkInterceptor() 能够添加network拦截器,处理全部的网络响应(一次请求若是发生了redirect ,那么这个拦截器的逻辑可能会被调用两次)服务器
Interceptor解析 由上面的分析能够知道,okhttp框架内自带了5个Interceptor的实现: RetryAndFollowUpInterceptor,重试那些失败或者redirect的请求。 BridgeInterceptor,请求以前对响应头作了一些检查,并添加一些头,而后在请求以后对响应作一些处理(gzip解压or设置cookie)。 CacheInterceptor,根据用户是否有设置cache,若是有的话,则从用户的cache中获取当前请求的缓存。 ConnectInterceptor,复用链接池中的链接,若是没有就与服务器创建新的socket链接。 CallServerInterceptor,负责发送请求和获取响应。cookie
下图是在Interceptor Chain中的数据流: 网络
官方文档关于Interceptor的解释是: Observes, modifies, and potentially short-circuits requests going out and the corresponding responses coming back in. Typically interceptors add, remove, or transform headers on the request or response.经过Interceptors 能够 观察,修改或者拦截请求/响应。通常拦截器添加,删除或修改 请求/响应的header。
Interceptor是一个接口,里面只有一个方法: public interface Interceptor { Response intercept(Chain chain) throws IOException;}
实现Interceptor须要注意两点(包括源码内置的Interceptor也是严格遵循如下两点):
** 关键代码 ** 如下是HTTP客户端向服务器发送报文的过程:
HTTP是个应用层协议。HTTP无需操心网络通讯的具体细节;它把联网的细节都交给了通用、可靠的因特网传输协议TCP/IP。TCP/IP隐藏了各类网络和硬件的特色及弱点,使各类类型的计算机和网络都可以进行可靠的通讯。简单来讲,HTTP协议位于TCP的上层。HTTP使用TCP来传输其报文数据。 若是你使用okhttp请求一个URL,具体的工做以下: 框架使用URL和配置好的OkHttpClient建立一个address。此地址指定咱们将如何链接到网络服务器。 框架经过address从链接池中取回一个链接。 若是没有在池中找到链接,ok会选择一个route尝试链接。这一般意味着使用一个DNS请求, 以获取服务器的IP地址。若是须要,ok还会选择一个TLS版本和代理服务器。 若是获取到一个新的route,它会与服务器创建一个直接的socket链接、使用TLS安全通道(基于HTTP代理的HTTPS),或直接TLS链接。它的TLS握手是必要的。 开始发送HTTP请求并读取响应。
若是有链接出现问题,OkHttp将选择另外一条route,而后再试一次。这样的好处是当服务器地址的一个子集不可达时,OkHttp可以自动恢复。并且当链接池过时或者TLS版本不受支持时,这种方式很是有用。一旦响应已经被接收到,该链接将被返回到池中,以便它能够在未来的请求中被重用。链接在池中闲置一段时间后,它会被赶出。
下面就说说这五个步骤的关键代码: 4.1 创建链接 —— ConnectInterceptor 上面所述前四个步骤都在ConnectInterceptor中。HTTP是创建在TCP协议之上,HTTP协议的瓶颈及其优化技巧都是基于TCP协议自己的特性。好比TCP创建链接时也要在第三次握手时才能捎带 HTTP 请求报文,达到真正的创建链接,可是这些链接没法复用会致使每次请求都经历三次握手和慢启动。正是因为TCP在创建链接的初期有慢启动(slow start)的特性,因此链接的重用老是比新建链接性能要好。 而okhttp的一大特色就是经过链接池来减少响应延迟。若是链接池中没有可用的链接,则会与服务器创建链接,并将socket的io封装到HttpStream(发送请求和接收response)中,这些都在ConnectInterceptor中完成。具体在StreamAllocation.findConnection() 方法中,下面是具体逻辑:
/**
* Returns a connection to host a new stream. This prefers the existing connection if it exists, * then the pool, finally building a new connection. */
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout, boolean connectionRetryEnabled) throws IOException {
Route selectedRoute; synchronized (connectionPool) {
...... // Attempt to get a connection from the pool.
RealConnection pooledConnection = Internal.instance.get(connectionPool, address, this);// 1 ......
if (selectedRoute == null) {
selectedRoute = routeSelector.next();//2 ......
}
RealConnection newConnection = new RealConnection(selectedRoute);//3 ......
synchronized (connectionPool) {//4
Internal.instance.put(connectionPool, newConnection); this.connection = newConnection;
if (canceled) throw new IOException("Canceled");
}
newConnection.connect(connectTimeout, readTimeout, writeTimeout, address.connectionSpecs(), connectionRetryEnabled);//5
return newConnection;
}
复制代码
下面具体说说每一步作了什么: 线程池中取得链接
RealConnection pooledConnection = pool.get(address, streamAllocation)
//StreamAllocation.java
RealConnection get(Address address, StreamAllocation streamAllocation) {
for (RealConnection connection : connections) {
if (connection.allocations.size() < connection.allocationLimit && address.equals(connection.route().address)//根据url来命中connection && !connection.noNewStreams) {
streamAllocation.acquire(connection);//将可用的链接放入 return connection; } } return null; }
若是selectedRoute为空,则选择下一条路由RouteselectedRoute = routeSelector.next();
//RouteSelector.java public final class RouteSelector { public Route next() throws IOException { // Compute the next route to attempt. if (!hasNextInetSocketAddress()) { if (!hasNextProxy()) { if (!hasNextPostponed()) { throw new NoSuchElementException(); } return nextPostponed(); } lastProxy = nextProxy(); } lastInetSocketAddress = nextInetSocketAddress(); // Route route = new Route(address, lastProxy, lastInetSocketAddress); if (routeDatabase.shouldPostpone(route)) { postponedRoutes.add(route); // We will only recurse in order to skip previously failed routes. They will be tried last. return next(); } return route; } private Proxy nextProxy() throws IOException { if (!hasNextProxy()) { throw new SocketException("No route to " + address.url().host() + "; exhausted proxy configurations: " + proxies); } Proxy result = proxies.get(nextProxyIndex++); resetNextInetSocketAddress(result); return result; } private void resetNextInetSocketAddress(Proxy proxy) throws IOException { ...... List<InetAddress> addresses = address.dns().lookup(socketHost); //调用dns查询域名对应的ip ... } }`
复制代码
浏览器须要知道目标服务器的 IP地址和端口号 才能创建链接。将域名解析为 IP地址 的这个系统就是 DNS。
之前面建立的route为参数新建一个RealConnectionRealConnection newConnection = new RealConnection(selectedRoute);
public RealConnection(Route route) { this.route = route; }
添加到链接池
`public final class ConnectionPool { void put(RealConnection connection) { assert (Thread.holdsLock(this)); if (!cleanupRunning) { cleanupRunning = true; executor.execute(cleanupRunnable); //这里很重要,把闲置超过keepAliveDurationNs时间的connection从链接池中移除。 //具体细节看ConnectionPool 的cleanupRunnable里的run()逻辑 } connections.add(connection); } }`
复制代码
调用RealConnection的connect()方法,其实是buildConnection() 构建链接。
`//RealConnection.javaprivate void buildConnection(int connectTimeout, int readTimeout, int writeTimeout, ConnectionSpecSelector connectionSpecSelector) throws IOException { connectSocket(connectTimeout, readTimeout); //创建socket链接establishProtocol(readTimeout, writeTimeout, connectionSpecSelector); }`
复制代码
调用connectSocket链接socket。调用establishProtocol根据HTTP协议版本作一些不一样的事情:SSL握手等等。 重点来了!connectSocket(connectTimeout, readTimeout); 里的逻辑其实是:
`public final class RealConnection extends FramedConnection.Listener implements Connection { public void connectSocket(Socket socket, InetSocketAddress address, int connectTimeout) throws IOException { socket.connect(address, connectTimeout); //Http是基于TCP的,天然底层也是创建了socket链接 ... source = Okio.buffer(Okio.source(rawSocket)); sink = Okio.buffer(Okio.sink(rawSocket)); //用Okio封装了socket的输入和输出流 }``
public final class Okio { public static Source source(Socket socket) throws IOException { if(socket == null) { throw new IllegalArgumentException("socket == null"); } else { AsyncTimeout timeout = timeout(socket); Source source = source((InputStream)socket.getInputStream(), (Timeout)timeout); return timeout.source(source); } } public static Sink sink(Socket socket) throws IOException { if(socket == null) { throw new IllegalArgumentException("socket == null"); } else { AsyncTimeout timeout = timeout(socket); Sink sink = sink((OutputStream)socket.getOutputStream(), (Timeout)timeout); return timeout.sink(sink); } } }`
`构建HttpStream
resultConnection.socket().setSoTimeout(readTimeout); resultConnection.source.timeout().timeout(readTimeout, MILLISECONDS); resultConnection.sink.timeout().timeout(writeTimeout, MILLISECONDS); resultStream = new Http1xStream( client, this, resultConnection.source, resultConnection.sink);`
复制代码
至此,HttpStream就构建好了,经过它能够发送请求和接收response。
4.2 发送request/接收Response —— CallServerInterceptor CallServerInterceptor的intercept() 方法里 负责发送请求和获取响应,实际上都是由HttpStream 类去完成具体的工做。 Http1XStream 一个socket链接用来发送HTTP/1.1消息,这个类严格按照如下生命周期: writeRequestHeaders() 发送request header 打开一个sink 来写request body,而后关闭sink readResponseHeaders()读取response头部 打开一个source 来读取response body,而后关闭source
4.2.1 writeRequest HTTP报文是由一行一行的简单字符串组成的,都是纯文本,不是二进制代码,能够很方便地进行读写。
public final class Http1xStream implements HttpStream { /** Returns bytes of a request header for sending on an HTTP transport. */ public void writeRequest(Headers headers, String requestLine) throws IOException { if (state != STATE_IDLE) throw new IllegalStateException("state: " + state); sink.writeUtf8(requestLine).writeUtf8("\r\n"); for (int i = 0, size = headers.size(); i < size; i++) { sink.writeUtf8(headers.name(i)) .writeUtf8(": ") .writeUtf8(headers.value(i)) .writeUtf8("\r\n"); } sink.writeUtf8("\r\n"); state = STATE_OPEN_REQUEST_BODY; }}public final class Headers { private final String[] namesAndValues; /** Returns the field at {@code position}. */ public String name(int index) { return namesAndValues[index * 2]; } /** Returns the value at {@code index}. */ public String value(int index) { return namesAndValues[index * 2 + 1]; }}`
复制代码
debug_write_request.png
`4.2.2 readResponse
public final class Http1xStream implements HttpStream {//读取Response Header public Response.Builder readResponse() throws IOException { ...... while (true) { StatusLine statusLine = StatusLine.parse(source.readUtf8LineStrict());//1 从InputStream上读入一行数据 Response.Builder responseBuilder = new Response.Builder() .protocol(statusLine.protocol) .code(statusLine.code) .message(statusLine.message) .headers(readHeaders()); if (statusLine.code != HTTP_CONTINUE) { state = STATE_OPEN_RESPONSE_BODY; return responseBuilder; } } }//读取Response Body,得到 @Override public ResponseBody openResponseBody(Response response) throws IOException { Source source = getTransferStream(response); return new RealResponseBody(response.headers(), Okio.buffer(source)); }}`
复制代码
解析HTTP报文,获得HTTP协议版本。
`public final class StatusLine { public static StatusLine parse(String statusLine/*HTTP/1.1 200 OK*/) throws IOException { // H T T P / 1 . 1 2 0 0 T e m p o r a r y R e d i r e c t // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 // Parse protocol like "HTTP/1.1" followed by a space. int codeStart; Protocol protocol; if (statusLine.startsWith("HTTP/1.")) { .......`
复制代码
读取ResponseHeader
`/** Reads headers or trailers. */ public Headers readHeaders() throws IOException { Headers.Builder headers = new Headers.Builder(); // parse the result headers until the first blank line for (String line; (line = source.readUtf8LineStrict()).length() != 0; ) { Internal.instance.addLenient(headers, line); } return headers.build(); }`
复制代码
读取ResponseBody,读取InputStream得到byte数组,至此就彻底获得了客户端请求服务端接口 的响应内容。
`public abstract class ResponseBody implements Closeable { public final byte[] bytes() throws IOException { ...... try { bytes = source.readByteArray(); } finally { Util.closeQuietly(source); } ...... return bytes; } /** * Returns the response as a string decoded with the charset of the Content-Type header. If that * header is either absent or lacks a charset, this will attempt to decode the response body as * UTF-8. */ public final String string() throws IOException { return new String(bytes(), charset().name()); }`
复制代码
5 总结 从上面关于okhttp发送网络请求及接受网络响应的过程的分析,能够发现 okhttp并非Volley和Retrofit这种二次封装的网络框架,而是基于最原始的java socket链接本身去实现了HTTP协议,就连Android源码也将其收录在内,堪称网络编程的典范。结合HTTP协议相关书籍与okhttp的源码实践相结合进行学习,相信能够对HTTP协议有具体且深刻的掌握。 [转载至]:www.jianshu.com/p/57c0b0694… ##