OKHttp源碼分析----責(zé)任鏈的最后一環(huán)

  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 (!forWebSocket) {
       interceptors.addAll(client.networkInterceptors());
     }
     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);
 }

OKHttp的責(zé)任鏈可以說是非常經(jīng)典的設(shè)計(jì)模式了英岭。這里說一下責(zé)任鏈的最后一環(huán)。

通過源碼分析,發(fā)現(xiàn)最后一環(huán)是在 CallServerInterceptor

  /** This is the last interceptor in the chain. It makes a network call to the server. */
 public final class CallServerInterceptor implements Interceptor {
 }

具體的請求還是要看intercept方法谁不。

@Override public Response intercept(Chain chain) throws IOException {
                  RealInterceptorChain realChain = (RealInterceptorChain) chain;
    .......
    //寫入請求頭
    realChain.eventListener().requestHeadersStart(realChain.call());
    long sentRequestMillis = System.currentTimeMillis();
    httpCodec.writeRequestHeaders(request);
    realChain.eventListener().requestHeadersEnd(realChain.call(), request);

    Response.Builder responseBuilder = null;
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      ..........
      //寫入請求體
      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();
        .........
    }

    httpCodec.finishRequest();
    //讀取響應(yīng)頭
    if (responseBuilder == null) {
      realChain.eventListener().responseHeadersStart(realChain.call());
      responseBuilder = httpCodec.readResponseHeaders(false);
    }
    //讀取響應(yīng)體
    response = response.newBuilder()
      .body(httpCodec.openResponseBody(response))
      .build();
   .......
    return response;
  }

可以看出關(guān)鍵的實(shí)現(xiàn)步驟,基本都封裝在了httpCodec這個類中徽诲。

 @Override public void writeRequestHeaders(Request request) throws IOException {
    String requestLine = RequestLine.get(
        request, streamAllocation.connection().route().proxy().type());
    writeRequest(request.headers(), requestLine);
  }

  /** 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;
  }

可以看出刹帕,通過for循環(huán)不斷讀取header的內(nèi)容,寫入到sink中谎替。
sink是OKio的類偷溺,也是square公司推出的框架。
繼續(xù)深入這個sink:

    public Http1Codec(OkHttpClient client, StreamAllocation streamAllocation, BufferedSource source,
        BufferedSink sink) {
      this.client = client;
      this.streamAllocation = streamAllocation;
      this.source = source;
      this.sink = sink;
    }

發(fā)現(xiàn)sink是在構(gòu)建http1Codec的時候就傳入進(jìn)來了钱贯。繼續(xù)找sink的初始化位置挫掏,經(jīng)過一番尋找,發(fā)現(xiàn)是在
RealConnection這個類中秩命,同時初始化了Sink和Source

        /** Does all the work necessary to build a full HTTP or HTTPS connection on a raw socket. */
      private void connectSocket(int connectTimeout, int readTimeout, Call call,
          EventListener eventListener) throws IOException {
        Proxy proxy = route.proxy();
        Address address = route.address();

        rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
            ? address.socketFactory().createSocket()
            : new Socket(proxy);
        eventListener.connectStart(call, route.socketAddress(), proxy);
        rawSocket.setSoTimeout(readTimeout);
        try {
          Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
        } catch (ConnectException e) {
          ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
          ce.initCause(e);
          throw ce;
        }
        try {
          source = Okio.buffer(Okio.source(rawSocket));
          sink = Okio.buffer(Okio.sink(rawSocket));
        } catch (NullPointerException npe) {
          if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) {
            throw new IOException(npe);
          }
        }
      }

我們知道http請求的底層實(shí)現(xiàn)是socket尉共,這里就是socket創(chuàng)建和連接的地方,而我們所用到的sink和source也都是在這里進(jìn)行的初始化弃锐。

      source = Okio.buffer(Okio.source(rawSocket));
      sink = Okio.buffer(Okio.sink(rawSocket));

