OkHttpClient 源碼分析 1(基於3.9.0的源碼)

OkHttpClient是目前開發 android 應用使用最普遍的網絡框架,最近看了阿里的 httpdns 裏面對於 dns 的處理,咱們團隊就想調研一下在項目中有什麼利弊,而且框架中是否對 socket 的鏈接是怎麼緩存的。下面就帶着問題去分析一下這個框架:java

new OkHttpClient.Builder().build();這個建造者的每一個傳參在源碼分析2中有詳細的講解android

發送請求的只有這一個方法,就順着點進去看下是怎麼設計的web

@Override public Call newCall(Request request) {
    return RealCall.newRealCall(this, request, false /* for web socket */);
  }
複製代碼
/** *注意這個方法是沒有聲明 public 的,只能在包內部調用,因此用戶沒法直接調用. *並在這裏eventListenerFactory爲每一個 call 設置了監聽 **/
 static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.eventListener = client.eventListenerFactory().create(call);
    return call;
  }
  //私有構造方法,只能經過上面的靜態方法調用,這樣的好處是,防止框架使用者傳參有誤致使沒法正常使用
    private RealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    this.client = client;
    this.originalRequest = originalRequest;
    this.forWebSocket = forWebSocket;
    this.retryAndFollowUpInterceptor = new RetryAndFollowUpInterceptor(client, forWebSocket);
  }
複製代碼

使用者只須要傳入一個 Request 就可使用了

//request 也是經過構造者生成的, 整個請求的頭,行,體都從這裏傳入,並多了一個 tag 做爲標識
  public static class Builder {
    HttpUrl url;
    String method;
    Headers.Builder headers;
    RequestBody body;
    Object tag;//使用者能夠設置一個惟一標識,拿到 Request 的時候分不出是哪一個請求
    //默認設定請求使用了 GET 方法,並傳入空 Headers
    public Builder() {
      this.method = "GET";
      this.headers = new Headers.Builder();
    }
}
複製代碼

HttpUrl url, String method

經過方法url()傳入,使用HttpUrl對 url 封裝,方便獲取 url 的各個部分信息。能夠直接調用get(),head(), post(body),delete(),put(body)等設置 methord 並傳入 Body,也可使用method(String method,RequestBody body)直接輸字符串,這個方法裏進行了校驗,傳參出錯拋出異常算法

Headers

Headers.Builder中使用 addLenient,add 添加一行 header,能夠是「key:value」這樣的字符串,也能夠是兩個參數 key,value。 調用 add 檢測中文或不可見字符會拋出異常,而addLenient 不會檢測。 還有一個 set 會覆蓋已經存在全部name同樣的值數組

Headers 中用一個數組保存數據,偶數位是 key,奇數位是 value,裏面的大部分方法是處理這個數組的。 除了用 buidler 生成header,還能夠調用靜態方法of傳入一個 map 或者[key1,value1,key2,value2...]生成緩存

String get(String name) //獲取 key=name 的 value public List<String> values(String name) //和 get 方法相似,若是有多個 key=name 的條目返回全部的 value。(http 協議的header不限制順序,不排重) public Date getDate(String name)//內部調用get(name)並轉成時間 public int size() //header 的條數,保存了key、value數組的一半 public String name(int index) //獲取指定位置的 name,index相對於 header 的條數,不是數組的 index public String value(int index)//獲取指定位置的value,index相對於 header 的條數,不是數組的 index public Set<String> names()//獲取全部的 name,因爲是 set 重複的只算一條 public long byteCount() //整個 header 要佔用的長度,包含分割的「: 」和回車換行 public Map<String, List<String>> toMultimap()//把這個 Headers 轉成 map,因爲可能有多個相同的 key,value 要用 list返回 複製代碼

RequestBody

框架中兩個實現類:FormBody上傳普通的參數請求,MultipartBody上傳文件。RequestBody中三個靜態 create 方法傳入MediaType和ByteString、byte[]、File 知足了大部分請求須要,而且傳給MultipartBody。安全

若是須要自定義須要至少實現contentType,writeTo,分別傳入數據類型和寫入。若是有斷點續傳的要求複寫contentLength。服務器

public abstract MediaType contentType();
  public long contentLength() throws IOException {
    return -1;
  }
  public abstract void writeTo(BufferedSink sink) throws IOException;
複製代碼
/**出來下面三個主要方法的調用還有一些獲取 name 和 value 的方法,和 builder 裏添加name 和 value的方法,容易理解不作解釋。 **/
public final class FormBody extends RequestBody {
  private static final MediaType CONTENT_TYPE = MediaType.parse("application/x-www-form-urlencoded");

  @Override public MediaType contentType() {
    return CONTENT_TYPE;//返回了x-www-form-urlencoded的類型,適用於帶參數的接口請求
  }

  @Override public long contentLength() {
    return writeOrCountBytes(null, true);//調用了writeOrCountBytes
  }

  @Override public void writeTo(BufferedSink sink) throws IOException {
    writeOrCountBytes(sink, false);//調用了writeOrCountBytes
  }

  private long writeOrCountBytes(@Nullable BufferedSink sink, boolean countBytes) {
    long byteCount = 0L;
    Buffer buffer;
    if (countBytes) {//只須要獲取 body 的長度,contentLength
      buffer = new Buffer();
    } else {
      buffer = sink.buffer();//寫入數據 writeTo
    }
    for (int i = 0, size = encodedNames.size(); i < size; i++) {
      if (i > 0) buffer.writeByte('&');//第一個請求參數前不拼'&'
      buffer.writeUtf8(encodedNames.get(i));
      buffer.writeByte('=');//拼接請求體
      buffer.writeUtf8(encodedValues.get(i));
    }
    if (countBytes) {//若是是 contentLength 調用須要返回長度
      byteCount = buffer.size();
      buffer.clear();
    }
    return byteCount;
  }
複製代碼

MultipartBody和FormBody計算 body 長度和寫入數據的方法相似,可是MediaType類型比較多,看服務器須要哪一種類型的,咱們公司服務器是MediaType.parse("multipart/form-data")的,在 builder 中添加了多個part,writeTo的時候用boundary分割開。cookie

public static final class Builder {
    private final ByteString boundary;//每一個 part 的分割線,基本上都是一個夠長的隨機串
    private MediaType type = MIXED;//默認類型MediaType.parse("multipart/mixed")
    private final List<Part> parts = new ArrayList<>();//保存了全部的 part

