OkHttp閱讀筆記(五)

先上前幾篇的地址
第一篇
第二篇
第三篇
第四篇
直接從上一篇開(kāi)始,上一篇描述的是ConnectIntercept連接攔截器芽唇,其中提到了如果沒(méi)有復(fù)用連接弛车,那么需要新建一個(gè)連接。
首先要通過(guò)路由選擇者來(lái)轉(zhuǎn)換域名為IP滨砍,那么先看一下這個(gè)的實(shí)現(xiàn)

RouteSelector

路由選擇者往湿,顧名思義就是選擇一個(gè)可用路由妖异,一個(gè)請(qǐng)求要通過(guò)這個(gè)路由到達(dá)指定的主機(jī),從而建立連接领追。
路由選擇者就是為了選擇一個(gè)合適的路徑他膳,接下來(lái)看一下細(xì)節(jié)

public Route next() throws IOException {
    // Compute the next route to attempt.
    // 判斷當(dāng)前是否有下一個(gè)可用的IP地址
    // 第一次進(jìn)入的時(shí)候此處因?yàn)檫€未查找IP地址,則必然為false
    // 后續(xù)進(jìn)入绒窑,比方說(shuō)一開(kāi)始查找IP地址的時(shí)候獲得兩個(gè)節(jié)點(diǎn)棕孙,然后第一個(gè)節(jié)點(diǎn)連接失敗
    // 進(jìn)行重連,此時(shí)會(huì)去使用第二個(gè)節(jié)點(diǎn)
    if (!hasNextInetSocketAddress()) {
      //要嘗試通過(guò)代理去將域名轉(zhuǎn)換為IP些膨,并且存儲(chǔ)起來(lái)
      //此處是查找下一個(gè)代理蟀俊,默認(rèn)只有NO_PROXY一個(gè)子項(xiàng)
      if (!hasNextProxy()) {
        //在獲取節(jié)點(diǎn)的時(shí)候如果有的節(jié)點(diǎn)已經(jīng)被標(biāo)記過(guò)失敗了,那么會(huì)優(yōu)先跳過(guò)
        //但是跳過(guò)之后發(fā)現(xiàn)沒(méi)有其它節(jié)點(diǎn)可用了傀蓉,那么還是會(huì)使用這個(gè)節(jié)點(diǎn)
        //否則拋出無(wú)可用節(jié)點(diǎn)異常欧漱,這個(gè)會(huì)直接在onFailed回調(diào)
        if (!hasNextPostponed()) {
          throw new NoSuchElementException();
        }
        return nextPostponed();
      }
      //lastProxy則為NO_PROXY
      lastProxy = nextProxy();
      //在nextProxy中已經(jīng)通過(guò)InetAddress通過(guò)Host獲得對(duì)應(yīng)IP和端口列表
    }
    //當(dāng)前有可用的節(jié)點(diǎn)
    //這里一開(kāi)始是獲取InetAddress返回的第一個(gè)地址,但是計(jì)數(shù)會(huì)增長(zhǎng)
    //后續(xù)進(jìn)入相當(dāng)于選擇其它節(jié)點(diǎn)葬燎,前提是存在其他節(jié)點(diǎn)
    lastInetSocketAddress = nextInetSocketAddress();
    //構(gòu)建一個(gè)路由
    Route route = new Route(address, lastProxy, lastInetSocketAddress);
    //當(dāng)前查找的節(jié)點(diǎn)之前已經(jīng)被標(biāo)記失敗了误甚,一般這種都是在重連/重定向的場(chǎng)景
    //盡可能避免使用當(dāng)前節(jié)點(diǎn)
    if (routeDatabase.shouldPostpone(route)) {
      postponedRoutes.add(route);//存儲(chǔ)曾經(jīng)被標(biāo)記為失敗的但是還是可以用的節(jié)點(diǎn)
      // We will only recurse in order to skip previously failed routes. They will be tried last.
      return next();
    }

    return route;
  }

這里其實(shí)體現(xiàn)了一個(gè)網(wǎng)絡(luò)請(qǐng)求的基本域名轉(zhuǎn)換原則:
1.首先查找可用的代理,默認(rèn)是系統(tǒng)的DNS
2.通過(guò)當(dāng)前代理來(lái)轉(zhuǎn)換域名為IP谱净,并且存儲(chǔ)起來(lái)
3.使用下一個(gè)可用的IP構(gòu)成路由
4.如果當(dāng)前路由曾經(jīng)請(qǐng)求失敗了窑邦,那么最好先跳過(guò)當(dāng)前路由,如果實(shí)在沒(méi)有可以用的其他路由壕探,那么再使用當(dāng)前路由
接下來(lái)看一下域名轉(zhuǎn)換的細(xì)節(jié)

