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]; }}`
複製代碼
![](http://upload-images.jianshu.io/upload_images/5827906-1a1ffde2e913d01a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)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… ##