    public Builder(); //隨機生成了一個boundary,沒有特殊需求不要修改
    public Builder(String boundary); //自定義一個boundary
    public Builder setType(MediaType type);//自定義類型
    public Builder addPart(Headers headers, RequestBody body); //添加一個 part 若是上傳文件能夠自定義 headers, FormBody.create(MultipartBody.FORM, file)
    public Builder addFormDataPart(String name, String value); //添加一個字符串的 body
    /**添加普通 header 的文件. *@params name 是服務器接受 file 的字段名; *@params filename 本地的文件名 *@params body 傳FormBody.create(MultipartBody.FORM, new File(filePath)) **/
    public Builder addFormDataPart(String name, @Nullable String filename, RequestBody body) ;
複製代碼

同步請求 execute() 異步請求 enqueue()

使用字段 executed 防止一個 RealCall 調用 enqueue 或者 execute 屢次,調用 eventListener.callStart 開始請求,兩個方法都調用到了client.dispatcher()okHttpClient.Builder中設置見 源碼解析2) 裏,其中enqueue使用了NamedRunnable, 最終都調用到了getResponseWithInterceptorChain();發送請求的核心方法。網絡

public abstract class NamedRunnable implements Runnable {
  protected final String name;
//在子類AsyncCall的傳參是「OkHttp%s」,redactedUrl()這個方法最終生成了一個和請求url類似的string。
  public NamedRunnable(String format, Object... args) {
    this.name = Util.format(format, args);//拼接成一個字符串,標識okhttp的請求
  }
/**在執行請求時,替換了線程名,子線程的名字多是dispatcher或者用戶定義的線程池設置的。調試更方便 *@methord execute 倒了個方法名,就是run方法。okhttp框架的方法名起得都很怪異,java原生 execute 是在origin thread調用 **/
  @Override public final void run() {
    String oldName = Thread.currentThread().getName();
    Thread.currentThread().setName(name);
    try {
      execute();
    } finally {
      Thread.currentThread().setName(oldName);
    }
  }
  protected abstract void execute();
}
複製代碼

getResponseWithInterceptorChain

設計思路是使用RealInterceptorChain保存了全部參數,相比舊版使用ApplicationInterceptorChain調用HttpEngine完成請求,新版依次調用攔截器interceptors的list生成請求,並把框架使用者設置的攔截器插入進來,能夠在請求過程當中拿到並修改包含request和response的全部值,提升了擴展性,而且這種鏈式依次調用應該會更容易理解。

client.interceptors()、client.networkInterceptors() (源碼分析2) 在OkHttpClient.Builder裏傳入,分別在 創建鏈接前 和 request 寫入服務器前 注入。 networkInterceptors 這個名字很到位,代表了是在網絡鏈接以後的注入的攔截器,最終寫入數據到socket中並獲取服務器返回的流,造成了一條數據調用鏈。

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());//OkHttpClient.Builder.addInterceptor(Interceptor interceptor)
    interceptors.add(retryAndFollowUpInterceptor);//錯誤處理,和其餘的攔截器相比 這個攔截器先初始化了,這樣設計我以爲是處理 cancel 用的
    interceptors.add(new BridgeInterceptor(client.cookieJar()));//拼接http協議 組裝header
    interceptors.add(new CacheInterceptor(client.internalCache()));//緩存處理
    interceptors.add(new ConnectInterceptor(client));//使用鏈接池創建鏈接
    if (!forWebSocket) {//forWebSocket 在 httpclient.newCall時是 false
      interceptors.addAll(client.networkInterceptors());//添加OkHttpClient.Builder.addNetworkInterceptor(Interceptor interceptor)
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));//想服務器發起請求
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, originalRequest, this, eventListener, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis());
    return chain.proceed(originalRequest);//開始請求
  }
複製代碼

Interceptor.Chain 它只有一個子類 RealInterceptorChain(舊版有兩個子類) 實際上就是一個保存了全部參數的類,裏面只有一個有用的方法 Response proceed(Request request)。像是 Interceptor 對應的 bean,而且每個攔截器都有一個 chain。

/**鏈式調用下一個攔截器,直到CallServerInterceptor發出請求,不在調用proceed。 *@params index 標識調用到第幾個攔截器了,固然不會超出interceptors.size(),邏輯上看是不會拋出AssetionError的 *@params calls 標識和攔截器對應的Chain調用了幾回,若是用戶定義的interceptor調用了屢次 chain.proceed會拋出異常 **/
  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec, RealConnection connection) throws IOException {
    if (index >= interceptors.size()) throw new AssertionError();
    calls++;
    // If we already have a stream, confirm that the incoming request will use it.
    if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {//connect不支持這個請求
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)  + " must retain the same host and port");
    }
    // If we already have a stream, confirm that this is the only call to chain.proceed().
    if (this.httpCodec != null && calls > 1) {//若是用戶定義的interceptor調用了屢次 chain.proceed會拋出異常
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)  + " must call proceed() exactly once");
    }
    // Call the next interceptor in the chain.
    RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec, connection, index + 1, request, call, eventListener, connectTimeout, readTimeout, writeTimeout);
    Interceptor interceptor = interceptors.get(index);//arraylist的下一個攔截器
    Response response = interceptor.intercept(next);//這裏的 interceptor若是命名爲next更好理解一點
    // Confirm that the next interceptor made its required call to chain.proceed().
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {//請求已經完成了,確保proceed必須僅僅調用一次,若是沒有調用 index+1小於size,若是調用屢次,calls會大於1
      throw new IllegalStateException("network interceptor " + interceptor  + " must call proceed() exactly once");
    }
    // Confirm that the intercepted response isn't null.
    if (response == null) {//已經走完了全部的攔截器,這時候必須有response
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }
    if (response.body() == null) {//response必須有相應體。分開判斷。提示不一樣的錯誤日誌,更好調試
      throw new IllegalStateException( "interceptor " + interceptor + " returned a response with no body");
    }
    return response;
  }
}
複製代碼