private void resetNextInetSocketAddress(Proxy proxy) throws IOException {
    // Clear the addresses. Necessary if getAllByName() below throws!
    // 新建一個(gè)IP地址的列表
    inetSocketAddresses = new ArrayList<>();

    String socketHost;
    int socketPort;
    //NO_PROXY默認(rèn)為DIRECT冈钦,則采用連接中的域名和端口
    if (proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.SOCKS) {
      socketHost = address.url().host();
      socketPort = address.url().port();
    } else {
      SocketAddress proxyAddress = proxy.address();
      if (!(proxyAddress instanceof InetSocketAddress)) {
        throw new IllegalArgumentException(
            "Proxy.address() is not an " + "InetSocketAddress: " + proxyAddress.getClass());
      }
      InetSocketAddress proxySocketAddress = (InetSocketAddress) proxyAddress;
      socketHost = getHostString(proxySocketAddress);
      socketPort = proxySocketAddress.getPort();
    }

    if (socketPort < 1 || socketPort > 65535) {
      throw new SocketException("No route to " + socketHost + ":" + socketPort
          + "; port is out of range");
    }

    if (proxy.type() == Proxy.Type.SOCKS) {
      inetSocketAddresses.add(InetSocketAddress.createUnresolved(socketHost, socketPort));
    } else {
      // Try each address for best behavior in mixed IPv4/IPv6 environments.
      // 這里的dns()實(shí)際上是可以在OkHttpClient.Builder中自己設(shè)置的,默認(rèn)是DNS.SYSTEM
      // 內(nèi)部會(huì)通過(guò)InetAddress.getAllByName(hostname)會(huì)返回IP和端口列表
      // 相當(dāng)于默認(rèn)的發(fā)起一個(gè)請(qǐng)求到DNS服務(wù)器李请,獲取當(dāng)前域名對(duì)應(yīng)的IP地址
      List<InetAddress> addresses = address.dns().lookup(socketHost);
      //記錄當(dāng)前域名可能返回的所有IP地址和端口
      for (int i = 0, size = addresses.size(); i < size; i++) {
        InetAddress inetAddress = addresses.get(i);
        inetSocketAddresses.add(new InetSocketAddress(inetAddress, socketPort));
      }
    }
    //標(biāo)記當(dāng)前從第一個(gè)IP地址開(kāi)始選擇
    nextInetSocketAddressIndex = 0;
  }

OkHttpClient中有:dns = Dns.SYSTEM;

Dns SYSTEM = new Dns() {
    @Override public List<InetAddress> lookup(String hostname) throws UnknownHostException {
      if (hostname == null) throw new UnknownHostException("hostname == null");
      return Arrays.asList(InetAddress.getAllByName(hostname));//這里會(huì)返回IP瞧筛、端口等數(shù)據(jù)的liebiao
    }
  };

實(shí)際上域名到IP的映射就是由DNS完成,默認(rèn)的DNS就是平常說(shuō)的DNS服務(wù)器导盅,內(nèi)部維持一張映射表這種概念较幌,將域名轉(zhuǎn)換為對(duì)應(yīng)的IP地址。
實(shí)際使用中可能會(huì)存在有的DNS緩存導(dǎo)致個(gè)別節(jié)點(diǎn)失效的問(wèn)題白翻,這種的解決方案可以通過(guò)自定義dns類(lèi)來(lái)添加映射方案乍炉,比方說(shuō)在系統(tǒng)的請(qǐng)求失敗之后重連的時(shí)候,使用自定的dns轉(zhuǎn)換映射模式滤馍,這也是常說(shuō)的HttpDNS方案岛琼。
到這里,路由已經(jīng)確定了巢株,那么接下來(lái)需要回到RealConnection中開(kāi)始socket連接了

RealConnection

實(shí)際上在java中連接的建立都是通過(guò)socket槐瑞,通過(guò)一個(gè)socket,指定服務(wù)器的IP地址和端口纯续,這樣就可以嘗試去訪問(wèn)某一臺(tái)主機(jī)了随珠∶鹪看一下具體的細(xì)節(jié)

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

    RouteException routeException = null;
    //如果是Https協(xié)議猬错,那么這里會(huì)涉及到一些加解密的套件窗看,Http則為空
    //這里就是獲取所支持的套件乙各,可以通過(guò)在OkHttpClient中自定義
    List<ConnectionSpec> connectionSpecs = route.address().connectionSpecs();
    //輔助進(jìn)行加解密套件選擇的選擇器
    ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);

    if (route.address().sslSocketFactory() == null) {
      if (!connectionSpecs.contains(ConnectionSpec.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"));
      }
    }
    //嘗試去建立socket連接翻具,有的時(shí)候單次失敗慰毅,可以繼續(xù)嘗試
    //除非在應(yīng)用層指定了不可重連或者一些嚴(yán)重錯(cuò)誤窃祝,重連沒(méi)有意義
    while (true) {
      try {
        if (route.requiresTunnel()) {
          connectTunnel(connectTimeout, readTimeout, writeTimeout);
        } else {//一般來(lái)說(shuō)都是NO_PROXY模式盅蝗,則不會(huì)使用隧道迈着,這里只是進(jìn)行Scoket的連接
          connectSocket(connectTimeout, readTimeout);
          //至此TCP3次握手結(jié)束竿拆,并且初始化了雙向流通道
        }
        //這里面主要是進(jìn)行SSL握手挑辆,并且如果當(dāng)前請(qǐng)求協(xié)議為Http2.0鳖藕,則需要?jiǎng)?chuàng)建Http2Connection
        establishProtocol(connectionSpecSelector);
        break;
      } catch (IOException e) {
        //出現(xiàn)異常的時(shí)候先清理當(dāng)前異常的連接數(shù)據(jù)
        closeQuietly(socket);
        closeQuietly(rawSocket);
        socket = null;
        rawSocket = null;
        source = null;
        sink = null;
        handshake = null;
        protocol = null;
        http2Connection = null;

        if (routeException == null) {
          routeException = new RouteException(e);
        } else {
          routeException.addConnectException(e);
        }
        // 如果在OkHttpClient中設(shè)置不允許重連魔慷,
        // 或者存在一些嚴(yán)重的錯(cuò)誤,重連沒(méi)有意義
        // 那么在一次連接失敗之后就會(huì)結(jié)束
        if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
          throw routeException;
        }
      }
    }
    //如果創(chuàng)建的連接為HTTP2.0著恩,不同于HTTP1相關(guān)的協(xié)議
    //HTTP2.0允許一個(gè)連接當(dāng)中有多個(gè)流
    //Connection中默認(rèn)最多StramAllocation為1
    //在Http2下可以為無(wú)限大
    if (http2Connection != null) {
      synchronized (connectionPool) {
        allocationLimit = http2Connection.maxConcurrentStreams();
      }
    }
  }