有沒有很熟悉的感覺袄友,我們在學(xué)java基礎(chǔ)的時候,模擬socket連接霹菊,也是獲取一個輸入流剧蚣,輸出流。
客戶端socket:
通過outputStream向socket寫入數(shù)據(jù)旋廷。--------> 等同于這里的sink
通過inputStream讀取socket的數(shù)據(jù)鸠按。---------> 等同于這里的source。

當(dāng)然柳洋,關(guān)于為什么能寫入數(shù)據(jù)到socket待诅,從socket讀取數(shù)據(jù),這里就是更底層的東西了熊镣,能力有限卑雁,暫時不做分析。

繼續(xù)回到剛才的代碼

    long contentLength = request.body().contentLength();
    CountingSink requestBodyOut =
        new CountingSink(httpCodec.createRequestBody(request, contentLength));
    BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);

    request.body().writeTo(bufferedRequestBody);
    bufferedRequestBody.close();

request.body() 有兩種實(shí)現(xiàn)方式绪囱,就是我們經(jīng)常用到的formbody 和 MultipartBody.
這里看writeto()方法测蹲。

      private long writeOrCountBytes(@Nullable BufferedSink sink, boolean countBytes) {
          long byteCount = 0L;

          Buffer buffer;
          if (countBytes) {
            buffer = new Buffer();
          } else {
            buffer = sink.buffer();
          }

          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) {
            byteCount = buffer.size();
            buffer.clear();
          }

          return byteCount;
      }

最后調(diào)取了這個方法。

又看到了熟悉的for循環(huán)鬼吵,可以猜測一下就知道扣甲,這里就是寫入請求體的方法。
看了一下encodeNames的實(shí)現(xiàn):

      FormBody(List<String> encodedNames, List<String> encodedValues) {
        this.encodedNames = Util.immutableList(encodedNames);
        this.encodedValues = Util.immutableList(encodedValues);
      }

可以看到就是key和value的集合。
這里實(shí)現(xiàn)了把請求體寫入到了sink中(sink的buffer中)琉挖。

那么什么時候启泣,是從buffer寫入到sink中呢?

      bufferedRequestBody.close();

close()的時候示辈,把buffer的數(shù)據(jù)寫入到sink中寥茫,也就是寫入到socket連接中。

寫數(shù)據(jù)的邏輯理清之后矾麻,從socket讀數(shù)據(jù)的邏輯就特別清晰了纱耻,直接看關(guān)鍵代碼:

      if (responseBuilder == null) {
        realChain.eventListener().responseHeadersStart(realChain.call());
        responseBuilder = httpCodec.readResponseHeaders(false);
      }

繼續(xù)看httpCodec.readResponseHeaders(false) 這句代碼:

      @Override public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {
        .......
        StatusLine statusLine = StatusLine.parse(readHeaderLine());
        .......
      }

看一下statusline的成員變量:

      public final Protocol protocol;
      public final int code;
      public final String message;

封裝了響應(yīng)碼等信息,具體的實(shí)現(xiàn)還是要到readHeaderline()方法中险耀。

      private String readHeaderLine() throws IOException {
          String line = source.readUtf8LineStrict(headerLimit);
          headerLimit -= line.length();
          return line;
      }

最后找到了source.read()方法弄喘,也就是從socket中讀取數(shù)據(jù)。
最后一步解析響應(yīng)體數(shù)據(jù)甩牺,還是要回到CallServerIntercept的intercept()方法中:

      response = response.newBuilder()
      .body(httpCodec.openResponseBody(response))
      .build();

看了一下httpCodec.openResponseBody(response)里面的代碼:

      if ("chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) {
        Source source = newChunkedSource(response.request().url());
        return new RealResponseBody(contentType, -1L, Okio.buffer(source));
      }

只是初始化了一個chunkSource蘑志,可以看出應(yīng)該是分塊獲取數(shù)據(jù)的。但是并沒有涉及讀數(shù)據(jù)的方法柴灯。