RetryAndFollowUpInterceptor

若是用戶沒有設置攔截器,第一個進入了RetryAndFollowUpInterceptor,經過 followRedirects、followSslRedirects(源碼分析2)控制302,401等響應碼邏輯。

/**使用StreamAllocation找到一個可用的 connect,而且傳給後面的攔截器繼續處理, *處理完成生成了 response,經過響應碼判斷是否還須要後續請求, *若是須要後續請求,判斷StreamAllocation這個鏈接是否能夠複用或者 new 一個新的。 *@methord createAddress 多是由於變量太多,不容易經過方法傳遞弄個Address類,沒有任何邏輯 *@field canceled 若是取消請求了,這裏拋出了IOException異常 *@constant MAX_FOLLOW_UPS最多能夠重定向20 **/
public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;//3.9版本只有這一中攔截器,若是用戶定義本身的攔截器要繼承RealInterceptorChain了
    Call call = realChain.call();
    EventListener eventListener = realChain.eventListener();
    //處理流和鏈接池的類
    streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()), call, eventListener, callStackTrace);

    int followUpCount = 0;//重定向多少次了
    Response priorResponse = null;//若是有重定向,記錄上一次返回值
    while (true) {//若是不 return 或拋出異常 會一直重試下去
      if (canceled) {//調用了 Call.cancel,若是這個 call 在子線程的隊列裏尚未發出去就能夠 cancel 掉
        streamAllocation.release();
        throw new IOException("Canceled");
      }
      Response response;//記錄請求結果
      boolean releaseConnection = true;//執行後面的攔截器的拋出的異常沒有抓到,是鏈接不到服務器或者服務器出錯,要釋放 StreamAllocation
      try {//執行後續的攔截器(下面一一介紹),當全部的攔截器執行完,就有了 response
        response = realChain.proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      } catch (RouteException e) {
        /* recover() 這個方法判斷若是鏈接出現問題是否能夠重試 *先判斷 Okhttpclient.retryOnConnectionFailure(), *在判斷是否能夠經過重試解決的Exception, *若是有更多的鏈接方案返回 true */
        if (!recover(e.getLastConnectException(), false, request)) {
          throw e.getLastConnectException();//路由認證失敗拋出異常
        }
        releaseConnection = false;
        continue;//掉用了streamAllocation.hasMoreRoutes,去找下一個可用的 connection
      } catch (IOException e) {
        // 和RouteException相似,重試請求仍是拋出異常
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        if (!recover(e, requestSendStarted, request)) throw e;
        releaseConnection = false;
        continue;
      } finally {
        if (releaseConnection) {//若是有請求過程當中拋出了異常,connection 不可用了,要釋放掉
          streamAllocation.streamFailed(null);
          streamAllocation.release();
        }
      }
      if (priorResponse != null) {//若是重試了一次或屢次,要把第一個響應的除了 body 部分返回給調用者
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder().body(null).build())
            .build();
      }
      Request followUp = followUpRequest(response);
      if (followUp == null) {//根據響應碼判斷是否須要後續請求,不須要後續處理則返回 response
        if (!forWebSocket) {
          streamAllocation.release();
        }
        return response;
      }

      closeQuietly(response.body());//body 不須要複用給priorResponse,則關掉

      if (++followUpCount > MAX_FOLLOW_UPS) {//若是從新發起請求次數超過20次就不在重試了
        streamAllocation.release();
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }

      if (followUp.body() instanceof UnrepeatableRequestBody) {//返回來的 requestbody 繼承了 UnrepeatableRequestBody。
        streamAllocation.release();
        throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
      }
//經過方法sameConnection判斷是否是要請求同一個 url,若是不是統一要 new 一個新的StreamAllocation
      if (!sameConnection(response, followUp.url())) {
        streamAllocation.release();
        streamAllocation = new StreamAllocation(client.connectionPool(),   createAddress(followUp.url()), call, eventListener, callStackTrace);
      } else if (streamAllocation.codec() != null) {//當前的streamAllocation沒有設置協議類型,正常不會進入到這裏
        throw new IllegalStateException("Closing the body of " + response + " didn't close its backing stream. Bad interceptor?");
      }

      request = followUp;//繼續請求這個
      priorResponse = response;//保存此次請求的結果,須要裏面的 request 和 header
    }
  }
複製代碼

300-399之間的請求碼是重定向,須要從新請求,拼接到方法裏了,實際這部分代碼也能夠在 builder 裏暴露出來提供默認實現。注意第一次408超時,只會再重試1次 client.proxyAuthenticator().authenticate(route, userResponse);參見源碼分析2

client.authenticator().authenticate(route, userResponse);參見源碼分析2