思路比較清晰
1.首先建立socket連接院尔,首先是TCP的3次握手過(guò)程
2.然后如果當(dāng)前請(qǐng)求協(xié)議為Https,那么要進(jìn)行SSL的握手和校驗(yàn)流程
3.如果當(dāng)前連接失敗喉誊,在滿足條件的情況下可以嘗試重新進(jìn)行連接
稍微看一下連接的實(shí)現(xiàn)細(xì)節(jié):

private void connectSocket(int connectTimeout, int readTimeout) throws IOException {
    //這里一般說(shuō)都是NO_PROXY
    Proxy proxy = route.proxy();
    Address address = route.address();
    //NO_PROXY模式為DIRECT邀摆,則需要通過(guò)Client中的SocketFactory來(lái)創(chuàng)建Socket
    //如果沒(méi)有在Client中手動(dòng)設(shè)置,默認(rèn)使用DefaultSocketFactory
    //其實(shí)就是new socket()而已
    rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
        ? address.socketFactory().createSocket()
        : new Socket(proxy);
    //設(shè)置Socket中InputStream的讀取最長(zhǎng)時(shí)間伍茄,簡(jiǎn)單理解就是Server的響應(yīng)時(shí)間
    rawSocket.setSoTimeout(readTimeout);
    try {
      //這里本質(zhì)上就是socket.connect
      //內(nèi)部實(shí)際上進(jìn)行的就是傳統(tǒng)的TCP3次握手栋盹,連接時(shí)長(zhǎng)也就是握手成功最大時(shí)長(zhǎng)
      //這里成功之后套接字連接就完成,接下來(lái)可以進(jìn)行流的操作
      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;
    }
    //在TCP握手結(jié)束之后敷矫,連接已經(jīng)建立例获,之后就是建立雙向的流通道
    //這里通過(guò)Okio對(duì)于socket方來(lái)說(shuō)建立輸入和輸出流
    //輸入流用于接收服務(wù)端的數(shù)據(jù),輸出流用于向服務(wù)端寫(xiě)入數(shù)據(jù)
    source = Okio.buffer(Okio.source(rawSocket));
    sink = Okio.buffer(Okio.sink(rawSocket));
  }

@Override public void connectSocket(Socket socket, InetSocketAddress address,
      int connectTimeout) throws IOException {
    try {
      socket.connect(address, connectTimeout);
    } catch (AssertionError e) {
      if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);
      throw e;
    } catch (SecurityException e) {
      // Before android 4.3, socket.connect could throw a SecurityException
      // if opening a socket resulted in an EACCES error.
      IOException ioException = new IOException("Exception in connect");
      ioException.initCause(e);
      throw ioException;
    }
  }

可以看到曹仗,在Android平臺(tái)上榨汤,就是通過(guò)socket連接到之前選擇的路由所對(duì)應(yīng)的IP地址和端口的主機(jī)而已。
這里稍微注意一下幾個(gè)時(shí)間的設(shè)置整葡,這些都是可以在OkHttpClient中設(shè)置的屬性

  //在ConnectInterceptor中調(diào)用件余,在建立連接之后來(lái)生成Codec
  public HttpCodec newCodec(
      OkHttpClient client, StreamAllocation streamAllocation) throws SocketException {
    //注意一個(gè)小區(qū)別,HTTP2在一次TCP連接中可能有多個(gè)流操作遭居,對(duì)應(yīng)的讀取/寫(xiě)入超時(shí)時(shí)間應(yīng)該從開(kāi)始寫(xiě)入頭部報(bào)文開(kāi)始
    //此時(shí)一個(gè)流操作正式開(kāi)始
    //對(duì)于HTTP1.1來(lái)說(shuō)啼器,一次TCP連接中雖然也可以有多次請(qǐng)求,但是流操作是在同一個(gè)流中進(jìn)行的
    //那么完全可以在初始化的時(shí)候設(shè)置讀取/寫(xiě)入超時(shí)時(shí)間
    if (http2Connection != null) {//HTTP2.0
      return new Http2Codec(client, streamAllocation, http2Connection);
    } else {
      socket.setSoTimeout(client.readTimeoutMillis());
      source.timeout().timeout(client.readTimeoutMillis(), MILLISECONDS);
      sink.timeout().timeout(client.writeTimeoutMillis(), MILLISECONDS);
      return new Http1Codec(client, streamAllocation, source, sink);
    }
  }

      //socket連接超時(shí)時(shí)間
      connectTimeout = 10_000;
      //從服務(wù)端讀取響應(yīng)報(bào)文的超時(shí)時(shí)間
      readTimeout = 10_000;
      //向服務(wù)端寫(xiě)入請(qǐng)求報(bào)文的超時(shí)時(shí)間
      writeTimeout = 10_000;