之前我看到這一直找不到在哪里讀取響應(yīng)體卖漫,后來忽然想到,只是封裝bufferSource到responseBody里面赠群,只有真正用的時候才會從source的buffer中讀取出來。
我們一般調(diào)取響應(yīng)的時候旱幼,會用response.body.string(),繼續(xù)看String()方法的源碼:

      public final String string() throws IOException {
        BufferedSource source = source();
        try {
          Charset charset = Util.bomAwareCharset(source, charset());
          return source.readString(charset);
        } finally {
          Util.closeQuietly(source);
        }
      }

這個source()回調(diào)的就是之前的 newChunkSource.
先通過Util.bomAwareCharset(source, charset()) 獲取編碼格式查描,然后調(diào)用source.readString("UTF_8例")的格式讀取字符串。
里面又回調(diào)到了RealBufferSource.readString()方法柏卤,

      @Override public String readString(Charset charset) throws IOException {
      if (charset == null) throw new IllegalArgumentException("charset == null");

      buffer.writeAll(source);
      return buffer.readString(charset);
      }

先看buffer.writeAll(source):

      @Override public long writeAll(Source source) throws IOException {
      if (source == null) throw new IllegalArgumentException("source == null");
      long totalBytesRead = 0;
      for (long readCount; (readCount = source.read(this(Sink), Segment.SIZE)) != -1; ) {
        totalBytesRead += readCount;
      }
      return totalBytesRead;
      }

這里是在Buffer里面實(shí)現(xiàn)的冬三,又是熟悉的for循環(huán)。

     source.read(sink) == sink.write(source)

sink.write()是一種高效的讀寫交互方式缘缚,底層是通過鏈表重指向勾笆,而不是數(shù)據(jù)拷貝實(shí)現(xiàn)的

      @Override public void write(Buffer source, long byteCount) {
      // Move bytes from the head of the source buffer to the tail of this buffer
      }

然后繼續(xù)回到RealBufferSource.readString()源碼,buffer.readString(charset)

      @Override public String readString(long byteCount, Charset charset) throws EOFException {
      ............
      Segment s = head;
      ............
      String result = new String(s.data, s.pos, (int) byteCount, charset);
      ............

    }

如果只是文件的讀寫桥滨,不涉及socket交互就可以分析具體實(shí)現(xiàn)了窝爪,但是能力有限,再深入應(yīng)該就到socket底層了齐媒,猜想實(shí)現(xiàn)原理是:
用戶-----》sink寫入請求-------》socket發(fā)送請求蒲每,將返回?cái)?shù)據(jù)復(fù)制給buffer中的head-------》獲取source------》讀取head。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末喻括,一起剝皮案震驚了整個濱河市邀杏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌唬血,老刑警劉巖望蜡,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件唤崭,死亡現(xiàn)場離奇詭異,居然都是意外死亡脖律,警方通過查閱死者的電腦和手機(jī)浩姥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來状您,“玉大人勒叠,你說我怎么就攤上這事「嗝希” “怎么了眯分?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長柒桑。 經(jīng)常有香客問我弊决,道長,這世上最難降的妖魔是什么魁淳? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任飘诗,我火速辦了婚禮,結(jié)果婚禮上界逛,老公的妹妹穿的比我還像新娘昆稿。我一直安慰自己,他們只是感情好息拜,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布溉潭。 她就那樣靜靜地躺著,像睡著了一般少欺。 火紅的嫁衣襯著肌膚如雪喳瓣。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天赞别,我揣著相機(jī)與錄音畏陕,去河邊找鬼。 笑死仿滔,一個胖子當(dāng)著我的面吹牛惠毁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播堤撵,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼仁讨,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了实昨?” 一聲冷哼從身側(cè)響起洞豁,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后丈挟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刁卜,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年曙咽,在試婚紗的時候發(fā)現(xiàn)自己被綠了蛔趴。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡例朱,死狀恐怖孝情,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情洒嗤,我是刑警寧澤箫荡,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站渔隶,受9級特大地震影響羔挡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜间唉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一绞灼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧呈野,春花似錦低矮、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至姆打,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間肠虽,已是汗流浹背幔戏。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留税课,地道東北人闲延。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像韩玩,于是被迫代替她去往敵國和親垒玲。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內(nèi)容