//當請求失敗的時候,經過這個方法判斷狀態碼,是否是要繼續請求,這裏只作簡單的分析
 private Request followUpRequest(Response userResponse) throws IOException {
  ..........
    switch (responseCode) {
      case HTTP_PROXY_AUTH://407 設置了代理,而且須要登陸代理才能繼續下面的訪問
        Proxy selectedProxy = route != null ? route.proxy()  : client.proxy();
        if (selectedProxy.type() != Proxy.Type.HTTP) {
          throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
        }
        //返回一個登陸代理的請求 
        return client.proxyAuthenticator().authenticate(route, userResponse);

      case HTTP_UNAUTHORIZED://401服務器須要受權,這裏返回一個登陸服務器的請求
        return client.authenticator().authenticate(route, userResponse);

      case HTTP_PERM_REDIRECT://308
      case HTTP_TEMP_REDIRECT://307
        // "If the 307 or 308 status code is received in response to a request other than GET
        // or HEAD, the user agent MUST NOT automatically redirect the request"
        if (!method.equals("GET") && !method.equals("HEAD")) {
          return null;
        }
        // fall-through
      case HTTP_MULT_CHOICE://300
      case HTTP_MOVED_PERM://301
      case HTTP_MOVED_TEMP://302
      case HTTP_SEE_OTHER://303
        if (!client.followRedirects()) return null;//若是設置成不支持重定向直接返回當前 response。

        String location = userResponse.header("Location");
        if (location == null) return null;//這裏的判空很嚴謹,按理說300的響應碼都會包含這個字段
        HttpUrl url = userResponse.request().url().resolve(location);
        if (url == null) return null;
        boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
        if (!sameScheme && !client.followSslRedirects()) return null;
        // Most redirects don't include a request body.
        Request.Builder requestBuilder = userResponse.request().newBuilder();
        if (HttpMethod.permitsRequestBody(method)) {//判斷是否是要修改 body,除了 post、get 都須要
          final boolean maintainBody = HttpMethod.redirectsWithBody(method);// 若是是PROPFIND須要保留 body,其餘的都不須要
          if (HttpMethod.redirectsToGet(method)) {//PROPFIND須要把方法給爲 get
            requestBuilder.method("GET", null);
          } else {//根據maintainBody是否保留 body
            RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
            requestBuilder.method(method, requestBody);
          }
          if (!maintainBody) {//body 都不要了,這些 header 裏標識 body 信息的都不要了
            requestBuilder.removeHeader("Transfer-Encoding");
            requestBuilder.removeHeader("Content-Length");
            requestBuilder.removeHeader("Content-Type");
          }
        }
        if (!sameConnection(userResponse, url)) {//認證過一次就不要再次認證了
          requestBuilder.removeHeader("Authorization");
        }
        return requestBuilder.url(url).build();
      case HTTP_CLIENT_TIMEOUT://請求超時,只重試一次
        if (!client.retryOnConnectionFailure()) {
          return null;
        }
        if (userResponse.request().body() instanceof UnrepeatableRequestBody) {
          return null;
        }
        if (userResponse.priorResponse() != null && userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {
          return null;//若是上次也是請求超時,此次仍是就不在重試了
        }
        return userResponse.request();
      default:
        return null;
    }
  }
複製代碼

BridgeInterceptor

根據body 中的信息設置 header 中的一些參數,好比數據長度,數據類型。

使用 OkHttpClient.Builder 設置過來的 CookieJar(源碼分析2) 接口設置 cookie,若是須要支持設置 cookie 請調用Builder cookieJar(CookieJar cookieJar)默認不支持cookie, 這樣設計多是考慮爲客戶端接口使用,基本不用 cookie,少了處理 cookie 的邏輯性能上更好一些

其中 Connection:Keep-Alive 對 socket 鏈接緩存,socket內部會每隔一段時間檢測是否和服務器連通,好比斷網了,檢測不到服務器在線,發送 RST 包斷開鏈接。

請求頭中的 Host 是爲了讓服務器定位哪一個域名下的服務的,只有不多的大公司會一個 ip 下的主機部署一套域名的服務,每每是一臺主機承載了不少個域名的服務,http 的請求頭裏只有要請求的路徑。爲了區分把請求下發給哪一個域名須要在 header 設置 Host,框架中已經自動搞定了。你們能夠試試,不帶 host 的請求有的是能夠走通的,但大部分是會報錯的

gzip 框架內部是自動支持壓縮的,這樣能夠減小傳輸的數據量。注意若是框架使用者在 Header 中設置了Accept-Encoding:gzip,框架不會自動解壓,會把原始數據在 response 直接返回。保證響應內容和請求是對應的

public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();
    RequestBody body = userRequest.body();
    if (body != null) {//若是是 post 請求,會設置 body
      MediaType contentType = body.contentType();
      if (contentType != null) {//請求體的數據類型,form 格式的 header 如 application/x-www-form-urlencoded
        requestBuilder.header("Content-Type", contentType.toString());
      }
      long contentLength = body.contentLength();
      if (contentLength != -1) {//數據長度,服務器根據數據長度讀取 body
        requestBuilder.header("Content-Length", Long.toString(contentLength));
        requestBuilder.removeHeader("Transfer-Encoding");
      } else {//輸出的內容長度不能肯定
        requestBuilder.header("Transfer-Encoding", "chunked");
        requestBuilder.removeHeader("Content-Length");
      }
    }
    if (userRequest.header("Host") == null) {//設置 host,一個ip會對應幾個域名的狀況,服務器判斷header 裏的 host,判斷應該去哪一個域名下查找數據
      requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }
    if (userRequest.header("Connection") == null) {//創建鏈接消耗性能,全部在服務器和客戶端都有緩存,要設置這個字段
      requestBuilder.header("Connection", "Keep-Alive");
    }
    boolean transparentGzip = false;
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {//使用 gzip 傳輸數據較少很大的數據量
      transparentGzip = true;
      requestBuilder.header("Accept-Encoding", "gzip");
    }
    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {//若是實現了 cookieJar 接口,會給 header 寫入 cookie
      requestBuilder.header("Cookie", cookieHeader(cookies));
    }
    if (userRequest.header("User-Agent") == null) {//設置了默認的 user-agent:okhttp/3.9.0
      requestBuilder.header("User-Agent", Version.userAgent());
    }
    Response networkResponse = chain.proceed(requestBuilder.build());//繼續執行後續的請求,直到服務器返回數據
    //這個方法裏只是調用了cookieJar.saveFromResponse(url, cookies);
    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
    //這裏給碼農返回的是傳進來的 request,並非添加了不少默認值以後的 request
    Response.Builder responseBuilder = networkResponse.newBuilder().request(userRequest);
    /**處理 gzip 的邏輯,框架使用者並無設置 gzip 的,框架自動添加了壓縮邏輯; *而且服務器的響應頭說返回了 gzip 的數據; *這裏解析了 body 中的數據並返回了沒壓縮的數據。 *清楚 header 中的Encoding和Length,由於碼農拿到的是解壓後的數據 **/
    if (transparentGzip && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding")) && HttpHeaders.hasBody(networkResponse)) {
      GzipSource responseBody = new GzipSource(networkResponse.body().source());
      Headers strippedHeaders = networkResponse.headers().newBuilder()
          .removeAll("Content-Encoding")//已經不是 gzip 壓縮以後的數據了
          .removeAll("Content-Length")//解壓後數據長度變長了
          .build();
      responseBuilder.headers(strippedHeaders);
      String contentType = networkResponse.header("Content-Type");
      responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
    }
    return responseBuilder.build();
  }