接下來(lái)看一下TLS的處理相關(guān):

private void connectTls(ConnectionSpecSelector connectionSpecSelector) throws IOException {
    Address address = route.address();
    //獲得SSLSocket的工廠俱萍,這個(gè)默認(rèn)為DefaultSocketFactory
    //內(nèi)部沒(méi)有任何策略端壳,只有創(chuàng)建socket而已
    SSLSocketFactory sslSocketFactory = address.sslSocketFactory();
    boolean success = false;
    SSLSocket sslSocket = null;
    try {
      // Create the wrapper over the connected socket.
      // 注意此時(shí)的Socket已經(jīng)完成握手過(guò)程,這里通過(guò)SSLSocketFactory的策略來(lái)構(gòu)建對(duì)應(yīng)的SSLSocket
      // 如果沒(méi)有手動(dòng)設(shè)置枪蘑,默認(rèn)可以看OkHttpClient中的DefaultSslSocketFactory
      sslSocket = (SSLSocket) sslSocketFactory.createSocket(
          rawSocket, address.url().host(), address.url().port(), true /* autoClose */);

      // Configure the socket's ciphers, TLS versions, and extensions.
      // ConnectionSpec的默認(rèn)值可以看OkHttpClient中DEFAULT_CONNECTION_SPECS
      // 此處主要是查找滿足SSLSocket的TLS版本和加解密套件损谦,并且關(guān)聯(lián)SSLSocket
      ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
      // 此處必然為true
      if (connectionSpec.supportsTlsExtensions()) {
        //注意這里的實(shí)現(xiàn)類(lèi)是AndroidPlatform
        //這里面主要是通過(guò)反射岖免,然后用SSLSocket調(diào)用一些方法,來(lái)實(shí)現(xiàn)一些設(shè)置
        //比方Session照捡、Host和ALPN颅湘,細(xì)節(jié)不太清楚
        Platform.get().configureTlsExtensions(
            sslSocket, address.url().host(), address.protocols());
      }

      // Force handshake. This can throw!
      //開(kāi)始TLS/SSL握手流程,
      sslSocket.startHandshake();
      //注意getSession會(huì)阻塞直到握手完成栗精,此時(shí)獲取握手的信息
      //這個(gè)Session包括服務(wù)端返回的TLS版本闯参、Cipher組件、證書(shū)等信息
      Handshake unverifiedHandshake = Handshake.get(sslSocket.getSession());

      // Verify that the socket's certificates are acceptable for the target host.
      // SSL握手完成之后悲立,要嘗試驗(yàn)證主機(jī)名鹿寨,如果嚴(yán)重失敗會(huì)拋出異常,則會(huì)一路回調(diào)到onFailure中
      if (!address.hostnameVerifier().verify(address.url().host(), sslSocket.getSession())) {
        X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0);
        throw new SSLPeerUnverifiedException("Hostname " + address.url().host() + " not verified:"
            + "\n    certificate: " + CertificatePinner.pin(cert)
            + "\n    DN: " + cert.getSubjectDN().getName()
            + "\n    subjectAltNames: " + OkHostnameVerifier.allSubjectAltNames(cert));
      }

      // Check that the certificate pinner is satisfied by the certificates presented.
      // CertificatePining證書(shū)鎖定薪夕,用于校驗(yàn)證書(shū)的有效性脚草,避免一些中間人攻擊
      // 在完成主機(jī)名驗(yàn)證之后,此處進(jìn)行的是證書(shū)認(rèn)證
      // 具體可以看CertificatePinner的注釋中的demo
      address.certificatePinner().check(address.url().host(),
          unverifiedHandshake.peerCertificates());

      // Success! Save the handshake and the ALPN protocol.
      // 嘗試通過(guò)反射getAlpnSelectedProtocol獲取ALPN協(xié)議并保存
      String maybeProtocol = connectionSpec.supportsTlsExtensions()
          ? Platform.get().getSelectedProtocol(sslSocket)
          : null;
      socket = sslSocket;
      source = Okio.buffer(Okio.source(socket));
      sink = Okio.buffer(Okio.sink(socket));
      handshake = unverifiedHandshake;
      protocol = maybeProtocol != null
          ? Protocol.get(maybeProtocol)
          : Protocol.HTTP_1_1;
      success = true;
    } catch (AssertionError e) {
      if (Util.isAndroidGetsocknameError(e)) throw new IOException(e);
      throw e;
    } finally {
      if (sslSocket != null) {
        Platform.get().afterHandshake(sslSocket);
      }
      if (!success) {
        closeQuietly(sslSocket);
      }
    }
  }

假設(shè)當(dāng)前為Https請(qǐng)求:
1.進(jìn)行SSL握手流程原献,這個(gè)主要就是生成隨機(jī)密鑰馏慨,證書(shū)校驗(yàn)這些流程,在握手完成之后嚼贡,當(dāng)前Https連接建立就完成了熏纯。
2.進(jìn)行主機(jī)名校驗(yàn),這個(gè)校驗(yàn)可以在OkHttpClient中自定義粤策。默認(rèn)的實(shí)現(xiàn)是校驗(yàn)證書(shū)中的域名或者IP
3.進(jìn)行證書(shū)校驗(yàn)樟澜,這個(gè)校驗(yàn)可以在OkHttpClient中自定義。通過(guò)指定公鑰來(lái)對(duì)應(yīng)一個(gè)簽名證書(shū)叮盘,使用的時(shí)候可能會(huì)導(dǎo)致服務(wù)端沒(méi)有辦法隨意更換證書(shū)秩贰。
至此,ConnectInterceptor的工作就差不多了柔吼,說(shuō)了那么多毒费,其實(shí)就是獲取連接這個(gè)工作,接著看下一個(gè)攔截器吧:

if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    //在已經(jīng)建立的鏈接上進(jìn)行參數(shù)發(fā)送和獲取響應(yīng)封裝等操作
    interceptors.add(new CallServerInterceptor(forWebSocket));

NetworkInterceptors

這個(gè)其實(shí)也是自定義攔截器愈魏,在OkHttpClient.Builder中設(shè)置

public Builder addNetworkInterceptor(Interceptor interceptor) {
      networkInterceptors.add(interceptor);
      return this;
    }

其實(shí)之前也提到過(guò)一個(gè)自定義攔截器觅玻,但是那個(gè)攔截器的處理時(shí)機(jī)是整個(gè)請(qǐng)求的開(kāi)始和結(jié)束,NetworkInterceptor則有所不同培漏,在ConnectInterceptor之后執(zhí)行溪厘,這意味著當(dāng)前的連接已經(jīng)建立,這個(gè)的意義個(gè)人覺(jué)得就是在可以第一時(shí)間在連接后和響應(yīng)后進(jìn)行數(shù)據(jù)的處理牌柄,比方說(shuō)修改響應(yīng)的結(jié)果畸悬,在請(qǐng)求之前修改請(qǐng)求頭部報(bào)文之類(lèi)的操作。
因?yàn)樽远x攔截器在重試和跟隨攔截器之前執(zhí)行珊佣,那么不具有修改響應(yīng)的功能蹋宦。
接著看最后一個(gè)攔截器CallServerInterceptor吧:

CallServerInterceptor

調(diào)用服務(wù)端攔截器披粟,其實(shí)就是和服務(wù)端進(jìn)行通信,主要就是發(fā)送請(qǐng)求報(bào)文和接收響應(yīng)報(bào)文這兩個(gè)操作

@Override public Response intercept(Chain chain) throws IOException {
    //獲得在RealInterceptorChain中的基本參數(shù)
    HttpCodec httpCodec = ((RealInterceptorChain) chain).httpStream();
    StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
    Request request = chain.request();

    long sentRequestMillis = System.currentTimeMillis();
    //到這里之前socket連接已經(jīng)建立冷冗,流通道也已經(jīng)準(zhǔn)備完成
    // 開(kāi)始將請(qǐng)求報(bào)文的一部分寫(xiě)入流中
    // 起始行
    // 頭部報(bào)文
    httpCodec.writeRequestHeaders(request);

    Response.Builder responseBuilder = null;
    //驗(yàn)證頭部請(qǐng)求method合法且具有請(qǐng)求體內(nèi)容守屉,比方此時(shí)是POST,那么需要一個(gè)正文體
    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();
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

      // Write the request body, unless an "Expect: 100-continue" expectation failed.
      if (responseBuilder == null) {//正常情況下贾惦,將requestBody寫(xiě)進(jìn)輸出流胸梆,這里一般就是往服務(wù)端傳POST的參數(shù)
        //構(gòu)建一個(gè)可以用于承載一定數(shù)量字節(jié)的流
        Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
        //使用緩沖流敦捧,簡(jiǎn)單理解就是類(lèi)似于BufferedStream
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
        //將request中的body寫(xiě)入sink中
        request.body().writeTo(bufferedRequestBody);
        //關(guān)閉流
        bufferedRequestBody.close();
      }
    }
    //關(guān)閉輸出流须板,這里可以認(rèn)為一次請(qǐng)求的發(fā)送已經(jīng)完成,剩下的就是等待服務(wù)端的返回響應(yīng)
    httpCodec.finishRequest();

    if (responseBuilder == null) {
      //此處是獲得響應(yīng)報(bào)文的起始行兢卵,method code reason
      //然后再讀取頭部報(bào)文
      responseBuilder = httpCodec.readResponseHeaders(false);
    }
    //到此處如果responseBuilder不為空习瑰,說(shuō)明已經(jīng)正常接收到起始行和頭部報(bào)文
    //可以設(shè)置握手結(jié)果、發(fā)送時(shí)間和接收到響應(yīng)的時(shí)間
    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    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 {
      //設(shè)置響應(yīng)中的正文體秽荤,注意此處只是關(guān)聯(lián)了source供應(yīng)流而已
      //大致也可以理解OkHttp的回調(diào)默認(rèn)是在子線程中甜奄,從該線程中獲取實(shí)際的body
      //比方說(shuō)String類(lèi)型是要經(jīng)過(guò)IO操作的,所以說(shuō)回調(diào)是合理的
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))
          .build();
      //至此整個(gè)網(wǎng)絡(luò)請(qǐng)求流程已經(jīng)完成
    }

    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      //如果當(dāng)前請(qǐng)求或者響應(yīng)報(bào)文中指定當(dāng)前為短連接窃款,那么當(dāng)前連接不應(yīng)該進(jìn)入連接池去復(fù)用
      streamAllocation.noNewStreams();
    }
    //204和205是無(wú)內(nèi)容狀態(tài)碼课兄,所以此時(shí)不應(yīng)該有正文體
    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }

    return response;
    //到這里一次請(qǐng)求以及響應(yīng)的數(shù)據(jù)設(shè)置就全部完成,剩下的會(huì)按照調(diào)用鏈向上返回
  }