複製代碼

CacheInterceptor

主要處理了緩存邏輯,若是須要支持 Cache-Control、Pragma 響應頭使用 cache,可使用框架中自帶的 Cache 類OkHttpClient.Builder().cache(new Cache(file,maxSize)默認不支持緩存

InternalCache(源碼分析2) 獲取或者保存緩存的接口,這裏只處理存取的邏輯。

判斷緩存是否可用的邏輯在CacheStrategy中,CacheStrategy裏面對header進行了一系列的計算,這些計算並無使用接口暴露出來,這樣設計由於裏面徹底遵循了http協議,只要協議裏全部的header參數都處理了不會有任何其餘實現類,則不須要抽取爲接口

Response intercept(Chain chain) throws IOException {
    Response cacheCandidate = cache != null? cache.get(chain.request()): null;//設置了InternalCache,根據請求獲取一個緩存的 response

    long now = System.currentTimeMillis();//http 的協議須要根據當前時間是否判斷使用緩存 
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();//判斷 header 的緩存有效期
    Request networkRequest = strategy.networkRequest;//根據 header 判斷是否須要發起請求
    Response cacheResponse = strategy.cacheResponse;//根據 header 判斷是否是使用緩存的 response 
    if (cache != null) {//記錄使用了這個更新,在 cache 中的實現只是對使用緩存狀況計數
      cache.trackResponse(strategy);
    }
    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }//在 cache 中找到了 request 緩存的 response,可是根據 header 中的時間策略,這個 response 已經超時失效了
    // If we're forbidden from using the network and the cache is insufficient, fail.
    if (networkRequest == null && cacheResponse == null) {//request 的 cacheControl只容許使用緩存 onlyIfCached 爲 true,響應投中包含only-if-cached
      return new Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(Util.EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    }
    if (networkRequest == null) {//緩存還在有效期內,直接返回了緩存
      return cacheResponse.newBuilder()//返回了緩存的 respose
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }
    Response networkResponse = null;
    try {//若是沒有緩存或者緩存失效,後面繼續請求網絡
      networkResponse = chain.proceed(networkRequest);
    } finally {
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());//networkResponse是空確定是拋異常了,沒有返回值
      }
    }
    if (cacheResponse != null) {
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {//若是有緩存而且網絡返回了304,仍是要使用緩存數據
        Response response = cacheResponse.newBuilder()
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
            .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();
        cache.trackConditionalCacheHit();//使用了一次緩存,記錄一下
        cache.update(cacheResponse, response);//更新緩存,主要是 header 變了
        return response;
      } else {//緩存失效了,釋放掉
        closeQuietly(cacheResponse.body());
      }
    }
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();
    if (cache != null) {//下面是存儲緩存的
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        CacheRequest cacheRequest = cache.put(response);//更新緩存,header/body都變了
        return cacheWritingResponse(cacheRequest, response);
      }

      if (HttpMethod.invalidatesCache(networkRequest.method())) {//不支持緩存方法,要清除掉緩存,post 不支持
        try {
          cache.remove(networkRequest);//
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }
    return response;
  }
複製代碼

CacheStrategy.Factory

只是根據requestHeader對 CacheStrategy 須要的參數作解析,這裏叫 Factory 並不合適,由於它不是工廠,還不如弄個靜態方法parse,全部的邏輯處理都在get()裏(下一個分析),若是對http協議緩存處理部分有必定了解,就不用看Factory的分析了

public Factory(long nowMillis, Request request, Response cacheResponse) {
      this.nowMillis = nowMillis;//當前時間,判斷當前客戶端的時間是否是能夠直接使用緩存,若是客戶端的時間是錯的,會對緩存邏輯有影響
      this.request = request;//碼農的request
      this.cacheResponse = cacheResponse;//上一次的緩存

      if (cacheResponse != null) {
        this.sentRequestMillis = cacheResponse.sentRequestAtMillis();//緩存的請求時間 在CallServerInterceptor中給這兩個字段賦值
        this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis();//請求的響應時間
        Headers headers = cacheResponse.headers();
        for (int i = 0, size = headers.size(); i < size; i++) {//拿到緩存的response的header,找裏面和緩存時間相關的值
          String fieldName = headers.name(i);
          String value = headers.value(i);
          if ("Date".equalsIgnoreCase(fieldName)) {//原始服務器消息發出的時間,若是服務器沒有返回這個字段就使用框架中保存的receivedResponseMillis。&emsp;Date:Tue,17 Apr 2017 06:46:28GMT
            servedDate = HttpDate.parse(value);
            servedDateString = value;
          } else if ("Expires".equalsIgnoreCase(fieldName)) {//緩存數據的過時時間 Expires: Fri, 30 Oct 2018 14:19:41
            expires = HttpDate.parse(value);
          } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {//請求的數據的最後修改時間,下次請求使用If-Modified-Since傳給服務器,若是數據沒變化,服務器返回304。Last-modified:Tue,17 Apr 2017 06:46:28GMT
            lastModified = HttpDate.parse(value);
            lastModifiedString = value;
          } else if ("ETag".equalsIgnoreCase(fieldName)) {//數據的惟一標識,下次請求使用If-None-Match傳給服務器,若是數據沒變化,服務器返回304。Etag:"a030f020ac7c01:1e9f"
            etag = value;
          } else if ("Age".equalsIgnoreCase(fieldName)) {//從原始服務器到代理緩存造成的估算多少秒 Age: 12
            ageSeconds = HttpHeaders.parseSeconds(value, -1);
          }
        }
      }
    }
複製代碼

Factory.get()主要是處理header中緩存的邏輯,判斷傳進來的cacheResponse是否是能夠直接使用,又分爲兩個主要方法。

getCandidate()方法裏主要是計算緩存過時時間,還有一小部分根據cache設置requestHeader的邏輯,是主要的分析方法。 response.cacheControl()方法對responseHeader服務器返回的字段(Cache-Control、Pragma)解析,把字符串轉換爲Bean,CacheControl裏也有一個Budiler,這裏有點雞肋了,實際這個只是在header中parse就行了,絕對用不到new Builder出來。邏輯並不複雜,有興趣本身看一下。

getCandidate

private CacheStrategy getCandidate() {
      if (cacheResponse == null) {//若是沒有chache直接返回request策略,繼續請求網絡
        return new CacheStrategy(request, null);
      }
      if (request.isHttps() && cacheResponse.handshake() == null) {//若是是https請求,要有證書認證的信息,若是沒有繼續請求網絡
        return new CacheStrategy(request, null);
      }
      if (!isCacheable(cacheResponse, request)) {//這個方法里根據響應碼判斷響應的數據是否可使用緩存
        return new CacheStrategy(request, null);
      }
      CacheControl requestCaching = request.cacheControl();
      if (requestCaching.noCache() || hasConditions(request)) {//若是reseponse中nocache,或者If-Modified-Since、If-None-Match字段則進行網絡請求
        return new CacheStrategy(request, null);
      }
      CacheControl responseCaching = cacheResponse.cacheControl();
      if (responseCaching.immutable()) {//緩存的響應頭 Cache-Control的值爲immutable,標識永久可使用的緩存
        return new CacheStrategy(null, cacheResponse);
      }

      long ageMillis = cacheResponseAge();//根據header中date、age和當前時間,計算cacheResponse距離當前時間已通過了多久了
      /*根據maxage和expires計算緩存數據還能用多久時間, *還根據上一次修改時間lastModified距返回數據時間Date的差10%時間之內,HTTP RFC 建議這段時間內數據是有效的*/
      long freshMillis = computeFreshnessLifetime();

      if (requestCaching.maxAgeSeconds() != -1) {//在computeFreshnessLifetime中用if elseif優先去了maxAge的時間,這裏的if判斷是多餘的
        freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
      }
      long minFreshMillis = 0;
      if (requestCaching.minFreshSeconds() != -1) {//響應頭的min-fresh 最小刷新間隔
        minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
      }
      long maxStaleMillis = 0;
      if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {//若是不是must-revalidate過時以後必須請求新數據,max-stale容許過時多少秒以後,仍然可使用緩存
        maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
      }
      //上面把全部控制緩存時間的數據都準備好了,判斷一下緩存數據到當前的時間 和 緩存數據最大容許的過時時間 哪一個大
      if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
        Response.Builder builder = cacheResponse.newBuilder();
        if (ageMillis + minFreshMillis >= freshMillis) {//尚未超過最大容許過時時間,可是已經超過了數據的有效期,數據過時後能夠不及時刷新,返回一個警告說數據是陳舊的
          builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
        }
        long oneDayMillis = 24 * 60 * 60 * 1000L;
        if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {//緩存的數據已經超過24小時了,也給一個警告,http協議不推薦設置超過24小時的緩存
          builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
        }
        return new CacheStrategy(null, builder.build());//緩存還有效直接把緩存扔給了碼農
      }

      // 客戶端不能決定緩存是否可使用,讓服務器去決定,那就須要帶一些參數,下面這些都是根據緩存頭中服務器返回的數據標識,設置本次請求頭應該帶的參數,服務器知道客戶端如今緩存是何時返回的
      String conditionValue;
      if (etag != null) {//注意這裏使用的是elseif,優先級依次是 etag-lastModifity-date
        conditionName = "If-None-Match";
        conditionValue = etag;
      } else if (lastModified != null) {
        conditionName = "If-Modified-Since";
        conditionValue = lastModifiedString;
      } else if (servedDate != null) {
        conditionName = "If-Modified-Since";
        conditionValue = servedDateString;
      } else {
        return new CacheStrategy(request, null); // No condition! Make a regular request.
      }

      Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
      Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);//給請求頭添加參數

      Request conditionalRequest = request.newBuilder()
          .headers(conditionalRequestHeaders.build())
          .build();
      return new CacheStrategy(conditionalRequest, cacheResponse);//有緩存,可是判斷不出來是否能夠直接使用,讓服務器去決定,若是服務器返回304,就不用返回請求體了,服務器決定使用緩存
    }