可以看到晨继,報(bào)文的寫(xiě)入細(xì)節(jié)都委托到Codec里面執(zhí)行了烟阐,而對(duì)于Http1.1來(lái)說(shuō),實(shí)現(xiàn)類(lèi)為Http1Codec紊扬,首先看一下起始行和頭部報(bào)文的寫(xiě)入格式

@Override public void writeRequestHeaders(Request request) throws IOException {
    //拼接請(qǐng)求報(bào)文中起始行數(shù)據(jù)
    String requestLine = RequestLine.get(
        request, streamAllocation.connection().route().proxy().type());
    //寫(xiě)入起始行和頭部報(bào)文數(shù)據(jù)
    writeRequest(request.headers(), requestLine);
  }

public static String get(Request request, Proxy.Type proxyType) {
    //此處是拼接請(qǐng)求報(bào)文起始行
    StringBuilder result = new StringBuilder();
    //POST
    result.append(request.method());
    result.append(' ');
    //默認(rèn)是NO_PROXY
    if (includeAuthorityInRequestLine(request, proxyType)) {
      result.append(request.url());
    } else {
      //一般是此處蜒茄,Host+參數(shù)的格式
      result.append(requestPath(request.url()));
    }
    //最后拼接協(xié)議,注意有空格
    result.append(" HTTP/1.1");
    //總結(jié)一下請(qǐng)求報(bào)文的起始行格式餐屎,比方說(shuō)請(qǐng)求http://www.hh.com?a=1的一個(gè)POST無(wú)代理HTTP1.1協(xié)議請(qǐng)求
    //此處注意空格檀葛,這個(gè)是協(xié)議的一部分
    //POST www.hh.com?a=1 HTTP1.1
    return result.toString();
  }

public void writeRequest(Headers headers, String requestLine) throws IOException {
    //稍微注意一下這個(gè)狀態(tài)標(biāo)記是用于處理當(dāng)前報(bào)文的發(fā)送流程的,在HTTP1.1下請(qǐng)求報(bào)文要滿足先協(xié)議報(bào)文后正文
    if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
    //起始行結(jié)束需要換行操作
    sink.writeUtf8(requestLine).writeUtf8("\r\n");
    //換行之后要開(kāi)始寫(xiě)入頭部報(bào)文數(shù)據(jù)
    for (int i = 0, size = headers.size(); i < size; i++) {
      //基本格式:
      //name: value
      //name1: value1
      //...
      sink.writeUtf8(headers.name(i))
          .writeUtf8(": ")
          .writeUtf8(headers.value(i))
          .writeUtf8("\r\n");
    }
    //最后與正文體之間的識(shí)別符是一個(gè)空行
    sink.writeUtf8("\r\n");
    //標(biāo)記狀態(tài)允許寫(xiě)入請(qǐng)求體
    state = STATE_OPEN_REQUEST_BODY;
  }

用一個(gè)例子總結(jié):
POST www.bai.com?a=1 http1.1
Connection: Keep-Alive
Cache-Control: no-cache; no-store

這個(gè)就是起始行的協(xié)議格式腹缩。
接著看正文體的處理

@Override public Sink createRequestBody(Request request, long contentLength) {
    if ("chunked".equalsIgnoreCase(request.header("Transfer-Encoding"))) {
      // 此處可以回想一下BridgeInterceptor中的情況屿聋,Transfer-Encoding: chunked
      // 相當(dāng)于在RequestBody中的Content-Length為-1,簡(jiǎn)單理解就是無(wú)法預(yù)測(cè)長(zhǎng)度
      // Stream a request body of unknown length.
      // 會(huì)根據(jù)需要一直讀
      return newChunkedSink();
    }

    if (contentLength != -1) {
      // 只能讀取指定的字節(jié)數(shù)
      // Stream a request body of a known length.
      return newFixedLengthSink(contentLength);
    }

    throw new IllegalStateException(
        "Cannot stream a request body without chunked encoding or a known content length!");
  }