複製代碼

ConnectInterceptor

在intercept中使用鏈接池建立連接,並根據協議獲取對應的HttpCodec的實現類,主要邏輯都在newStream、connection這兩個方法中。

@Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    StreamAllocation streamAllocation = realChain.streamAllocation();
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);//http 協議格式的接口,直接影響了協議版本
    RealConnection connection = streamAllocation.connection();
    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }
複製代碼

StreamAllocation

newStream 初始化connectTimeout、readTimeout、writeTimeout等全局變量,並調用findHealthyConnection找到一個可用的連接 RealConnection,在方法內 while (true) 從鏈接池裏找到沒有被佔用successCount=0,而且沒有close的連接,關鍵方法是findConnection。

/**找到一個connect。**/
  private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout, boolean connectionRetryEnabled) throws IOException {
    boolean foundPooledConnection = false;//是否找到了可用的RealConnection
    RealConnection result = null;//和foundPooledConnection對應,將要使用的connect
    Route selectedRoute = null;//生成RealConnection用到的Address、Proxy存到這個類中
    Connection releasedConnection;//將要釋放的連接
    Socket toClose;
    synchronized (connectionPool) {
      if (released) throw new IllegalStateException("released");
      if (codec != null) throw new IllegalStateException("codec != null");
      if (canceled) throw new IOException("Canceled");

      releasedConnection = this.connection;//默認全局保存的connection是不可用的了,須要釋放,從newStream進來是null
      toClose = releaseIfNoNewStreams();//判斷connection是否須要釋放,若是已經不可用把connection置空,並返回sockte準備釋放
      if (this.connection != null) {//通過上面releaseIfNoNewStreams的方法判斷,connection還可用沒有置空
        result = this.connection;
        releasedConnection = null;
      }
      if (!reportedAcquired) {//沒有須要釋放的 鏈接
        releasedConnection = null;
      }

      if (result == null) {//不能直接使用connection,從緩存池裏去一個,這裏route是空,先當沒有代理處理
        Internal.instance.get(connectionPool, address, this, null);//裏面調用了acquire,若是緩存池裏有可用的,就賦值給connection
        if (connection != null) {//使用了緩存池中的 connect 不用從新建立。
          foundPooledConnection = true;
          result = connection;
        } else {//緩存池中沒取到,須要從新建立鏈接,若是有代理,使用route取一個有代理的試試
          selectedRoute = route;
        }
      }
    }
    closeQuietly(toClose);

    if (releasedConnection != null) {//釋放掉不可用的
      eventListener.connectionReleased(call, releasedConnection);
    }
    if (foundPooledConnection) {//上面已經從鏈接池緩存中找到了可用的
      eventListener.connectionAcquired(call, result);
    }
    if (result != null) {
      return result;
    }

    boolean newRouteSelection = false;//緩存中沒有返回一個可用的RealConnection,須要生成一個
    if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {//判斷剛纔的全局保存的route是否爲空,若是尚未賦值 就使用routeSelector找一個
      newRouteSelection = true;
      routeSelection = routeSelector.next();//routeSelector中保存了全部的代理(可能多個),從中找到一個可用的並調用 dns,找到要鏈接的 ip,準備創建 socket 鏈接
    }

    synchronized (connectionPool) {
      if (canceled) throw new IOException("Canceled");

      if (newRouteSelection) {//Selection中返回了全部可用的route,從頭日後找,越靠前的代理連通性越高
        List<Route> routes = routeSelection.getAll();
        for (int i = 0, size = routes.size(); i < size; i++) {
          Route route = routes.get(i);
          Internal.instance.get(connectionPool, address, this, route);//掉用了connectPool.get在方法isEligible中判斷是否有和 address、route對應的緩存 Connection,若是有調用 StreamAllocation.acquire給全局變量connection賦值
          if (connection != null) {//上面的Internal.instance.get取到了緩存的 connection直接返回
            foundPooledConnection = true;
            result = connection;
            this.route = route;
            break;
          }
        }
      }
      if (!foundPooledConnection) {//沒有取到緩存
        if (selectedRoute == null) {
          selectedRoute = routeSelection.next();
        }
        route = selectedRoute;
        refusedStreamCount = 0;
        result = new RealConnection(connectionPool, selectedRoute);//緩存中沒有,只能建立一個新的
        acquire(result, false);
      }
    }
    if (foundPooledConnection) {
      eventListener.connectionAcquired(call, result);//若是緩存中取到了,調用監聽,這個方法應該在上面的 if (!foundPooledConnection) 前面就好理解了
      return result;//和上面的 if 比 這裏 return 了,不會建立 socket鏈接了
    }
    //這裏是調用socket.connect的主要方法,並調用connectTls方法對 https進行 Handshake並調用 HostnameVerifier.verify 驗證證書, 只有新建立的纔會進入這裏,在RealConnection中會詳細分析
    result.connect( connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled, call, eventListener);
    routeDatabase().connected(result.route());

    Socket socket = null;
    synchronized (connectionPool) {//把這個RealConnection緩存到鏈接池裏
      reportedAcquired = true;

      Internal.instance.put(connectionPool, result);
      if (result.isMultiplexed()) {//是http2.0協議
        socket = Internal.instance.deduplicate(connectionPool, address, this);//調用了releaseAndAcquire把connection從新賦值,並釋放舊的socket
        result = connection;
      }
    }
    closeQuietly(socket);
    eventListener.connectionAcquired(call, result);
    return result;
  }
複製代碼

RealConnection