@Override public void write(Buffer source, long byteCount) throws IOException {
      if (closed) throw new IllegalStateException("closed");
      checkOffsetAndCount(source.size(), 0, byteCount);
      if (byteCount > bytesRemaining) {
        throw new ProtocolException("expected " + bytesRemaining
            + " bytes but received " + byteCount);
      }
      sink.write(source, byteCount);
      bytesRemaining -= byteCount;
    }

這里其實(shí)也沒(méi)什么注意的藏鹊,總之就是通過(guò)sink來(lái)寫(xiě)數(shù)據(jù)润讥,注意到上面的全局變量sink其實(shí)就是當(dāng)初在建立連接的時(shí)候創(chuàng)建的,也就是對(duì)應(yīng)socket建立后的OutputStream伙判,可以用于向建立連接后的服務(wù)端寫(xiě)入數(shù)據(jù)象对。
接著看接收響應(yīng)報(bào)文的操作

@Override public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {
    //正常來(lái)說(shuō)此時(shí)的狀態(tài)都是STATE_OPEN_REQUEST_BODY
    if (state != STATE_OPEN_REQUEST_BODY && state != STATE_READ_RESPONSE_HEADERS) {
      throw new IllegalStateException("state: " + state);
    }
    //毫無(wú)疑問(wèn),此處是等待阻塞性操作
    try {
      //讀取響應(yīng)的起始行
      //parse內(nèi)部是解析協(xié)議宴抚、狀態(tài)碼等參數(shù)勒魔,然后設(shè)置到StatusLine中甫煞,具體解析可以看parse函數(shù)
      StatusLine statusLine = StatusLine.parse(source.readUtf8LineStrict());

      Response.Builder responseBuilder = new Response.Builder()
          .protocol(statusLine.protocol)
          .code(statusLine.code)
          .message(statusLine.message)
          .headers(readHeaders());//此處是讀取并解析響應(yīng)的頭部報(bào)文

      if (expectContinue && statusLine.code == HTTP_CONTINUE) {
        return null;
      }

      state = STATE_OPEN_RESPONSE_BODY;
      return responseBuilder;
    } catch (EOFException e) {
      // Provide more context if the server ends the stream before sending a response.
      IOException exception = new IOException("unexpected end of stream on " + streamAllocation);
      exception.initCause(e);
      throw exception;
    }
  }

public static StatusLine parse(String statusLine) throws IOException {
    //這個(gè)沒(méi)什么說(shuō)的,參見(jiàn)下面的例子就好
    // 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.")) {
      if (statusLine.length() < 9 || statusLine.charAt(8) != ' ') {
        throw new ProtocolException("Unexpected status line: " + statusLine);
      }
      int httpMinorVersion = statusLine.charAt(7) - '0';
      codeStart = 9;
      if (httpMinorVersion == 0) {
        protocol = Protocol.HTTP_1_0;
      } else if (httpMinorVersion == 1) {
        protocol = Protocol.HTTP_1_1;
      } else {
        throw new ProtocolException("Unexpected status line: " + statusLine);
      }
    } else if (statusLine.startsWith("ICY ")) {
      // Shoutcast uses ICY instead of "HTTP/1.0".
      protocol = Protocol.HTTP_1_0;
      codeStart = 4;
    } else {
      throw new ProtocolException("Unexpected status line: " + statusLine);
    }

    // Parse response code like "200". Always 3 digits.
    if (statusLine.length() < codeStart + 3) {
      throw new ProtocolException("Unexpected status line: " + statusLine);
    }
    int code;
    try {
      code = Integer.parseInt(statusLine.substring(codeStart, codeStart + 3));
    } catch (NumberFormatException e) {
      throw new ProtocolException("Unexpected status line: " + statusLine);
    }

    // Parse an optional response message like "OK" or "Not Modified". If it
    // exists, it is separated from the response code by a space.
    String message = "";
    if (statusLine.length() > codeStart + 3) {
      if (statusLine.charAt(codeStart + 3) != ' ') {
        throw new ProtocolException("Unexpected status line: " + statusLine);
      }
      message = statusLine.substring(codeStart + 4);
    }

    return new StatusLine(protocol, code, message);
  }

可以看到冠绢,首先也是讀取了響應(yīng)的起始行和頭部報(bào)文抚吠,這里也舉一個(gè)例子來(lái)理解:
http1.1 200 OK
Connection: close
Transfer-Encoding: chunked

類(lèi)似這種格式,接下來(lái)看響應(yīng)報(bào)文的正文體處理

@Override public ResponseBody openResponseBody(Response response) throws IOException {
    Source source = getTransferStream(response);
    return new RealResponseBody(response.headers(), Okio.buffer(source));
  }