HostnameVerifier.verify驗證tls證書 (源碼分析2

public void connect(int connectTimeout, int readTimeout, int writeTimeout, boolean connectionRetryEnabled, Call call, EventListener eventListener) {
    if (protocol != null) throw new IllegalStateException("already connected");

    RouteException routeException = null;
    List<ConnectionSpec> connectionSpecs = route.address().connectionSpecs();//選擇https的算法用
    ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);

    if (route.address().sslSocketFactory() == null) {
      if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {//判斷 ssl 的安全性,不支持 CLEARTEXT
        throw new RouteException(new UnknownServiceException("CLEARTEXT communication not enabled for client"));
      }
      String host = route.address().url().host();
      
      if (!Platform.get().isCleartextTrafficPermitted(host)) {
        throw new RouteException(new UnknownServiceException("CLEARTEXT communication to " + host + " not permitted by network security policy"));
      }
    }

    while (true) {
      try {
        if (route.requiresTunnel()) {//若是是https 而且含有代理,須要先建立代理通道
          connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);//先發出一個請求建立通道
          if (rawSocket == null) {
            // We were unable to connect the tunnel but properly closed down our resources.
            break;
          }
        } else {//只建立一個socket
          connectSocket(connectTimeout, readTimeout, call, eventListener);
        }
        establishProtocol(connectionSpecSelector, call, eventListener);//判斷http協議版本,連接socket,並創建 https 使用的加密通道,使用HostnameVerifier.verify驗證tls證書
        eventListener.connectEnd(call, route.socketAddress(), route.proxy(), protocol);
        break;
      } catch (IOException e) {//建立連接失敗,釋放資源
        closeQuietly(socket);
        closeQuietly(rawSocket);
        socket = null;
        rawSocket = null;
        source = null;
        sink = null;
        handshake = null;
        protocol = null;
        http2Connection = null;

        eventListener.connectFailed(call, route.socketAddress(), route.proxy(), null, e);

        if (routeException == null) {
          routeException = new RouteException(e);
        } else {
          routeException.addConnectException(e);
        }

        if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
          throw routeException;
        }
      }
    }
    if (route.requiresTunnel() && rawSocket == null) {//連接太多了
      ProtocolException exception = new ProtocolException("Too many tunnel connections attempted: " + MAX_TUNNEL_ATTEMPTS);
      throw new RouteException(exception);
    }
    if (http2Connection != null) {
      synchronized (connectionPool) {
        allocationLimit = http2Connection.maxConcurrentStreams();//限制http2.0同一個connection的最大的鏈接數
      }
    }
  }
複製代碼

CallServerInterceptor

已經在上面的步驟建立好了鏈接,下面就能夠直接寫入數據了,使用Http1Codec按照 http1.1的格式寫入數據到 socket 中,使用Http2Codec按照 http2的格式寫入數據,注意這裏返回的 Response持有的 Body是已流的形式存在的,使用完後必定要調用 close,不然鏈接池等資源不能釋放。

public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    HttpCodec httpCodec = realChain.httpStream();//在ConnectInterceptor中創建鏈接後賦值,
    StreamAllocation streamAllocation = realChain.streamAllocation();
    RealConnection connection = (RealConnection) realChain.connection();
    Request request = realChain.request();

    long sentRequestMillis = System.currentTimeMillis();

    realChain.eventListener().requestHeadersStart(realChain.call());
    httpCodec.writeRequestHeaders(request);//把 request 中的 header按照 http 協議的各個版本拼接好寫入到 socket 中
    realChain.eventListener().requestHeadersEnd(realChain.call(), request);

    Response.Builder responseBuilder = null;
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {//判斷是否有請求體
      // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
      // Continue" response before transmitting the request body. If we don't get that, return
      // what we did get (such as a 4xx response) without ever transmitting the request body.
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        httpCodec.flushRequest();//若是 get 請求數據大於1k 當前請求不能把數據所有寫入,須要分次
        realChain.eventListener().responseHeadersStart(realChain.call());
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

      if (responseBuilder == null) {
        // Write the request body if the "Expect: 100-continue" expectation was met.
        realChain.eventListener().requestBodyStart(realChain.call());
        long contentLength = request.body().contentLength();
        CountingSink requestBodyOut =  new CountingSink(httpCodec.createRequestBody(request, contentLength));//對寫入的數據大小計數
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);

        request.body().writeTo(bufferedRequestBody);//寫入請求體中的數據
        bufferedRequestBody.close();
        realChain.eventListener()
            .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
      } else if (!connection.isMultiplexed()) {
        // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
        // from being reused. Otherwise we're still obligated to transmit the request body to
        // leave the connection in a consistent state.
        streamAllocation.noNewStreams();//釋放鏈接
      }
    }

    httpCodec.finishRequest();//請求數據所有寫入完成,等待服務器響應

    if (responseBuilder == null) {
      realChain.eventListener().responseHeadersStart(realChain.call());//這個 listener的位置在服務器響應以前,並非已經開始讀取 response 了
      responseBuilder = httpCodec.readResponseHeaders(false);//讀取響應頭
    }
    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();//生成了請求的結果

    realChain.eventListener() .responseHeadersEnd(realChain.call(), response);//在responseHeadersStart和responseHeadersEnd之間包含了讀取響應頭和服務器處理請求的時間
    int code = response.code();//響應碼
    if (forWebSocket && code == 101) {//應當繼續發送請求,請求被服務器接受可是須要更多的請求數據 服務器才能處理
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)
          .build();
    } else {
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))//讀取響應體到FixedLengthSource 或者ChunkedSource 中 使用完必須調用調用close,不然資源不能釋放。
          .build();
    }
    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      streamAllocation.noNewStreams();//響應頭中包含了close須要釋放鏈接
    }
    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {//20四、205不須要返回請求體
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }
    return response;//已經完成了返回了此次請求的響應
複製代碼

add by dingshaoran

相關文章
相關標籤/搜索