private Source getTransferStream(Response response) throws IOException {
    if (!HttpHeaders.hasBody(response)) {
      return newFixedLengthSource(0);
    }
    //分塊傳輸模式弟胀,一般就是不確定當(dāng)前數(shù)據(jù)大小的時(shí)候使用
    if ("chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) {
      return newChunkedSource(response.request().url());
    }
    //固定大小楷力,會(huì)指定在頭部報(bào)文的Content-Length中
    long contentLength = HttpHeaders.contentLength(response);
    if (contentLength != -1) {
      return newFixedLengthSource(contentLength);
    }

    // Wrap the input stream from the connection (rather than just returning
    // "socketIn" directly here), so that we can control its use after the
    // reference escapes.
    return newUnknownLengthSource();
  }

實(shí)際上就是封裝了一個(gè)ResponseBody

public final class RealResponseBody extends ResponseBody {
  private final Headers headers;
  private final BufferedSource source;

  public RealResponseBody(Headers headers, BufferedSource source) {
    this.headers = headers;
    this.source = source;
  }

  @Override public MediaType contentType() {
    String contentType = headers.get("Content-Type");
    return contentType != null ? MediaType.parse(contentType) : null;
  }

  @Override public long contentLength() {
    return HttpHeaders.contentLength(headers);
  }

  @Override public BufferedSource source() {
    return source;
  }
}

也沒(méi)做什么,重點(diǎn)是source孵户,可以看到這里并沒(méi)有去讀取萧朝,稍微看一下newFixedLengthSource的實(shí)現(xiàn):

@Override public long read(Buffer sink, long byteCount) throws IOException {
      if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
      if (closed) throw new IllegalStateException("closed");
      if (bytesRemaining == 0) return -1;

      long read = source.read(sink, Math.min(bytesRemaining, byteCount));
      if (read == -1) {
        endOfInput(false); // The server didn't supply the promised content length.
        throw new ProtocolException("unexpected end of stream");
      }

      bytesRemaining -= read;
      if (bytesRemaining == 0) {//當(dāng)前數(shù)據(jù)讀完了
        //實(shí)際上關(guān)注這里就好了,一旦數(shù)據(jù)讀取完成夏哭,這里就會(huì)去處理連接完成
        //true的話表示連接可以復(fù)用
        //會(huì)釋放連接的引用检柬,標(biāo)記連接池中當(dāng)前連接可以復(fù)用了
        endOfInput(true);
      }
      return read;
    }

實(shí)際上這個(gè)read完成了才算是一個(gè)連接真正完成,然后標(biāo)記當(dāng)前連接可以復(fù)用竖配。
上面的內(nèi)容主要都是一些協(xié)議報(bào)文的內(nèi)容何址,可以看基礎(chǔ)這篇文章。
上述流程關(guān)心一下就好了进胯,平時(shí)的重點(diǎn)可能都在RequestBody和ResponseBody的封裝用爪,其實(shí)從這里可以看到,這兩個(gè)實(shí)際上就是封裝Content-Type胁镐、Content-Length和如何寫(xiě)入數(shù)據(jù)偎血,通過(guò)實(shí)現(xiàn)這兩個(gè)類(lèi),可以做到不同的請(qǐng)求正文體和響應(yīng)正文體希停。

總結(jié)

攔截器的部分就結(jié)束了烁巫,其實(shí)按著當(dāng)前攔截器一路走下來(lái),一個(gè)完整的請(qǐng)求也就完成了宠能,對(duì)于整個(gè)框架的理解也提高了一些亚隙。
OkHttp這個(gè)框架可以說(shuō)非常優(yōu)秀,比方說(shuō)很方便的封裝了dns违崇、通過(guò)Okio簡(jiǎn)化了傳輸數(shù)據(jù)流程的監(jiān)聽(tīng)阿弃、進(jìn)行了連接復(fù)用優(yōu)化、重連機(jī)制等羞延,這些都可以成為選擇這個(gè)框架的理由渣淳。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市伴箩,隨后出現(xiàn)的幾起案子入愧,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件棺蛛,死亡現(xiàn)場(chǎng)離奇詭異怔蚌,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)旁赊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)桦踊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人终畅,你說(shuō)我怎么就攤上這事籍胯。” “怎么了离福?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵杖狼,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我术徊,道長(zhǎng)本刽,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任赠涮,我火速辦了婚禮,結(jié)果婚禮上暗挑,老公的妹妹穿的比我還像新娘笋除。我一直安慰自己,他們只是感情好炸裆,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布垃它。 她就那樣靜靜地躺著,像睡著了一般烹看。 火紅的嫁衣襯著肌膚如雪国拇。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,394評(píng)論 1 310
  • 那天惯殊,我揣著相機(jī)與錄音酱吝,去河邊找鬼。 笑死土思,一個(gè)胖子當(dāng)著我的面吹牛务热,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播己儒,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼崎岂,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了闪湾?” 一聲冷哼從身側(cè)響起冲甘,我...
    開(kāi)封第一講書(shū)人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后江醇,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體省艳,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年嫁审,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了跋炕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡律适,死狀恐怖辐烂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情捂贿,我是刑警寧澤纠修,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站厂僧,受9級(jí)特大地震影響扣草,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜颜屠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一辰妙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧甫窟,春花似錦密浑、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至浇衬,卻和暖如春懒构,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背耘擂。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工胆剧, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人梳星。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓赞赖,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親冤灾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子前域,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

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