HttpURLConnection 源碼分析

1 概述

在Android應(yīng)用中大都會(huì)使用Http協(xié)議來(lái)訪(fǎng)問(wèn)網(wǎng)絡(luò), Android主要提供了兩種方式(HttpURLConnection僚楞、HttpClient)來(lái)進(jìn)行Http操作,那么應(yīng)該使用那種方式訪(fǎng)問(wèn)網(wǎng)絡(luò)呢鹏往?
關(guān)于這個(gè)話(huà)題大家可以產(chǎn)考Android訪(fǎng)問(wèn)網(wǎng)絡(luò)摇庙,使用HttpURLConnection還是HttpClient?,
最后我要補(bǔ)充一句牙言,現(xiàn)在的應(yīng)用最低版本都不會(huì)低于Android2.3,所以根據(jù)上面文章的結(jié)論怪得,應(yīng)該使用HttpURLConnection進(jìn)行網(wǎng)絡(luò)請(qǐng)求咱枉,并且在Android6.0版本中HttpClient已經(jīng)被移除。

在TCP/IP的五層協(xié)議模型中徒恋,Socket是對(duì)傳輸層協(xié)議的封裝蚕断,Socket本身并不是協(xié)議,而是一個(gè)調(diào)用接口(API)入挣,目前傳輸層協(xié)議有TCP亿乳、UDP協(xié)議兩種,Socket可以指定使用的傳輸層協(xié)議,當(dāng)使用TCP協(xié)議進(jìn)行連接時(shí)葛假,該Socket連接就是一個(gè)TCP連接障陶。HttpURLConnection建立的連接是TCP連接,TCP連接的建立需要經(jīng)過(guò)三次握手過(guò)程聊训,然后開(kāi)始慢啟動(dòng)抱究。

2 預(yù)備知識(shí)

2.1 HTTP的發(fā)展史

1> 1996年HTTP/1.0誕生
在發(fā)送請(qǐng)求之前,建立一個(gè)新的TCP連接带斑,在這個(gè)請(qǐng)求完成后鼓寺,該TCP連接就會(huì)被關(guān)閉,因此每發(fā)送一條請(qǐng)求都需要重新建立TCP連接勋磕,這樣就會(huì)導(dǎo)致性能消耗和延遲妈候,因此HttpURLConnection通過(guò)添加請(qǐng)求頭字段Connection: keep-alive來(lái)優(yōu)化這個(gè)問(wèn)題,但這并沒(méi)有得到廣泛支持挂滓,所以問(wèn)題仍然存在苦银;HttpURLConnection通過(guò)連接池(ConnectionPool)來(lái)優(yōu)化這個(gè)問(wèn)題,連接池中TCP連接的數(shù)量是沒(méi)有上限的赶站,理論上說(shuō)可以并發(fā)處理無(wú)數(shù)個(gè)HTTP請(qǐng)求墓毒,但是服務(wù)端肯定是不允許這樣的事情發(fā)生。

2> 1999年HTTP/1.1誕生
HTTP/1.1規(guī)范規(guī)定使用長(zhǎng)連接(Persistent Connection)亲怠,這樣的話(huà)可以在同一個(gè)TCP連接上發(fā)送多個(gè)請(qǐng)求,一定程度上彌補(bǔ)了HTTP1.0每次發(fā)送請(qǐng)求都要?jiǎng)?chuàng)建連接導(dǎo)致的性能消耗和延遲柠辞,如果要關(guān)閉連接团秽,需要添加請(qǐng)求頭字段Connection: close;同時(shí)為了降低等待響應(yīng)的耗時(shí)叭首,HTTP/1.1規(guī)范提出管線(xiàn)化(Pipelining)來(lái)實(shí)現(xiàn)在在單個(gè)TCP連接上發(fā)送多個(gè)請(qǐng)求而不等待相應(yīng)響應(yīng)习勤,但是服務(wù)器必須按照收到請(qǐng)求的順序發(fā)送響應(yīng),這時(shí)多個(gè)響應(yīng)有可能會(huì)被阻塞的焙格,通過(guò)下圖你可以更加清晰的理解Pipelining的實(shí)現(xiàn)原理:


HttpURLConnection目前是不支持管線(xiàn)化的图毕,因此HttpURLConnection中使用上圖左側(cè)的方式處理 請(qǐng)求-響應(yīng)的。

3> 2015年HTTP/2誕生
官方HTTP/2規(guī)范
中文版HTTP/2規(guī)范眷唉,謝謝作者的分享予颤。
HTTP/2規(guī)范提出了多路復(fù)用(Multiplexing)來(lái)實(shí)現(xiàn)多個(gè)請(qǐng)求在一個(gè)連接上并發(fā),避免了HTTP/1.1中的多個(gè)響應(yīng)可能會(huì)被阻塞的問(wèn)題冬阳,通過(guò)下圖你可以更加清晰的理解多路復(fù)用的實(shí)現(xiàn)原理:


上圖中間部分代表TCP連接蛤虐,4行代表4個(gè)并行的Stream,每一個(gè)方塊可以理解成一個(gè)Frame肝陪,在HTTP/2中請(qǐng)求或者響應(yīng)會(huì)被分割成多個(gè)Frame被傳遞驳庭,官方規(guī)范HTTP Frames描述了Frame的格式:

 +-----------------------------------------------+
 |                 Length (24)                   |
 +---------------+---------------+---------------+
 |   Type (8)    |   Flags (8)   |
 +-+-------------+---------------+-------------------------------+
 |R|                 Stream Identifier (31)                      |
 +=+=============================================================+
 |                   Frame Payload (0...)                      ...
 +---------------------------------------------------------------+

Frame由首部(9個(gè)字節(jié)長(zhǎng)度)和負(fù)載(上圖中的Frame Payload空間,用來(lái)負(fù)載請(qǐng)求或者響應(yīng)的空間)兩部分組成,首部由上圖中的前5個(gè)區(qū)域組成:
Length:代表負(fù)載的長(zhǎng)度饲常,默認(rèn)最大值為2^14 (16384)蹲堂,如果想將最大值設(shè)置為[2^14 + 1,2^24 - 1]范圍中的值贝淤,那么可以通過(guò)發(fā)送
SETTINGS類(lèi)型的Frame來(lái)說(shuō)明發(fā)送方可以接受的最大負(fù)載大小柒竞。
Type:表示幀的類(lèi)型,官方HTTP/2規(guī)范一共定義了10種Frame類(lèi)型霹娄,其中HEADERS能犯、PUSH_PROMISECONTINUATION三個(gè)類(lèi)型是用來(lái)負(fù)載請(qǐng)求頭或者響應(yīng)頭的犬耻,具體可以參考Header Compression and Decompression踩晶;其中DATA類(lèi)型是用來(lái)負(fù)載請(qǐng)求體或者響應(yīng)體的。
Flags:用來(lái)表達(dá)一種語(yǔ)義枕磁,比如
END_HEADERS:在HEADERS渡蜻、PUSH_PROMISECONTINUATION類(lèi)型的Frame中计济,表達(dá)請(qǐng)求頭或者響應(yīng)頭結(jié)束茸苇。
END_STREAM:表達(dá)請(qǐng)求或者響應(yīng)結(jié)束。
R: 保留位沦寂。
Stream Identifier: Stream標(biāo)識(shí)学密,用來(lái)標(biāo)識(shí)該Frame來(lái)自于那個(gè)Stream。

在 HTTP/2 中一對(duì) 請(qǐng)求-響應(yīng) 就對(duì)應(yīng)一個(gè)Stream传藏,換句話(huà)說(shuō)一個(gè)Stream的任務(wù)就是完成一對(duì) 請(qǐng)求-響應(yīng) 的傳輸腻暮,然后就沒(méi)用了;接收端通過(guò)Frame中攜帶的StreamId(即Stream Identifier)來(lái)區(qū)分不同的請(qǐng)求或者響應(yīng)毯侦,接著連接Frame得到得到完整的請(qǐng)求或者響應(yīng)哭靖。

注意:請(qǐng)求頭或者響應(yīng)頭必須作為一個(gè)連續(xù)的Frame序列傳送,不能有任何其他類(lèi)型或任何其他Stream上的交錯(cuò)Frame,侈离。

2.2 Upgrade協(xié)商機(jī)制和APLN協(xié)商機(jī)制

用于協(xié)商使用的應(yīng)用層協(xié)議:
1> Upgrade協(xié)商機(jī)制(HTTP/1.1引入的Upgrade 機(jī)制)
只有在不使用SSL/TLS的情況下试幽,在協(xié)商使用那個(gè)應(yīng)用層協(xié)議時(shí)才會(huì)用到Upgrade協(xié)商機(jī)制,客戶(hù)端通過(guò)發(fā)送一個(gè)包含請(qǐng)求頭字段為 Upgrade:協(xié)議名稱(chēng)列表 的HTTP/1.1請(qǐng)求來(lái)發(fā)起應(yīng)用層協(xié)議的協(xié)商卦碾,例如:

GET / HTTP/1.1
Host: server.example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>

客戶(hù)端通過(guò)Upgrade請(qǐng)求頭字段中的協(xié)議列表(按照優(yōu)先級(jí)降序排列)建議服務(wù)端切換到其中的某個(gè)協(xié)議铺坞,上面的請(qǐng)求建議服務(wù)端使用h2c協(xié)議(HTTP/2協(xié)議直接運(yùn)行在TCP協(xié)議之上,沒(méi)有中間層SSL/TLS)洲胖,如果服務(wù)端同意使用Upgrade列舉的協(xié)議康震,就會(huì)給出如下響應(yīng):

HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: h2c

[ HTTP/2 connection ...

如果服務(wù)端不同意或者不支持Upgrade列舉的協(xié)議,就會(huì)直接忽略(當(dāng)成 HTTP/1.1 請(qǐng)求宾濒,給出HTTP/1.1 響應(yīng)):

HTTP/1.1 200 OK
Content-Length: 243
Content-Type: text/html

...

2> APLN協(xié)商機(jī)制
首先通過(guò)下圖來(lái)了解HTTPS HTTP SSL/TLS TCP之間的關(guān)系:



網(wǎng)景公司(Netscape)開(kāi)發(fā)了原始的SSL(Secure Sockets Layer)協(xié)議腿短,由于協(xié)議中嚴(yán)重的安全漏洞沒(méi)有發(fā)布1.0版;版本2.0在1995年2月發(fā)布,由于包含了一些安全缺陷橘忱,因此需要3.0版本的設(shè)計(jì)赴魁,SSL版本3.0于1996年發(fā)布;SSL 2.0在2011年被RFC 6176禁用钝诚,SSL 3.0在2015年6月也被RFC 7568禁用颖御;1999年1月,TLS(Transport Layer Security) 1.0版本在RFC 2246中被定義為SSL Version 3.0的升級(jí)版凝颇;TLS 1.1是在2006年4月的RFC 4346中定義的潘拱;TLS 1.2是在2008年8月的RFC 5246中定義的。

應(yīng)用層協(xié)議協(xié)商Application-Layer Protocol Negotiation拧略,簡(jiǎn)稱(chēng)ALPN)是一個(gè)TLS擴(kuò)展芦岂。ALPN用于協(xié)商使用的應(yīng)用層協(xié)議,以避免額外的往返協(xié)商通信垫蛆;Google 在 SPDY 協(xié)議中開(kāi)發(fā)了一個(gè)名為 NPN(Next Protocol Negotiation禽最,下一代協(xié)議協(xié)商)的 TLS 擴(kuò)展。隨著 SPDY 被 HTTP/2 取代(2015年9月袱饭,Google 宣布了計(jì)劃川无,移除對(duì)SPDY的支持,擁抱 HTTP/2虑乖,并將在Chrome 51中生效懦趋。),NPN 也被官方修訂為 ALPN(Application Layer Protocol Negotiation疹味,應(yīng)用層協(xié)議協(xié)商)仅叫;HttpURLConnection中使用ALPN進(jìn)行應(yīng)用層協(xié)議的協(xié)商,在TLS握手的Client Hello中佛猛,客戶(hù)端會(huì)通過(guò)ALPN 擴(kuò)展列出自己支持的各種應(yīng)用層協(xié)議,按照優(yōu)先級(jí)降序排列坠狡,服務(wù)端如果支持其中某個(gè)應(yīng)用層協(xié)議继找,就會(huì)在Server Hello中通過(guò)ALPN擴(kuò)展指定協(xié)商的結(jié)果為該應(yīng)用層協(xié)議。

關(guān)于TLS握手階段的流程逃沿,大家可以產(chǎn)考如下文章:
SSL/TLS協(xié)議運(yùn)行機(jī)制的概述
TLS 握手優(yōu)化詳解

HTTP/2 協(xié)議本身并沒(méi)有要求必須基于TLS部署婴渡,但是實(shí)際使用中,HTTP/2 和TLS幾乎都是捆綁在一起凯亮,當(dāng)前主流瀏覽器都只支持基于TLS部署的 HTTP/2边臼。

2.3 HTTP緩存策略

首先通過(guò)下圖整體的看一下HTTP的緩存策略:


下面會(huì)根據(jù)源碼來(lái)講解HTTP的緩存策略。

2.4 HttpURLConnection相關(guān)類(lèi)的解析

2.4.1 ConnectionPool

源碼地址Android源碼中內(nèi)嵌的okhttp源碼假消,該類(lèi)用來(lái)管理TCP連接(RealConnection是對(duì)TCP連接的封裝柠并,所以這里就直接叫TCP連接)的復(fù)用以減少網(wǎng)絡(luò)延遲,下面就來(lái)看看是如何來(lái)管理的:
該類(lèi)中有兩個(gè)非常重要的成員變量:
maxIdleConnections :ConnectionPool中空閑TCP連接的最大數(shù)量,默認(rèn)為5個(gè)臼予。
keepAliveDurationNs:ConnectionPool中TCP連接最長(zhǎng)的空閑時(shí)長(zhǎng)鸣戴,默認(rèn)為5分鐘。
1> 首先看ConnectionPool中保持連接和獲取連接的過(guò)程:

/** Returns a recycled connection to {@code address}, or null if no such connection exists. */
RealConnection get(Address address, StreamAllocation streamAllocation) {
  assert (Thread.holdsLock(this));
  for (RealConnection connection : connections) {
    // connection.allocationLimit()方法返回的是每一個(gè)TCP連接上最大的Stream數(shù)量粘拾,每一個(gè)Stream對(duì)應(yīng)一對(duì) 請(qǐng)求-響應(yīng)窄锅,那么該方法的返回值代表同一時(shí)刻同一個(gè)TCP連接上最多可以處理多少對(duì) 請(qǐng)求-響應(yīng),
    // 對(duì)于HTTP/1.x缰雇,最大的Stream數(shù)量是1入偷,對(duì)于HTTP/2,最大的Stream數(shù)量是4械哟,具體實(shí)現(xiàn)可以直接參考該方法疏之。
    // address.equals(connection.getRoute().address相同代表URL字符串中的scheme、host戒良、port是相同的体捏,即只有scheme、host糯崎、port相同的情況下几缭,才有可能復(fù)用同一個(gè)TCP連接。
    if (connection.allocations.size() < connection.allocationLimit()
        && address.equals(connection.getRoute().address)
        && !connection.noNewStreams) {
      streamAllocation.acquire(connection);
      return connection;
    }
  }
  return null;
}

void put(RealConnection connection) {
  assert (Thread.holdsLock(this));
  if (connections.isEmpty()) { 
    // 當(dāng)給連接池中放入第一個(gè)TCP連接沃呢,會(huì)在后臺(tái)開(kāi)啟一個(gè)后臺(tái)的清理線(xiàn)程年栓,用于輪詢(xún)連接池中的所有的TCP連接,關(guān)掉符合清理?xiàng)l件的TCP連接薄霜。
    executor.execute(cleanupRunnable);
  }
  connections.add(connection);
}

2> 接下來(lái)看一下cleanupRunnable:

private Runnable cleanupRunnable = new Runnable() {
@Override public void run() {
  while (true) {
    long waitNanos = cleanup(System.nanoTime());
    // cleanup方法的返回值是-1代表連接池中為空某抓,此時(shí)后臺(tái)清理線(xiàn)程結(jié)束
    if (waitNanos == -1) return;
    // cleanup方法返回值大于0代表需要暫停后臺(tái)清理線(xiàn)程,暫停時(shí)長(zhǎng)為返回值的大小惰瓜,返回值為0時(shí)否副,就會(huì)立刻進(jìn)入下一輪的輪詢(xún)。
    if (waitNanos > 0) {
      long waitMillis = waitNanos / 1000000L;
      waitNanos -= (waitMillis * 1000000L);
      synchronized (ConnectionPool.this) {
        try {
          ConnectionPool.this.wait(waitMillis, (int) waitNanos);
        } catch (InterruptedException ignored) {
        }
      }
    }
  }
}
};

3> 接下來(lái)看一下輪詢(xún)的過(guò)程崎坊,即cleanup方法的實(shí)現(xiàn):

long cleanup(long now) {
  // 記錄連接池中處于使用狀態(tài)的TCP連接的數(shù)量
  int inUseConnectionCount = 0;
  // 記錄連接池中處于空閑狀態(tài)的TCP連接的數(shù)量
  int idleConnectionCount = 0;
  // 記錄連接池中空閑時(shí)長(zhǎng)最長(zhǎng)的TCP連接
  RealConnection longestIdleConnection = null;
  // 記錄連接池中所有TCP連接中最長(zhǎng)的空閑時(shí)長(zhǎng)
  long longestIdleDurationNs = Long.MIN_VALUE;

  // Find either a connection to evict, or the time that the next eviction is due.
  synchronized (this) {
    for (Iterator<RealConnection> i = connections.iterator(); i.hasNext(); ) {
      RealConnection connection = i.next();

      // pruneAndGetAllocationCount返回值大于零代表該TCP連接處于使用狀態(tài)
      if (pruneAndGetAllocationCount(connection, now) > 0) {
        // inUseConnectionCount加1备禀,然后繼續(xù)輪詢(xún)下一個(gè)TCP連接
        inUseConnectionCount++;
        continue;
      }

      // 可以運(yùn)行到這里,說(shuō)明該TCP連接處于空閑狀態(tài)奈揍,此時(shí)idleConnectionCount加1
      idleConnectionCount++;

      long idleDurationNs = now - connection.idleAtNanos;
      // 下面條件成立曲尸,代表該TCP的空閑時(shí)間比其前面的連接的空閑時(shí)長(zhǎng)都長(zhǎng)
      if (idleDurationNs > longestIdleDurationNs) {
        // 記錄連接池中所有TCP連接中最長(zhǎng)的空閑時(shí)長(zhǎng)
        longestIdleDurationNs = idleDurationNs;
        // 記錄連接池中空閑時(shí)長(zhǎng)最長(zhǎng)的TCP連接
        longestIdleConnection = connection;
      }
    }
    
    // longestIdleDurationNs >= this.keepAliveDurationNs成立代表連接池中所有TCP連接中最長(zhǎng)的空閑時(shí)長(zhǎng)大于5分鐘。
    // idleConnectionCount > this.maxIdleConnections成立接池中處于空閑狀態(tài)的TCP連接的數(shù)量大于5
    if (longestIdleDurationNs >= this.keepAliveDurationNs
        || idleConnectionCount > this.maxIdleConnections) {
      // 將longestIdleConnection從連接池中移除
      connections.remove(longestIdleConnection);

    } else if (idleConnectionCount > 0) {
      // 此時(shí)連接池中所有TCP連接中最長(zhǎng)的空閑時(shí)長(zhǎng)沒(méi)有達(dá)到5分鐘時(shí)
      return keepAliveDurationNs - longestIdleDurationNs;

    } else if (inUseConnectionCount > 0) {
      // 所有的TCP連接都處于使用狀態(tài)
      return keepAliveDurationNs;

    } else {
      // No connections, idle or in use.
      return -1;
    }
  }

  // 關(guān)閉掉從連接池中移除TCP連接
  Util.closeQuietly(longestIdleConnection.getSocket());

  // 立刻進(jìn)入下一輪的輪詢(xún)
  return 0;
}
2.4.2 ConfigAwareConnectionPool

該類(lèi)是單例的男翰,用于提供一個(gè)共享的ConnectionPool另患,該類(lèi)會(huì)監(jiān)聽(tīng)網(wǎng)絡(luò)配置改變事件,當(dāng)網(wǎng)絡(luò)配置發(fā)生改變蛾绎,ConnectionPool對(duì)象就會(huì)被作廢昆箕,之后可以通過(guò)get方法創(chuàng)建一個(gè)新的ConnectionPool對(duì)象鸦列。

3 源碼分析

首先放個(gè)例子:

if (NETWORK_GET.equals(action)) {
    //發(fā)送GET請(qǐng)求
    url = new URL("http://www.reibang.com/recommendations/notes?category_id=56&utm_medium=index-banner-s&utm_source=desktop");
    conn = (HttpURLConnection) url.openConnection();
    //HttpURLConnection默認(rèn)就是用GET發(fā)送請(qǐng)求,所以下面的setRequestMethod可以省略
    conn.setRequestMethod("GET");
    // 表示是否可以讀取響應(yīng)體中的數(shù)據(jù)为严,默認(rèn)為true敛熬。
    conn.setDoInput(true);
    //用setRequestProperty方法設(shè)置一個(gè)自定義的請(qǐng)求頭字段
    conn.setRequestProperty("action", NETWORK_GET);
    //禁用網(wǎng)絡(luò)緩存
    conn.setUseCaches(false);
    //在對(duì)各種參數(shù)配置完成后,通過(guò)調(diào)用connect方法建立TCP連接第股,但是并未真正獲取數(shù)據(jù)
    //conn.connect()方法不必顯式調(diào)用应民,當(dāng)調(diào)用conn.getInputStream()方法時(shí)內(nèi)部也會(huì)自動(dòng)調(diào)用connect方法
    conn.connect();
    //調(diào)用getInputStream方法后,服務(wù)端才會(huì)收到完整的請(qǐng)求夕吻,并阻塞式地接收服務(wù)端返回的數(shù)據(jù)
    InputStream is = conn.getInputStream();
} else if (NETWORK_POST_KEY_VALUE.equals(action)) {
    //用POST發(fā)送鍵值對(duì)數(shù)據(jù)
    url = new URL("http://www.reibang.com/recommendations/notes");
    conn = (HttpURLConnection) url.openConnection();
    //通過(guò)setRequestMethod將conn設(shè)置成POST方法
    conn.setRequestMethod("POST");
    //表示是否可以通過(guò)請(qǐng)求體發(fā)送數(shù)據(jù)給服務(wù)端诲锹,默認(rèn)為false。
    conn.setDoOutput(true);
    //用setRequestProperty方法設(shè)置一個(gè)自定義的請(qǐng)求頭:action
    conn.setRequestProperty("action", NETWORK_POST_KEY_VALUE);
    //獲取conn的輸出流
    OutputStream os = conn.getOutputStream();
    //獲取兩個(gè)鍵值對(duì)name=孫群和age=27的字節(jié)數(shù)組涉馅,將該字節(jié)數(shù)組作為請(qǐng)求體
    requestBody = new String("category_id=56&utm_medium=index-banner-s&utm_source=desktop").getBytes("UTF-8");
    //將請(qǐng)求體寫(xiě)入到conn的輸出流中
    os.write(requestBody);
    //記得調(diào)用輸出流的flush方法
    os.flush();
    //關(guān)閉輸出流
    os.close();
    //當(dāng)調(diào)用getInputStream方法時(shí)才真正將請(qǐng)求體數(shù)據(jù)上傳至服務(wù)器
    InputStream is = conn.getInputStream();
}

上面是通過(guò)HttpURLConnection發(fā)起GET/POST請(qǐng)求的實(shí)現(xiàn)代碼归园,接著通過(guò)下面的時(shí)序圖整體的看一下流程:



接下來(lái)就是按照上圖一步步解析。

3.1 創(chuàng)建URL對(duì)象

第1步稚矿,通過(guò)URL字符串創(chuàng)建URL對(duì)象:

public URL(String spec) throws MalformedURLException {
    this(null, spec);
}

public URL(URL context, String spec) throws MalformedURLException {
    this(context, spec, null);
}

public URL(URL context, String spec, URLStreamHandler handler)
    throws MalformedURLException
{
    String original = spec;
    int i, limit, c;
    int start = 0;
    String newProtocol = null;
    boolean aRef=false;
    boolean isRelative = false;

    // Check for permission to specify a handler
    if (handler != null) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkSpecifyHandler(sm);
        }
    }

    try {
        limit = spec.length();
        while ((limit > 0) && (spec.charAt(limit - 1) <= ' ')) {
            limit--;        //eliminate trailing whitespace
        }
        while ((start < limit) && (spec.charAt(start) <= ' ')) {
            start++;        // eliminate leading whitespace
        }

        if (spec.regionMatches(true, start, "url:", 0, 4)) {
            start += 4;
        }
        if (start < spec.length() && spec.charAt(start) == '#') {
            /* we're assuming this is a ref relative to the context URL.
             * This means protocols cannot start w/ '#', but we must parse
             * ref URL's like: "hello:there" w/ a ':' in them.
             */
            aRef=true;
        }
        for (i = start ; !aRef && (i < limit) &&
                 ((c = spec.charAt(i)) != '/') ; i++) {
            if (c == ':') {

                String s = spec.substring(start, i).toLowerCase();
                if (isValidProtocol(s)) {
                    newProtocol = s;
                    start = i + 1;
                }
                break;
            }
        }

        // Only use our context if the protocols match.
        protocol = newProtocol;
        if ((context != null) && ((newProtocol == null) ||
                        newProtocol.equalsIgnoreCase(context.protocol))) {
            // inherit the protocol handler from the context
            // if not specified to the constructor
            if (handler == null) {
                handler = context.handler;
            }

            // If the context is a hierarchical URL scheme and the spec
            // contains a matching scheme then maintain backwards
            // compatibility and treat it as if the spec didn't contain
            // the scheme; see 5.2.3 of RFC2396
            if (context.path != null && context.path.startsWith("/"))
                newProtocol = null;

            if (newProtocol == null) {
                protocol = context.protocol;
                authority = context.authority;
                userInfo = context.userInfo;
                host = context.host;
                port = context.port;
                file = context.file;
                path = context.path;
                isRelative = true;
            }
        }

        if (protocol == null) {
            throw new MalformedURLException("no protocol: "+original);
        }

        // Get the protocol handler if not specified or the protocol
        // of the context could not be used
        if (handler == null &&
            (handler = getURLStreamHandler(protocol)) == null) {
            throw new MalformedURLException("unknown protocol: "+protocol);
        }

        this.handler = handler;

        i = spec.indexOf('#', start);
        if (i >= 0) {
            ref = spec.substring(i + 1, limit);
            limit = i;
        }

        /*
         * Handle special case inheritance of query and fragment
         * implied by RFC2396 section 5.2.2.
         */
        if (isRelative && start == limit) {
            query = context.query;
            if (ref == null) {
                ref = context.ref;
            }
        }

        handler.parseURL(this, spec, start, limit);

    } catch(MalformedURLException e) {
        throw e;
    } catch(Exception e) {
        MalformedURLException exception = new MalformedURLException(e.getMessage());
        exception.initCause(e);
        throw exception;
    }
}

URL的構(gòu)造方法很簡(jiǎn)單庸诱,主要做了如下幾件事情:
1> 解析出URL字符串中的協(xié)議,即上面代碼中的protocol
2> 通過(guò)getURLStreamHandler方法獲取處理protocol 協(xié)議對(duì)應(yīng)的URLStreamHandler
3> 利用URLStreamHandler的parseURL方法解析URL字符串

接著看第2步:

static URLStreamHandler getURLStreamHandler(String protocol) {

    URLStreamHandler handler = handlers.get(protocol);
    if (handler == null) {
        ......
        // Fallback to built-in stream handler.
        // Makes okhttp the default http/https handler
        if (handler == null) {
            try {
                // BEGIN Android-changed
                // Use of okhttp for http and https
                // Removed unnecessary use of reflection for sun classes
                if (protocol.equals("file")) {
                    handler = new sun.net.www.protocol.file.Handler();
                } else if (protocol.equals("ftp")) {
                    handler = new sun.net.www.protocol.ftp.Handler();
                } else if (protocol.equals("jar")) {
                    handler = new sun.net.www.protocol.jar.Handler();
                } else if (protocol.equals("http")) {
                    handler = (URLStreamHandler)Class.
                        forName("com.android.okhttp.HttpHandler").newInstance();
                } else if (protocol.equals("https")) {
                    handler = (URLStreamHandler)Class.
                        forName("com.android.okhttp.HttpsHandler").newInstance();
                }
                // END Android-changed
            } catch (Exception e) {
                throw new AssertionError(e);
            }
        }
        ......
    }

    return handler;

}

由于HttpURLConnection是對(duì)HTTP協(xié)議的實(shí)現(xiàn)晤揣,所以下面只關(guān)注com.android.okhttp.HttpHandler和com.android.okhttp.HttpsHandler桥爽,HttpsHandler繼承至HttpHandler,區(qū)別在于添加了對(duì)TLS的支持昧识,即在建立TCP連接后會(huì)執(zhí)行TLS握手钠四;接下來(lái)就是下載Android源碼中內(nèi)嵌的okhttp源碼,就可以找到HttpHandler和HttpsHandler的源碼跪楞,但是發(fā)現(xiàn)了一個(gè)很奇怪的現(xiàn)象缀去,HttpHandler和HttpsHandler的包名是com.squareup.okhttp,但是上面getURLStreamHandler方法中卻是com.android.okhttp甸祭,這是怎么回事呢缕碎?這時(shí)我就看見(jiàn)源碼中有一個(gè)文件有點(diǎn)眼熟:


看到這里大家應(yīng)該明白為什么了吧。

還有一個(gè)問(wèn)題池户,什么時(shí)候Android中開(kāi)始使用okhttp咏雌,我對(duì)比了一下Android 4.4(左)和Android 4.3的URL.java,如下圖所示:



可以看出從Android 4.4開(kāi)始使用okhttp處理HTTP協(xié)議煞檩。

接著看第3步处嫌,調(diào)用URLStreamHandler的parseURL方法來(lái)解析URL字符串栅贴,然后用解析后得到的結(jié)果初始化URL對(duì)象。為了更好的理解parseURL方法的原理,那就要知道URL字符串的結(jié)構(gòu):

scheme:[//authority][/path][?query][#fragment]
scheme:[//[userInfo@]host[:port]][/path][?query][#fragment]
scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]

上面的三種格式就是對(duì)URL中的authority部分的逐步細(xì)分率翅,目前Android API 26中URL.java中細(xì)分到了第二種格式袍冷。
具體每部分是什么意思注暗,大家可以參考https://en.wikipedia.org/wiki/URL

對(duì)于上面例子中的URL字符串 http://www.reibang.com/recommendations/notes?category_id=56&utm_medium=index-banner-s&utm_source=desktop:
scheme:https
authority:www.reibang.com
host:www.reibang.com
path:/recommendations/notes
query:category_id=56&utm_medium=index-banner-s&utm_source=desktop
其中userInfo、port和fragment是沒(méi)有的墓猎。

解析URL字符串用是URLStreamHandler的parseURL方法捆昏,該方法就是按照上面的URL字符串的結(jié)構(gòu)來(lái)解析出每一部分的值,然后用這些值來(lái)初始化URL對(duì)象毙沾,具體的細(xì)節(jié)大家可以自己查看parseURL方法源碼骗卜。

3.2 創(chuàng)建HttpURLConnection實(shí)例

接下來(lái)看第5步,調(diào)用URL的openConnection方法:

public URLConnection openConnection() throws java.io.IOException {
    return handler.openConnection(this);
}

首先看一下HttpHandler的openConnection方法左胞,即第6步:

@Override protected URLConnection openConnection(URL url) throws IOException {
    return newOkUrlFactory(null /* proxy */).open(url);
}

接著看一下HttpHandler的newOkUrlFactory方法寇仓,即第7步:

// CLEARTEXT_ONLY代表不使用TLS,即URL字符串中scheme為http的情況下使用明文傳輸
private final static List<ConnectionSpec> CLEARTEXT_ONLY =
    Collections.singletonList(ConnectionSpec.CLEARTEXT);

private static final CleartextURLFilter CLEARTEXT_FILTER = new CleartextURLFilter();

private final ConfigAwareConnectionPool configAwareConnectionPool =
        ConfigAwareConnectionPool.getInstance();

// http的默認(rèn)端口號(hào)為80
@Override protected int getDefaultPort() {
    return 80;
}

protected OkUrlFactory newOkUrlFactory(Proxy proxy) {
    OkUrlFactory okUrlFactory = createHttpOkUrlFactory(proxy);
    // For HttpURLConnections created through java.net.URL Android uses a connection pool that
    // is aware when the default network changes so that pooled connections are not re-used when
    // the default network changes.
    okUrlFactory.client().setConnectionPool(configAwareConnectionPool.get());
    return okUrlFactory;
}

/**
 * Creates an OkHttpClient suitable for creating {@link java.net.HttpURLConnection} instances on
 * Android.
 */
// Visible for android.net.Network.
public static OkUrlFactory createHttpOkUrlFactory(Proxy proxy) {
    OkHttpClient client = new OkHttpClient();

    // Explicitly set the timeouts to infinity.
    client.setConnectTimeout(0, TimeUnit.MILLISECONDS);
    client.setReadTimeout(0, TimeUnit.MILLISECONDS);
    client.setWriteTimeout(0, TimeUnit.MILLISECONDS);

    // Set the default (same protocol) redirect behavior. The default can be overridden for
    // each instance using HttpURLConnection.setInstanceFollowRedirects().
    client.setFollowRedirects(HttpURLConnection.getFollowRedirects());

    // Do not permit http -> https and https -> http redirects.
    client.setFollowSslRedirects(false);

    // 僅允許明文傳輸(針對(duì)URL字符串中scheme為http的情況烤宙,而不是https)
    client.setConnectionSpecs(CLEARTEXT_ONLY);

    // When we do not set the Proxy explicitly OkHttp picks up a ProxySelector using
    // ProxySelector.getDefault().
    if (proxy != null) {
        client.setProxy(proxy);
    }

    // OkHttp requires that we explicitly set the response cache.
    OkUrlFactory okUrlFactory = new OkUrlFactory(client);

    // Use the installed NetworkSecurityPolicy to determine which requests are permitted over
    // http.
    OkUrlFactories.setUrlFilter(okUrlFactory, CLEARTEXT_FILTER);

    ResponseCache responseCache = ResponseCache.getDefault();
    if (responseCache != null) {
        AndroidInternal.setResponseCache(okUrlFactory, responseCache);
    }
    return okUrlFactory;
}

private static final class CleartextURLFilter implements URLFilter {
    @Override
    public void checkURLPermitted(URL url) throws IOException {
        String host = url.getHost();
        if (!NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted(host)) {
            throw new IOException("Cleartext HTTP traffic to " + host + " not permitted");
        }
    }
}

由上面的代碼可知newOkUrlFactory方法主要做了如下幾件事情:
1> 調(diào)用createHttpOkUrlFactory方法創(chuàng)建OkUrlFactory實(shí)例:
createHttpOkUrlFactory方法中首先創(chuàng)建OkHttpClient實(shí)例遍烦,并且為OkHttpClient實(shí)例設(shè)置了一些參數(shù):
讀寫(xiě)超時(shí):0,代表超時(shí)時(shí)間為無(wú)窮大躺枕,即沒(méi)有超時(shí)服猪;可以通過(guò)調(diào)用HttpURLConnection的setReadTimeout方法設(shè)置該值。
連接超時(shí):0拐云,代表超時(shí)時(shí)間為無(wú)窮大罢猪,即沒(méi)有超時(shí);可以通過(guò)調(diào)用HttpURLConnection的setConnectTimeout方法設(shè)置該值慨丐。
ConnectionSpecs:CLEARTEXT_ONLY坡脐,代表不需要TLS,即明文傳輸房揭。
接著以O(shè)kHttpClient實(shí)例為參數(shù)創(chuàng)建OkUrlFactory實(shí)例备闲,接著將OkUrlFactory實(shí)例的urlFilter字段設(shè)置為CleartextURLFilter(用于判斷是否可以與指定host的服務(wù)器進(jìn)行明文通信),最后返回OkUrlFactory實(shí)例捅暴。
2> 接著為OkHttpClient實(shí)例設(shè)置ConnectionPool并且返回OkUrlFactory實(shí)例恬砂,關(guān)于ConnectionPool更加細(xì)致的講解可以參考2.4.1。
3> 接著調(diào)用OkUrlFactory的open方法蓬痒,即第8步:

  HttpURLConnection open(URL url, Proxy proxy) {
    String protocol = url.getProtocol();
    OkHttpClient copy = client.copyWithDefaults();
    copy.setProxy(proxy);

    if (protocol.equals("http")) return new HttpURLConnectionImpl(url, copy, urlFilter);
    if (protocol.equals("https")) return new HttpsURLConnectionImpl(url, copy, urlFilter);
    throw new IllegalArgumentException("Unexpected protocol: " + protocol);
  }

到這里泻骤,HttpHandler的openConnection方法分析完畢,接下來(lái)就要看看HttpsHandler梧奢,HttpsHandler重寫(xiě)了HttpHandler的newOkUrlFactory方法和createHttpOkUrlFactory方法:

/**
 * The connection spec to use when connecting to an https:// server. Note that Android does
 * not set the cipher suites or TLS versions to use so the socket's defaults will be used
 * instead. When the SSLSocketFactory is provided by the app or GMS core we will not
 * override the enabled ciphers or TLS versions set on the sockets it produces with a
 * list hardcoded at release time. This is deliberate.
 */
private static final ConnectionSpec TLS_CONNECTION_SPEC = ConnectionSpecs.builder(true)
        .allEnabledCipherSuites()
        .allEnabledTlsVersions()
        .supportsTlsExtensions(true)
        .build();

// TLS握手階段時(shí)狱掂,通過(guò)ALPN協(xié)商應(yīng)用層協(xié)議時(shí)客戶(hù)端ClientHello攜帶的應(yīng)用層協(xié)議列表
private static final List<Protocol> HTTP_1_1_ONLY =
        Collections.singletonList(Protocol.HTTP_1_1);

private final ConfigAwareConnectionPool configAwareConnectionPool =
        ConfigAwareConnectionPool.getInstance();

// https的默認(rèn)端口號(hào)為443
@Override protected int getDefaultPort() {
    return 443;
}

@Override
protected OkUrlFactory newOkUrlFactory(Proxy proxy) {
    OkUrlFactory okUrlFactory = createHttpsOkUrlFactory(proxy);
    // For HttpsURLConnections created through java.net.URL Android uses a connection pool that
    // is aware when the default network changes so that pooled connections are not re-used when
    // the default network changes.
    okUrlFactory.client().setConnectionPool(configAwareConnectionPool.get());
    return okUrlFactory;
}

/**
 * Creates an OkHttpClient suitable for creating {@link HttpsURLConnection} instances on
 * Android.
 */
// Visible for android.net.Network.
public static OkUrlFactory createHttpsOkUrlFactory(Proxy proxy) {
    // The HTTPS OkHttpClient is an HTTP OkHttpClient with extra configuration.
    OkUrlFactory okUrlFactory = HttpHandler.createHttpOkUrlFactory(proxy);

    // All HTTPS requests are allowed.
    OkUrlFactories.setUrlFilter(okUrlFactory, null);

    OkHttpClient okHttpClient = okUrlFactory.client();

    // Only enable HTTP/1.1 (implies HTTP/1.0). Disable SPDY / HTTP/2.0.
    okHttpClient.setProtocols(HTTP_1_1_ONLY);

    okHttpClient.setConnectionSpecs(Collections.singletonList(TLS_CONNECTION_SPEC));

    // Android support certificate pinning via NetworkSecurityConfig so there is no need to
    // also expose OkHttp's mechanism. The OkHttpClient underlying https HttpsURLConnections
    // in Android should therefore always use the default certificate pinner, whose set of
    // {@code hostNamesToPin} is empty.
    okHttpClient.setCertificatePinner(CertificatePinner.DEFAULT);

    // OkHttp does not automatically honor the system-wide HostnameVerifier set with
    // HttpsURLConnection.setDefaultHostnameVerifier().
    okUrlFactory.client().setHostnameVerifier(HttpsURLConnection.getDefaultHostnameVerifier());
    // OkHttp does not automatically honor the system-wide SSLSocketFactory set with
    // HttpsURLConnection.setDefaultSSLSocketFactory().
    // See https://github.com/square/okhttp/issues/184 for details.
    okHttpClient.setSslSocketFactory(HttpsURLConnection.getDefaultSSLSocketFactory());

    return okUrlFactory;
}

從上面源碼可以看出HttpsHandler改寫(xiě)了第7步中的createHttpOkUrlFactory方法:
首先調(diào)用父類(lèi)HttpHandler的createHttpOkUrlFactory方法返回OkUrlFactory實(shí)例,接著將OkUrlFactory實(shí)例的urlFilter字段設(shè)置為null亲轨;接著為OkHttpClient實(shí)例設(shè)置一些參數(shù):
ConnectionSpecs:TLS_CONNECTION_SPEC趋惨,代表需要支持TLS,即密文傳輸惦蚊。
protocols:HTTP_1_1_ONLY器虾,TLS握手階段時(shí)讯嫂,通過(guò)ALPN協(xié)商應(yīng)用層協(xié)議時(shí)客戶(hù)端ClientHello攜帶的應(yīng)用層協(xié)議列表。
sslSocketFactory:HttpsURLConnection.getDefaultSSLSocketFactory()兆沙。

通過(guò)上面的分析可知URL的openConnection方法并沒(méi)有建立TCP連接欧芽,只是創(chuàng)建了HttpURLConnectionImpl實(shí)例或者HttpsURLConnectionImpl實(shí)例。

3.3 調(diào)用HttpURLConnection的connect方法與服務(wù)端建立TCP連接

HttpsURLConnectionImpl使用了裝飾者模式對(duì)HttpURLConnectionImpl進(jìn)行了裝飾葛圃,HttpsURLConnectionImpl對(duì)connect方法沒(méi)有任何的裝飾千扔,所以直接看HttpURLConnectionImpl中connect方法,即第12步:

  @Override public final void connect() throws IOException {
    initHttpEngine();
    boolean success;
    do {
      // execute方法用于發(fā)送請(qǐng)求库正,如果請(qǐng)求成功執(zhí)行昏鹃,則返回true,如果請(qǐng)求可以重試诀诊,則返回false洞渤,
      // 如果請(qǐng)求永久失敗,則拋出異常属瓣。
      success = execute(false);
    } while (!success);
  }

HttpURLConnectionImpl中的initHttpEngine方法是私有方法载迄,所以不會(huì)被裝飾,所以直接看HttpURLConnectionImpl的initHttpEngine方法抡蛙,即第13步:

  private void initHttpEngine() throws IOException {
    if (httpEngineFailure != null) {
      throw httpEngineFailure;
    } else if (httpEngine != null) {
      return;
    }

    connected = true;
    try {
      if (doOutput) {
        if (method.equals("GET")) {
          // they are requesting a stream to write to. This implies a POST method
          method = "POST";
        } else if (!HttpMethod.permitsRequestBody(method)) {
          throw new ProtocolException(method + " does not support writing");
        }
      }
      // If the user set content length to zero, we know there will not be a request body.
      httpEngine = newHttpEngine(method, null, null, null);
    } catch (IOException e) {
      httpEngineFailure = e;
      throw e;
    }
  }

在initHttpEngine方法中护昧,如果doOutput為true并且請(qǐng)求方法是GET的情況下,就會(huì)將請(qǐng)求方法強(qiáng)制替換成POST粗截,這是為什么呢惋耙?首先讓我們了解doInput和doOutput是用來(lái)干嘛的?
doInput:表示是否可以讀取服務(wù)端返回的響應(yīng)體中的數(shù)據(jù)熊昌,默認(rèn)為true绽榛。
doOutput:表示是否可以通過(guò)請(qǐng)求體發(fā)送數(shù)據(jù)給服務(wù)端,默認(rèn)為false婿屹。
對(duì)于請(qǐng)求方法是GET的請(qǐng)求是沒(méi)有請(qǐng)求體的灭美,而請(qǐng)求方法是POST的請(qǐng)求是有請(qǐng)求體的(具體哪些請(qǐng)求方法有請(qǐng)求體,可以參考HttpMethod.permitsRequestBody方法)昂利,所以在doOutput為true并且請(qǐng)求方法是GET的情況下届腐,就會(huì)將請(qǐng)求方法強(qiáng)制替換成POST。

由于HttpURLConnectionImpl中的newHttpEngine方法是私有方法蜂奸,即不會(huì)被裝飾犁苏,所以直接看HttpURLConnectionImpl中的newHttpEngine方法:

  private HttpEngine newHttpEngine(String method, StreamAllocation streamAllocation,
      RetryableSink requestBody, Response priorResponse)
      throws MalformedURLException, UnknownHostException {
    // OkHttp's Call API requires a placeholder body; the real body will be streamed separately.
    RequestBody placeholderBody = HttpMethod.requiresRequestBody(method)
        ? EMPTY_REQUEST_BODY
        : null;
    URL url = getURL();
    HttpUrl httpUrl = Internal.instance.getHttpUrlChecked(url.toString());
    // 根據(jù)請(qǐng)求的url、method扩所、請(qǐng)求頭(通過(guò)HttpURLConnection的setRequestProperty方法設(shè)置的)
    // 創(chuàng)建Request實(shí)例
    Request.Builder builder = new Request.Builder()
        .url(httpUrl)
        .method(method, placeholderBody);
    Headers headers = requestHeaders.build();
    for (int i = 0, size = headers.size(); i < size; i++) {
      builder.addHeader(headers.name(i), headers.value(i));
    }

    boolean bufferRequestBody = false;
    if (HttpMethod.permitsRequestBody(method)) { // 判斷請(qǐng)求是否有請(qǐng)求體
      // HTTP/1.1以及之后的版本都是長(zhǎng)連接的围详,對(duì)于請(qǐng)求有請(qǐng)求體的情況,就需要告訴服務(wù)端請(qǐng)求體何時(shí)結(jié)束碌奉,
      // 有兩種方式來(lái)告訴服務(wù)端請(qǐng)求體已經(jīng)結(jié)束:Content-Length短曾、Transfer-Encoding,
      // 具體可以參考[HTTP 協(xié)議中的 Transfer-Encoding](https://imququ.com/post/transfer-encoding-header-in-http.html)
      if (fixedContentLength != -1) { 
        // 調(diào)用了HttpURLConnection的setFixedLengthStreamingMode來(lái)設(shè)置請(qǐng)求體的結(jié)束方式為Content-Length
        builder.header("Content-Length", Long.toString(fixedContentLength));
      } else if (chunkLength > 0) { 
        // 調(diào)用了HttpURLConnection的setChunkedStreamingMode來(lái)設(shè)置請(qǐng)求體的結(jié)束方式為T(mén)ransfer-Encoding
        builder.header("Transfer-Encoding", "chunked");
      } else {
        // 沒(méi)有設(shè)置請(qǐng)求體結(jié)束方式的情況
        bufferRequestBody = true;
      }

      // Add a content type for the request body, if one isn't already present.
      if (headers.get("Content-Type") == null) {
        builder.header("Content-Type", "application/x-www-form-urlencoded");
      }
    }

    if (headers.get("User-Agent") == null) {
      builder.header("User-Agent", defaultUserAgent());
    }

    Request request = builder.build();

    // If we're currently not using caches, make sure the engine's client doesn't have one.
    OkHttpClient engineClient = client;
    if (Internal.instance.internalCache(engineClient) != null && !getUseCaches()) {
      engineClient = client.clone().setCache(null);
    }

    return new HttpEngine(engineClient, request, bufferRequestBody, true, false, streamAllocation,
        requestBody, priorResponse);
  }

接下來(lái)看一下HttpEngine的構(gòu)造方法以及相關(guān)代碼:

public HttpEngine(OkHttpClient client, Request request, boolean bufferRequestBody,
    boolean callerWritesRequestBody, boolean forWebSocket, StreamAllocation streamAllocation,
    RetryableSink requestBodyOut, Response priorResponse) {
  this.client = client;
  this.userRequest = request;
  this.bufferRequestBody = bufferRequestBody;
  this.callerWritesRequestBody = callerWritesRequestBody;
  this.forWebSocket = forWebSocket;
  this.streamAllocation = streamAllocation != null
      ? streamAllocation
      : new StreamAllocation(client.getConnectionPool(), createAddress(client, request));
  this.requestBodyOut = requestBodyOut;
  this.priorResponse = priorResponse;
}

private static Address createAddress(OkHttpClient client, Request request) {
  SSLSocketFactory sslSocketFactory = null;
  HostnameVerifier hostnameVerifier = null;
  CertificatePinner certificatePinner = null;
  if (request.isHttps()) {
    // 只有scheme為https時(shí)赐劣,sslSocketFactory才不為null
    sslSocketFactory = client.getSslSocketFactory();
    hostnameVerifier = client.getHostnameVerifier();
    certificatePinner = client.getCertificatePinner();
  }

  return new Address(request.httpUrl().host(), request.httpUrl().port(), client.getDns(),
      client.getSocketFactory(), sslSocketFactory, hostnameVerifier, certificatePinner,
      client.getAuthenticator(), client.getProxy(), client.getProtocols(),
      client.getConnectionSpecs(), client.getProxySelector());
}

回到HttpURLConnectionImpl中connect方法嫉拐;由于HttpURLConnectionImpl中的execute方法是私有方法,即不會(huì)被裝飾魁兼,所以直接看HttpURLConnectionImpl中的execute方法婉徘,即第14步:

private boolean execute(boolean readResponse) throws IOException {
  boolean releaseConnection = true;
  // 由第7步可知,在scheme為http情況下咐汞,urlFilter為CleartextURLFilter類(lèi)型的實(shí)例盖呼,
  // CleartextURLFilter用于判斷是否可以與指定host的服務(wù)器進(jìn)行明文通信。
  if (urlFilter != null) {
    urlFilter.checkURLPermitted(httpEngine.getRequest().url());
  }
  try {
    // 發(fā)起請(qǐng)求
    httpEngine.sendRequest();
    Connection connection = httpEngine.getConnection();
    if (connection != null) {
      route = connection.getRoute();
      handshake = connection.getHandshake();
    } else {
      route = null;
      handshake = null;
    }
    if (readResponse) {
      // 讀取響應(yīng)
      httpEngine.readResponse();
    }
    releaseConnection = false;

    return true;
  } 
  ......
}

接下來(lái)看HttpEngine的sendRequest方法化撕,即第15步:

public void sendRequest() throws RequestException, RouteException, IOException {
  if (cacheStrategy != null) return; // Already sent.
  if (httpStream != null) throw new IllegalStateException();

  // 為請(qǐng)求添加默認(rèn)的請(qǐng)求頭
  Request request = networkRequest(userRequest);

  // 從緩存中獲取該請(qǐng)求對(duì)應(yīng)的緩存的響應(yīng)几晤,當(dāng)調(diào)用HttpURLConnection的setUseCaches方法
  // 將useCaches字段(默認(rèn)值為true)設(shè)置為false時(shí),responseCache為null
  InternalCache responseCache = Internal.instance.internalCache(client);
  Response cacheCandidate = responseCache != null
      ? responseCache.get(request)
      : null;

  long now = System.currentTimeMillis();
  // 給出請(qǐng)求和請(qǐng)求對(duì)應(yīng)的緩存的響應(yīng)植阴,然后根據(jù)緩存策略得出符合緩存策略的請(qǐng)求和響應(yīng)
  cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
  // 當(dāng)?shù)贸龅恼?qǐng)求不為null時(shí)(即networkRequest不為null)蟹瘾,代表該請(qǐng)求對(duì)應(yīng)的緩存的響應(yīng)不存在或者已經(jīng)過(guò)期,
  // 此時(shí)需要從服務(wù)端獲取掠手,否則使用請(qǐng)求對(duì)應(yīng)的緩存的響應(yīng)
  networkRequest = cacheStrategy.networkRequest;
  cacheResponse = cacheStrategy.cacheResponse;

  if (responseCache != null) {
    responseCache.trackResponse(cacheStrategy);
  }

  if (cacheCandidate != null && cacheResponse == null) {
    closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
  }

  // 當(dāng)networkRequest不等于null時(shí)憾朴,代表該請(qǐng)求對(duì)應(yīng)的緩存響應(yīng)不存在或者已經(jīng)過(guò)期,需要從新從服務(wù)端獲取
  if (networkRequest != null) {
    // 建立Socket連接
    httpStream = connect();
    httpStream.setHttpEngine(this);

    // If the caller's control flow writes the request body, we need to create that stream
    // immediately. And that means we need to immediately write the request headers, so we can
    // start streaming the request body. (We may already have a request body if we're retrying a
    // failed POST.)
    if (callerWritesRequestBody && permitsRequestBody(networkRequest) && requestBodyOut == null) {
      long contentLength = OkHeaders.contentLength(request);
      //在第13步中說(shuō)過(guò)喷鸽,在請(qǐng)求體存在并且沒(méi)有設(shè)置請(qǐng)求體結(jié)束的方式的情況下众雷,bufferRequestBody為true
      if (bufferRequestBody) {
        if (contentLength > Integer.MAX_VALUE) {
          throw new IllegalStateException("Use setFixedLengthStreamingMode() or "
              + "setChunkedStreamingMode() for requests larger than 2 GiB.");
        }

        if (contentLength != -1) {
          // 請(qǐng)求體的長(zhǎng)度知道的情況(即請(qǐng)求頭中包含頭字段Content-Length),當(dāng)通過(guò)HttpURLConnection的setRequestProperty方法設(shè)置了Content-Length時(shí)做祝,這種情況才會(huì)發(fā)生砾省,
          // writeRequestHeaders最終將請(qǐng)求頭寫(xiě)入到RealBufferedSink實(shí)例中,該RealBufferedSink實(shí)例是下面第21步中的提到的sink字段混槐。
          httpStream.writeRequestHeaders(networkRequest);
          // 創(chuàng)建RetryableSink類(lèi)型的實(shí)例requestBodyOut纯蛾,RetryableSink中有一個(gè)Buffer類(lèi)型的字段,
          // 用于緩存請(qǐng)求體纵隔,因此通過(guò)RetryableSink類(lèi)型的requestBodyOut實(shí)例寫(xiě)入請(qǐng)求體翻诉,只不過(guò)是將其緩存到內(nèi)存中
          requestBodyOut = new RetryableSink((int) contentLength);
        } else {
          // 請(qǐng)求體的長(zhǎng)度未知的情況(即請(qǐng)求頭中沒(méi)有正確設(shè)置頭字段Content-Length),此時(shí)就必須在整個(gè)請(qǐng)求體準(zhǔn)備好之后才能寫(xiě)請(qǐng)求頭捌刮。
          requestBodyOut = new RetryableSink();
        }
      } else {
        // 有請(qǐng)求體并且設(shè)置了請(qǐng)求體的結(jié)束方式為Content-Length和Transfer-Encoding其中之一(即請(qǐng)求頭中包含頭字段Content-Length和Transfer-Encoding其中之一)碰煌,
        // writeRequestHeaders最終將請(qǐng)求頭寫(xiě)入到RealBufferedSink實(shí)例中,該RealBufferedSink實(shí)例是下面第21步中的提到的sink字段绅作。
        httpStream.writeRequestHeaders(networkRequest);
        // createRequestBody方法中會(huì)根據(jù)請(qǐng)求頭中頭字段Content-Length或者Transfer-Encoding創(chuàng)建
        // 不同類(lèi)型的Sink實(shí)例芦圾,通過(guò)該Sink實(shí)例寫(xiě)入的請(qǐng)求體休息最終會(huì)被寫(xiě)入到RealBufferedSink實(shí)例中,該RealBufferedSink實(shí)例是下面第21步中的提到的sink字段俄认。
        requestBodyOut = httpStream.createRequestBody(networkRequest, contentLength);
      }
    }

  } else { // 代表使用該請(qǐng)求對(duì)應(yīng)的緩存的響應(yīng)
    if (cacheResponse != null) {
      // We have a valid cached response. Promote it to the user response immediately.
      this.userResponse = cacheResponse.newBuilder()
          .request(userRequest)
          .priorResponse(stripBody(priorResponse))
          .cacheResponse(stripBody(cacheResponse))
          .build();
    } else {
      // We're forbidden from using the network, and the cache is insufficient.
      this.userResponse = new Response.Builder()
          .request(userRequest)
          .priorResponse(stripBody(priorResponse))
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(EMPTY_BODY)
          .build();
    }

    // 當(dāng)響應(yīng)頭包含頭字段Content-Encoding并且值為gzip時(shí)个少,unzip中會(huì)利用GzipSource將響應(yīng)體進(jìn)行解壓洪乍。
    userResponse = unzip(userResponse);
  }
}

private Request networkRequest(Request request) throws IOException {
  Request.Builder result = request.newBuilder();

  if (request.header("Host") == null) {
    result.header("Host", Util.hostHeader(request.httpUrl(), false));
  }

  // 告訴服務(wù)端使用長(zhǎng)連接
  if (request.header("Connection") == null) {
    result.header("Connection", "Keep-Alive");
  }

  // 告訴服務(wù)端客戶(hù)端支持的壓縮類(lèi)型
  if (request.header("Accept-Encoding") == null) {
    transparentGzip = true;
    result.header("Accept-Encoding", "gzip");
  }

  CookieHandler cookieHandler = client.getCookieHandler();
  if (cookieHandler != null) {
    // Capture the request headers added so far so that they can be offered to the CookieHandler.
    // This is mostly to stay close to the RI; it is unlikely any of the headers above would
    // affect cookie choice besides "Host".
    Map<String, List<String>> headers = OkHeaders.toMultimap(result.build().headers(), null);

    Map<String, List<String>> cookies = cookieHandler.get(request.uri(), headers);

    // Add any new cookies to the request.
    OkHeaders.addCookies(result, cookies);
  }

  if (request.header("User-Agent") == null) {
    result.header("User-Agent", Version.userAgent());
  }

  return result.build();
}

上面代碼中講到了CacheStrategy類(lèi),該類(lèi)實(shí)現(xiàn)了2.3中的緩存策略夜焦,下面就來(lái)分析一下CacheStrategy的使用流程:

public Factory(long nowMillis, Request request, Response cacheResponse) {
  this.nowMillis = nowMillis;
  this.request = request;
  this.cacheResponse = cacheResponse;

  if (cacheResponse != null) {
    // 當(dāng)請(qǐng)求對(duì)應(yīng)的緩存的響應(yīng)不為空時(shí)壳澳,獲取緩存的響應(yīng)的一些信息,后面在判斷緩存的響應(yīng)是否新鮮時(shí)會(huì)用到
    Headers headers = cacheResponse.headers();
    for (int i = 0, size = headers.size(); i < size; i++) {
      String fieldName = headers.name(i);
      String value = headers.value(i);
      if ("Date".equalsIgnoreCase(fieldName)) { 
        // Data代表服務(wù)端發(fā)送緩存響應(yīng)的日期和時(shí)間茫经,比如 Date: Tue, 15 Nov 1994 08:12:31 GMT
        servedDate = HttpDate.parse(value);
        servedDateString = value;
      } else if ("Expires".equalsIgnoreCase(fieldName)) { 
        // Expires代表一個(gè)日期和時(shí)間巷波,超過(guò)該時(shí)間則認(rèn)為此回應(yīng)已經(jīng)過(guò)期,比如 Expires: Thu, 01 Dec 1994 16:00:00 GMT
        expires = HttpDate.parse(value);
      } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
        // Last-Modified代表緩存響應(yīng)的最后修改日期卸伞,比如 Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT
        lastModified = HttpDate.parse(value);
        lastModifiedString = value;
      } else if ("ETag".equalsIgnoreCase(fieldName)) {
        // ETag代表對(duì)于緩存響應(yīng)的某個(gè)特定版本的一個(gè)標(biāo)識(shí)符抹镊,比如 ETag: "737060cd8c284d8af7ad3082f209582d"
        etag = value;
      } else if ("Age".equalsIgnoreCase(fieldName)) {
        // Age代表緩存響應(yīng)在緩存中存在的時(shí)間,以秒為單位
        ageSeconds = HeaderParser.parseSeconds(value, -1);
      } else if (OkHeaders.SENT_MILLIS.equalsIgnoreCase(fieldName)) {
        // SENT_MILLIS字段是Okhttp中添加的字段荤傲,用于記錄請(qǐng)求頭被發(fā)送時(shí)的時(shí)間點(diǎn)
        sentRequestMillis = Long.parseLong(value);
      } else if (OkHeaders.RECEIVED_MILLIS.equalsIgnoreCase(fieldName)) {
        // RECEIVED_MILLIS字段是Okhttp中添加的字段垮耳,用于記錄客戶(hù)端接收到響應(yīng)的時(shí)間點(diǎn)
        receivedResponseMillis = Long.parseLong(value);
      }
    }
  }
}

/**
 * Returns a strategy to satisfy {@code request} using the a cached response
 * {@code response}.
 */
public CacheStrategy get() {
  CacheStrategy candidate = getCandidate();

  if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
    // We're forbidden from using the network and the cache is insufficient.
    return new CacheStrategy(null, null);
  }

  return candidate;
}

/** Returns a strategy to use assuming the request can use the network. */
private CacheStrategy getCandidate() {
  if (cacheResponse == null) {
    // 請(qǐng)求對(duì)應(yīng)的緩存響應(yīng)為null,對(duì)應(yīng)于2.3中的第1步否定的情況遂黍。
    return new CacheStrategy(request, null);
  }

  // Drop the cached response if it's missing a required handshake.
  if (request.isHttps() && cacheResponse.handshake() == null) {
    return new CacheStrategy(request, null);
  }
  
  // 可以執(zhí)行到這里氨菇,代表進(jìn)入到2.3中第1步肯定情況下的程
  // isCacheable用于判斷請(qǐng)求對(duì)應(yīng)的響應(yīng)是否允許被緩存,下面條件語(yǔ)句成立代表不允許緩存的情況妓湘,
  // 對(duì)應(yīng)于2.3中的第二步否定的情況查蓉。
  if (!isCacheable(cacheResponse, request)) {
    return new CacheStrategy(request, null);
  }

  CacheControl requestCaching = request.cacheControl();
  if (requestCaching.noCache() || hasConditions(request)) {
    return new CacheStrategy(request, null);
  }

  long ageMillis = cacheResponseAge();
  long freshMillis = computeFreshnessLifetime();

  if (requestCaching.maxAgeSeconds() != -1) {
    freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
  }

  long minFreshMillis = 0;
  if (requestCaching.minFreshSeconds() != -1) {
    minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
  }

  long maxStaleMillis = 0;
  CacheControl responseCaching = cacheResponse.cacheControl();
  if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
    maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
  }

  // 可以執(zhí)行到這里,代表進(jìn)入到2.3中第2步肯定情況下的流程
  // 下面條件語(yǔ)句如果成立的話(huà)榜贴,代表緩存的響應(yīng)還是新鮮的豌研,說(shuō)明請(qǐng)求對(duì)應(yīng)的緩存的響應(yīng)沒(méi)有過(guò)期,
  // 對(duì)應(yīng)于2.3中的第3步肯定的情況唬党。
  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()) {
      builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
    }
    return new CacheStrategy(null, builder.build());
  }

  Request.Builder conditionalRequestBuilder = request.newBuilder();

  // 如果可以執(zhí)行到這里鹃共,說(shuō)明請(qǐng)求對(duì)應(yīng)的緩存響應(yīng)已經(jīng)過(guò)期,代表進(jìn)入到2.3中第3步否定情況下的流程驶拱,
  // 這時(shí)就需要與服務(wù)端進(jìn)行驗(yàn)證是否還可用霜浴,驗(yàn)證方式根據(jù)請(qǐng)求對(duì)應(yīng)的緩存的響應(yīng)決定,有如下三種
  if (etag != null) { 
    // 如果請(qǐng)求對(duì)應(yīng)的緩存的響應(yīng)包含字段ETag蓝纲,則使用請(qǐng)求頭字段If-None-Match驗(yàn)證是否還可用
    conditionalRequestBuilder.header("If-None-Match", etag);
  } else if (lastModified != null) {
    // 如果請(qǐng)求對(duì)應(yīng)的緩存的響應(yīng)包含字段Last-Modified阴孟,則使用請(qǐng)求頭字段If-Modified-Since驗(yàn)證是否還可用
    conditionalRequestBuilder.header("If-Modified-Since", lastModifiedString);
  } else if (servedDate != null) {
    // 如果請(qǐng)求對(duì)應(yīng)的緩存的響應(yīng)包含字段Date,則使用請(qǐng)求頭字段If-Modified-Since驗(yàn)證是否還可用
    conditionalRequestBuilder.header("If-Modified-Since", servedDateString);
  }

  Request conditionalRequest = conditionalRequestBuilder.build();
  return hasConditions(conditionalRequest)
      ? new CacheStrategy(conditionalRequest, cacheResponse)
      : new CacheStrategy(conditionalRequest, null);
}

對(duì)于請(qǐng)求頭和響應(yīng)頭中字段的含義税迷,可以參考HTTP頭字段永丝。

接下來(lái)看一下HttpEngine的connect方法,即第16步:

private HttpStream connect() throws RouteException, RequestException, IOException {
  boolean doExtensiveHealthChecks = !networkRequest.method().equals("GET");
  return streamAllocation.newStream(client.getConnectTimeout(),
      client.getReadTimeout(), client.getWriteTimeout(),
      client.getRetryOnConnectionFailure(), doExtensiveHealthChecks);
}

接下來(lái)看StreamAllocation的newStream方法箭养,即第17步:

public HttpStream newStream(int connectTimeout, int readTimeout, int writeTimeout,
    boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
    throws RouteException, IOException {
  try {
    // 尋找一個(gè)健康的連接
    RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
        writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);

    // 在尋找到的連接上設(shè)置HttpStream慕嚷,HttpStream是用來(lái)發(fā)送請(qǐng)求和接收響應(yīng)的
    HttpStream resultStream;
    if (resultConnection.framedConnection != null) { // 對(duì)應(yīng)于HTTP/2協(xié)議
      resultStream = new Http2xStream(this, resultConnection.framedConnection);
    } else { // 對(duì)應(yīng)于HTTP/1.x協(xié)議
      resultConnection.getSocket().setSoTimeout(readTimeout);
      resultConnection.source.timeout().timeout(readTimeout, MILLISECONDS);
      resultConnection.sink.timeout().timeout(writeTimeout, MILLISECONDS);
      resultStream = new Http1xStream(this, resultConnection.source, resultConnection.sink);
    }

    synchronized (connectionPool) {
      resultConnection.streamCount++;
      stream = resultStream;
      return resultStream;
    }
  } catch (IOException e) {
    throw new RouteException(e);
  }
}

接下來(lái)看StreamAllocation的findHealthyConnection方法,即第18步:

/**
 * 循環(huán)尋找一個(gè)健康的連接,直到找到為止
 */
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
    int writeTimeout, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
    throws IOException, RouteException {
  while (true) {
    RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
        connectionRetryEnabled);

    // 如果candidate是一個(gè)全新的連接(即連接上面Steam的個(gè)數(shù)為0)喝检,則跳過(guò)下面的健康檢查直接返回嗅辣。
    synchronized (connectionPool) {
      if (candidate.streamCount == 0) {
        return candidate;
      }
    }

    // Otherwise do a potentially-slow check to confirm that the pooled connection is still good.
    if (candidate.isHealthy(doExtensiveHealthChecks)) {
      return candidate;
    }

    connectionFailed();
  }
}

接下來(lái)看StreamAllocation的findConnection方法,即第19步:

/**
 * 如果沒(méi)有找到挠说,就創(chuàng)建一個(gè)新的連接澡谭,首先將新創(chuàng)建的連接實(shí)例放到連接池,然后通過(guò)新創(chuàng)建的連接實(shí)例與
 * 服務(wù)端建立Socket連接纺涤,最后將新創(chuàng)建的連接返回。
 */
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
    boolean connectionRetryEnabled) throws IOException, RouteException {
  synchronized (connectionPool) {
    if (released) throw new IllegalStateException("released");
    if (stream != null) throw new IllegalStateException("stream != null");
    if (canceled) throw new IOException("Canceled");

    RealConnection allocatedConnection = this.connection;
    if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
      return allocatedConnection;
    }

    // 從連接池中尋找抠忘,如果之前已經(jīng)通過(guò)相同的Address(只要URL字符串中的scheme撩炊、host、port相同崎脉,
    // Address一般都是相同的拧咳,具體可以看Address的equals方法)創(chuàng)建過(guò)連接并且該連接上Stream沒(méi)有達(dá)到上限(對(duì)于HTTP/1.x,Stream的上限為1囚灼,對(duì)于HTTP/2骆膝,Stream的上限為4),
    // 那就找到了可復(fù)用的連接
    RealConnection pooledConnection = Internal.instance.get(connectionPool, address, this);
    if (pooledConnection != null) {
      // 在連接池中找到了可復(fù)用的連接灶体,直接返回
      this.connection = pooledConnection;
      return pooledConnection;
    }

    if (routeSelector == null) {
      // RouteSelector的構(gòu)造方法中會(huì)調(diào)用resetNextProxy方法阅签,該方法中會(huì)獲取系統(tǒng)默認(rèn)的ProxySelector,
      // 然后調(diào)用ProxySelector的select方法(以address的url為參數(shù))獲取http或者h(yuǎn)ttps協(xié)議對(duì)應(yīng)的
      // 代理列表蝎抽,默認(rèn)情況下代理列表是空的政钟,可以通過(guò)http://www.blogs8.cn/posts/EU5L296中提供的方式設(shè)置代理。
      // 接著將代理列表保存到proxies字段中樟结,最后在proxies的末尾添加一個(gè)Proxy.NO_PROXY代理养交,
      // Proxy.NO_PROXY代理的type是Type.DIRECT類(lèi)型,即直接連接瓢宦,不使用代理碎连,這也是默認(rèn)的方式。
      routeSelector = new RouteSelector(address, routeDatabase());
    }
  }

  // RouteSelector的next方法中首先獲取proxies中的第一個(gè)代理(在沒(méi)有設(shè)置代理的情況下驮履,
  // 該代理為Proxy.NO_PROXY)鱼辙,然后用該代理創(chuàng)建Route實(shí)例
  Route route = routeSelector.next();
  RealConnection newConnection = new RealConnection(route);
  acquire(newConnection);

  // 將新創(chuàng)建的連接放到連接池中以備后用
  synchronized (connectionPool) {
    Internal.instance.put(connectionPool, newConnection);
    this.connection = newConnection;
    if (canceled) throw new IOException("Canceled");
  }

  // 通過(guò)新創(chuàng)建的連接與服務(wù)端建立Socket連接
  newConnection.connect(connectTimeout, readTimeout, writeTimeout, address.getConnectionSpecs(),
      connectionRetryEnabled);
  routeDatabase().connected(newConnection.getRoute());

  return newConnection;
}

接下來(lái)就來(lái)看看RealConnection的connect方法,即第20步:

public void connect(int connectTimeout, int readTimeout, int writeTimeout,
    List<ConnectionSpec> connectionSpecs, boolean connectionRetryEnabled) throws RouteException {
  if (protocol != null) throw new IllegalStateException("already connected");

  RouteException routeException = null;
  ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);
  Proxy proxy = route.getProxy();
  Address address = route.getAddress();

  // 如果getSslSocketFactory()為null玫镐,那么schema為http座每,繼而使用明文傳輸,
  // 所以下面條件判斷中 如果不包含ConnectionSpec.CLEARTEXT摘悴,就拋出異常峭梳。
  if (route.getAddress().getSslSocketFactory() == null
      && !connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
    throw new RouteException(new UnknownServiceException(
        "CLEARTEXT communication not supported: " + connectionSpecs));
  }

  // 有兩種情況可以使protocol不為null:
  // 1> scheme為http時(shí),成功建立Socket連接,protocol就會(huì)被設(shè)置為Protocol.HTTP_1_1
  // 2> scheme為https時(shí)葱椭,成功建立Socket連接并且TLS握手成功捂寿,protocol就會(huì)被設(shè)置為Protocol.HTTP_1_1
  // 那么在HttpURLConnection中,無(wú)論scheme為https還是http孵运,協(xié)議版本都是HTTP/1.1秦陋。
  // 具體原因第21步會(huì)詳細(xì)說(shuō)明
  while (protocol == null) {
    try {
      // 根據(jù)上面findConnection的注釋可知,默認(rèn)情況下不設(shè)置代理治笨,即proxy.type() == Proxy.Type.DIRECT是成立的驳概,
      // 因此通過(guò)address.getSocketFactory().createSocket()創(chuàng)建一個(gè)Socket實(shí)例
      rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
          ? address.getSocketFactory().createSocket()
          : new Socket(proxy);
      // 利用rawSocket實(shí)例根據(jù)指定host和port發(fā)起與服務(wù)端的TCP連接
      connectSocket(connectTimeout, readTimeout, writeTimeout, connectionSpecSelector);
    } catch (IOException e) {
      Util.closeQuietly(socket);
      Util.closeQuietly(rawSocket);
      socket = null;
      rawSocket = null;
      source = null;
      sink = null;
      handshake = null;
      protocol = null;

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

      if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
        throw routeException;
      }
    }
  }
}

接下來(lái)就來(lái)看看RealConnection的connectSocket方法,即第21步:


/** Does all the work necessary to build a full HTTP or HTTPS connection on a raw socket. */
private void connectSocket(int connectTimeout, int readTimeout, int writeTimeout,
    ConnectionSpecSelector connectionSpecSelector) throws IOException {
  rawSocket.setSoTimeout(readTimeout);
  try {
    // 利用rawSocket實(shí)例根據(jù)指定host和port的服務(wù)端建立TCP連接
    Platform.get().connectSocket(rawSocket, route.getSocketAddress(), connectTimeout);
  } catch (ConnectException e) {
    throw new ConnectException("Failed to connect to " + route.getSocketAddress());
  }
  // 為rawSocket的InputStream建立RealBufferedSource實(shí)例旷赖,為rawSocket的OutputStream建立RealBufferedSink實(shí)例
  // 這兩個(gè)實(shí)例就是用來(lái)讀取響應(yīng)和發(fā)送請(qǐng)求的顺又,具體原理可以參考http://www.reibang.com/p/0bc80063afb3
  source = Okio.buffer(Okio.source(rawSocket));
  sink = Okio.buffer(Okio.sink(rawSocket));

  // 只有scheme為https的情況下,下面條件才會(huì)成立
  if (route.getAddress().getSslSocketFactory() != null) {
    // 進(jìn)行TLS握手
    connectTls(readTimeout, writeTimeout, connectionSpecSelector);
  } else { // 對(duì)于scheme為http情況下等孵,則將協(xié)議版本設(shè)置為HTTP/1.1
    protocol = Protocol.HTTP_1_1;
    socket = rawSocket;
  }

  if (protocol == Protocol.SPDY_3 || protocol == Protocol.HTTP_2) {
    socket.setSoTimeout(0); // Framed connection timeouts are set per-stream.

    // 當(dāng)協(xié)議版本為HTTP/2時(shí)稚照,framedConnection才會(huì)被初始化,framedConnection針對(duì)HTTP/2中的Frame結(jié)構(gòu)俯萌。
    FramedConnection framedConnection = new FramedConnection.Builder(true)
        .socket(socket, route.getAddress().url().host(), source, sink)
        .protocol(protocol)
        .build();
    framedConnection.sendConnectionPreface();

    // Only assign the framed connection once the preface has been sent successfully.
    this.framedConnection = framedConnection;
  }
}

private void connectTls(int readTimeout, int writeTimeout,
    ConnectionSpecSelector connectionSpecSelector) throws IOException {
  if (route.requiresTunnel()) {
    createTunnel(readTimeout, writeTimeout);
  }

  Address address = route.getAddress();
  SSLSocketFactory sslSocketFactory = address.getSslSocketFactory();
  boolean success = false;
  SSLSocket sslSocket = null;
  try {
    // 將rawSocket基礎(chǔ)上創(chuàng)建SSLSocket實(shí)例果录,SSLSocket用于發(fā)送請(qǐng)求時(shí)的加密和解析響應(yīng)時(shí)的解密
    sslSocket = (SSLSocket) sslSocketFactory.createSocket(
        rawSocket, address.getUriHost(), address.getUriPort(), true /* autoClose */);

    // 為sslSocket配置ciphers、 TLS版本咐熙、和extensions弱恒,為T(mén)LS握手做準(zhǔn)備
    ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
    // 能運(yùn)行到這里,scheme一定為https棋恼,那么connectionSpec為
    // HttpsHandler.TLS_CONNECTION_SPEC(具體原因可以參考第7步)斤彼,那么下面一定是成立的
    if (connectionSpec.supportsTlsExtensions()) {
      // 下面方法的第三個(gè)參數(shù)就是TLS握手時(shí)通過(guò)ALPN協(xié)商使用哪個(gè)應(yīng)用層協(xié)議時(shí)建議的協(xié)議列表,
      // 由于scheme為https蘸泻,那么address.getProtocols()列表為HTTP_1_1_ONLY(具體原因可以參考第7步)琉苇,
      // 即只支持HTTP/1.1
      Platform.get().configureTlsExtensions(
          sslSocket, address.getUriHost(), address.getProtocols());
    }

    // Force handshake. This can throw!
    sslSocket.startHandshake();
    Handshake unverifiedHandshake = Handshake.get(sslSocket.getSession());

    // Verify that the socket's certificates are acceptable for the target host.
    if (!address.getHostnameVerifier().verify(address.getUriHost(), sslSocket.getSession())) {
      X509Certificate cert = (X509Certificate) unverifiedHandshake.peerCertificates().get(0);
      throw new SSLPeerUnverifiedException("Hostname " + address.getUriHost() + " 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.
    if (address.getCertificatePinner() != CertificatePinner.DEFAULT) {
      TrustRootIndex trustRootIndex = trustRootIndex(address.getSslSocketFactory());
      List<Certificate> certificates = new CertificateChainCleaner(trustRootIndex)
          .clean(unverifiedHandshake.peerCertificates());
      address.getCertificatePinner().check(address.getUriHost(), certificates);
    }

    // 上面是TLS握手相關(guān)的代碼,有興趣的同學(xué)可以自己研究下悦施,執(zhí)行到這里說(shuō)明握手成功并扇,
    // 這是就會(huì)保存ALPN協(xié)商后的應(yīng)用層協(xié)議名稱(chēng)
    String maybeProtocol = connectionSpec.supportsTlsExtensions()
        ? Platform.get().getSelectedProtocol(sslSocket)
        : null;
    socket = sslSocket;
    // 更新source和sink,使其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);
    }
  }
}

3.4 在有請(qǐng)求體的情況下往請(qǐng)求體中寫(xiě)入內(nèi)容

由于HttpsURLConnectionImpl對(duì)HttpURLConnectionImpl中g(shù)etOutputStream方法沒(méi)有任何裝飾穷蛹,所以直接看HttpURLConnectionImpl中的getOutputStream方法,即第25步:

@Override public final OutputStream getOutputStream() throws IOException {
  connect();

  // 通過(guò)OutputStream寫(xiě)入數(shù)據(jù)時(shí)昼汗,首先會(huì)寫(xiě)入到下面sink實(shí)例中肴熏,該sink實(shí)例是通過(guò)第15步中的requestBodyOut創(chuàng)建的。
  // 通過(guò)第15步中可知顷窒,當(dāng)OutputStream的類(lèi)型為RetryableSink時(shí)蛙吏,通過(guò)下面的sink實(shí)例寫(xiě)入的數(shù)據(jù)會(huì)保存到內(nèi)存中源哩;
  // 否則對(duì)于HTTP/1.x,通過(guò)sink實(shí)例會(huì)將數(shù)據(jù)寫(xiě)入到第21步中提到的sink字段中鸦做,對(duì)于HTTP/2励烦,通過(guò)sink實(shí)例寫(xiě)入的數(shù)據(jù)在達(dá)到一Frame(默認(rèn)值為16KB)時(shí),就會(huì)將該Frame寫(xiě)入到第21步中的提到的sink字段中泼诱。
  BufferedSink sink = httpEngine.getBufferedRequestBody();
  if (sink == null) {
    throw new ProtocolException("method does not support a request body: " + method);
  } else if (httpEngine.hasResponse()) {
    throw new ProtocolException("cannot write request body after response has been read");
  }

  return sink.outputStream();
}

接下來(lái)看一下HttpEngine的getBufferedRequestBody方法坛掠,即第26步:

public BufferedSink getBufferedRequestBody() {
  BufferedSink result = bufferedRequestBody;
  if (result != null) return result;
  Sink requestBody = getRequestBody();
  return requestBody != null
      ? (bufferedRequestBody = Okio.buffer(requestBody))
      : null;
}

/** Returns the request body or null if this request doesn't have a body. */
public Sink getRequestBody() {
  if (cacheStrategy == null) throw new IllegalStateException();
  // requestBodyOut就是15步中提到的requestBodyOut
  return requestBodyOut;
}

3.5 從服務(wù)端獲取響應(yīng)

由于HttpsURLConnectionImpl對(duì)HttpURLConnectionImpl中g(shù)etInputStream方法沒(méi)有任何裝飾,所以直接看HttpURLConnectionImpl中的getInputStream方法治筒,即第29步:

@Override public final InputStream getInputStream() throws IOException {
  if (!doInput) {
    throw new ProtocolException("This protocol does not support input");
  }

  HttpEngine response = getResponse();

  // if the requested file does not exist, throw an exception formerly the
  // Error page from the server was returned if the requested file was
  // text/html this has changed to return FileNotFoundException for all
  // file types
  if (getResponseCode() >= HTTP_BAD_REQUEST) {
    throw new FileNotFoundException(url.toString());
  }

  return response.getResponse().body().byteStream();
}

由于HttpURLConnectionImpl中的getResponse方法是私有方法屉栓,即不會(huì)被裝飾,所以直接看HttpURLConnectionImpl中的getResponse方法耸袜,即第30步:

private HttpEngine getResponse() throws IOException {
  initHttpEngine();

  if (httpEngine.hasResponse()) {
    return httpEngine;
  }

  while (true) {
    if (!execute(true)) {
      continue;
    }

    ......
  }
}

由于HttpURLConnectionImpl中的execute方法是私有方法友多,即不會(huì)被裝飾,所以直接看HttpURLConnectionImpl中的execute方法句灌,即第31步:

private boolean execute(boolean readResponse) throws IOException {
    ......
    if (readResponse) {
      httpEngine.readResponse();
    }
    ......
}

接下來(lái)看下HttpEngine的readResponse方法夷陋,即第32步:

public void readResponse() throws IOException {
  if (userResponse != null) {
    return; // Already ready.
  }
  if (networkRequest == null && cacheResponse == null) {
    throw new IllegalStateException("call sendRequest() first!");
  }
  if (networkRequest == null) {
    return; // No network response to read.
  }

  Response networkResponse;

  if (forWebSocket) {
    httpStream.writeRequestHeaders(networkRequest);
    networkResponse = readNetworkResponse();

  } else if (!callerWritesRequestBody) {
    networkResponse = new NetworkInterceptorChain(0, networkRequest).proceed(networkRequest);

  } else {
    // 由第13步中的中newHttpEngine方法可知欠拾,forWebSocket為false胰锌、callerWritesRequestBody為true,因此會(huì)運(yùn)行到這里
    // 將bufferedRequestBody中剩下的數(shù)據(jù)(即不夠一個(gè)Segment的數(shù)據(jù))全部移動(dòng)到requestBodyOut中藐窄。
    if (bufferedRequestBody != null && bufferedRequestBody.buffer().size() > 0) {
      bufferedRequestBody.emit();
    }

    if (sentRequestMillis == -1) { 
      // sentRequestMillis等于-1代表請(qǐng)求頭還沒(méi)有寫(xiě)入到第21步中的提到的sink字段资昧,沒(méi)有寫(xiě)入的原因是請(qǐng)求體的長(zhǎng)度是未知的。
      if (OkHeaders.contentLength(networkRequest) == -1
          && requestBodyOut instanceof RetryableSink) {
        long contentLength = ((RetryableSink) requestBodyOut).contentLength();
        networkRequest = networkRequest.newBuilder()
            // 添加頭字段Content-Length荆忍,用于告訴服務(wù)端請(qǐng)求體的長(zhǎng)度
            .header("Content-Length", Long.toString(contentLength))
            .build();
      }
      // writeRequestHeaders最終將請(qǐng)求頭寫(xiě)入到RealBufferedSink實(shí)例中格带,該RealBufferedSink實(shí)例是下面第21步中的提到的sink字段,對(duì)應(yīng)第33步
      httpStream.writeRequestHeaders(networkRequest);
    }

    if (requestBodyOut != null) {
      if (bufferedRequestBody != null) {
        // This also closes the wrapped requestBodyOut.
        bufferedRequestBody.close();
      } else {
        requestBodyOut.close();
      }
      if (requestBodyOut instanceof RetryableSink) {
        // 由第15步可知刹枉,當(dāng)requestBodyOut的類(lèi)型是RetryableSink時(shí)叽唱,寫(xiě)入到requestBodyOut中的請(qǐng)求體數(shù)據(jù)是保持在內(nèi)存中,下面的方法就是將內(nèi)存中的請(qǐng)求體數(shù)據(jù)寫(xiě)入到第21步中的提到的sink字段中微宝,對(duì)應(yīng)第34步棺亭。
        httpStream.writeRequestBody((RetryableSink) requestBodyOut);
      }
    }

    // 從服務(wù)端讀取響應(yīng)
    networkResponse = readNetworkResponse();
  }

  receiveHeaders(networkResponse.headers());

  // If we have a cache response too, then we're doing a conditional get.
  if (cacheResponse != null) {
    if (validate(cacheResponse, networkResponse)) {
      // 執(zhí)行到這里,說(shuō)明過(guò)期的緩存的響應(yīng)仍然是可用的蟋软,這時(shí)直接使用緩存的響應(yīng)镶摘,對(duì)應(yīng)于2.3中第5步肯定的情況
      userResponse = cacheResponse.newBuilder()
          .request(userRequest)
          .priorResponse(stripBody(priorResponse))
          // combine方法用于更新緩存的響應(yīng)的新鮮度,比如修改Last-Modified字段岳守,對(duì)應(yīng)2.3中的第7步
          .headers(combine(cacheResponse.headers(), networkResponse.headers()))
          .cacheResponse(stripBody(cacheResponse))
          .networkResponse(stripBody(networkResponse))
          .build();
      networkResponse.body().close();
      releaseStreamAllocation();

      // Update the cache after combining headers but before stripping the
      // Content-Encoding header (as performed by initContentStream()).
      InternalCache responseCache = Internal.instance.internalCache(client);
      responseCache.trackConditionalCacheHit();
      responseCache.update(cacheResponse, stripBody(userResponse));
      userResponse = unzip(userResponse);
      return;
    } else {
      closeQuietly(cacheResponse.body());
    }
  }

  // 運(yùn)行到這里凄敢,說(shuō)明緩存響應(yīng)已經(jīng)不新鮮了,這時(shí)使用networkResponse湿痢,對(duì)應(yīng)于2.3中第5步否定的情況
  userResponse = networkResponse.newBuilder()
      .request(userRequest)
      .priorResponse(stripBody(priorResponse))
      .cacheResponse(stripBody(cacheResponse))
      .networkResponse(stripBody(networkResponse))
      .build();

  if (hasBody(userResponse)) {
    // maybeCache是用于對(duì)網(wǎng)絡(luò)響應(yīng)的緩存處理涝缝,對(duì)應(yīng)于2.3中的第8步,可以看出只有有響應(yīng)體的響應(yīng)才會(huì)被緩存
    maybeCache();
    userResponse = unzip(cacheWritingResponse(storeRequest, userResponse));
  }
}

/**
 * 返回 true 代表應(yīng)該使用緩存響應(yīng),否則使用服務(wù)端返回的響應(yīng)
 * 下面這個(gè)方法就是用來(lái)判斷過(guò)期的緩存的響應(yīng)是否還可用
 */
private static boolean validate(Response cached, Response network) {
  if (network.code() == HTTP_NOT_MODIFIED) {
    return true;
  }

  // The HTTP spec says that if the network's response is older than our
  // cached response, we may return the cache's response. Like Chrome (but
  // unlike Firefox), this client prefers to return the newer response.
  Date lastModified = cached.headers().getDate("Last-Modified");
  if (lastModified != null) {
    Date networkLastModified = network.headers().getDate("Last-Modified");
    if (networkLastModified != null
        && networkLastModified.getTime() < lastModified.getTime()) {
      return true;
    }
  }

  return false;
}

接下來(lái)看下HttpEngine的readNetworkResponse方法俊卤,即第35步:

private Response readNetworkResponse() throws IOException {
  // 請(qǐng)求的所有數(shù)據(jù)都被寫(xiě)入到了第21步中的提到的sink字段中嫩挤,
  // 下面的finishRequest方法是將sink字段中剩余的數(shù)據(jù)(即不夠一個(gè)Segment的數(shù)據(jù))寫(xiě)入到Socket的OutputStream中。
  httpStream.finishRequest();

  // 通過(guò)httpStream讀取狀態(tài)行和響應(yīng)頭消恍,并且將OkHeaders.SENT_MILLIS岂昭、OkHeaders.RECEIVED_MILLIS記錄在響應(yīng)頭中。
  Response networkResponse = httpStream.readResponseHeaders()
      .request(networkRequest)
      .handshake(streamAllocation.connection().getHandshake())
      .header(OkHeaders.SENT_MILLIS, Long.toString(sentRequestMillis))
      .header(OkHeaders.RECEIVED_MILLIS, Long.toString(System.currentTimeMillis()))
      .build();

  if (!forWebSocket) {
    // 創(chuàng)建RealResponseBody類(lèi)型的請(qǐng)求體實(shí)例狠怨,通過(guò)該實(shí)例的byteStream讀取的數(shù)據(jù)最終來(lái)自于第21步中提到的source字段约啊。
    networkResponse = networkResponse.newBuilder()
        .body(httpStream.openResponseBody(networkResponse))
        .build();
  }

  if ("close".equalsIgnoreCase(networkResponse.request().header("Connection"))
      || "close".equalsIgnoreCase(networkResponse.header("Connection"))) {
    streamAllocation.noNewStreams();
  }

  return networkResponse;
}

private void maybeCache() throws IOException {
  InternalCache responseCache = Internal.instance.internalCache(client);
  if (responseCache == null) return;

  if (!CacheStrategy.isCacheable(userResponse, networkRequest)) {
    // 走到這里說(shuō)明網(wǎng)路緩存不允許緩存,對(duì)應(yīng)2.3中第8步否定的情況
    if (HttpMethod.invalidatesCache(networkRequest.method())) {
      try {
        // 移除過(guò)期的緩存響應(yīng)
        responseCache.remove(networkRequest);
      } catch (IOException ignored) {
        // The cache cannot be written.
      }
    }
    return;
  }

  // 運(yùn)行的到這里說(shuō)明該網(wǎng)絡(luò)響應(yīng)可以緩存佣赖,對(duì)應(yīng)于2.3中的第8步肯定的情況
  storeRequest = responseCache.put(stripBody(userResponse));
}

到這里恰矩,HttpURLConnection的源碼分析完畢。

4 總結(jié)

首先通過(guò)下圖概括一下HttpURLConnection的操作流程:
1> 首先通過(guò)Socket接口與服務(wù)端建立TCP連接憎蛤,連接的最大空閑時(shí)間為5分鐘外傅。
所有建立的TCP連接都會(huì)放到連接池中進(jìn)行集中管理,具體細(xì)節(jié)請(qǐng)參考2.4.1俩檬。
2> 接著就是確定使用的應(yīng)用層協(xié)議
對(duì)于scheme為http的情況萎胰,直接使用HTTP/1.1協(xié)議。
對(duì)于scheme為https的情況棚辽,通過(guò)ALPN和服務(wù)端協(xié)商使用的應(yīng)用層協(xié)議技竟,協(xié)商的細(xì)節(jié)請(qǐng)參考2.2,協(xié)商的結(jié)果一定是使用HTTP/1.1屈藐。
3> 接著就是在TCP連接上發(fā)生請(qǐng)求和接受響應(yīng)榔组,如下圖:


上圖根據(jù)HTTP/1.1繪制的,OkHttp目前不支持Pipelining的特性联逻,因此HttpURLConnection中的實(shí)現(xiàn)與上圖的左側(cè)相同搓扯,一個(gè)TCP連接在同一時(shí)刻只能服務(wù)于1對(duì)請(qǐng)求-響應(yīng),如果在一個(gè)請(qǐng)求的響應(yīng)沒(méi)有返回的情況下發(fā)起另一個(gè)請(qǐng)求包归,則會(huì)新建一個(gè)TCP連接锨推。
雖然HttpURLConnection默認(rèn)使用的是HTTP/1.1,但是還是通過(guò)下圖說(shuō)明一下對(duì)于HTTP/2在TCP連接上發(fā)生請(qǐng)求和接受響應(yīng)的過(guò)程:

一個(gè)TCP連接在同一時(shí)刻最多可以服務(wù)于4對(duì) 請(qǐng)求-響應(yīng)(具體原因請(qǐng)參考2.4.1)箫踩,上圖中的4行代表4個(gè)并行的Stream(用StreamId進(jìn)行標(biāo)識(shí))爱态,每一個(gè)方塊代表一個(gè)Frame(請(qǐng)求和響應(yīng)都會(huì)被分割成多個(gè)Frame,具體可以參考2.1)境钟,F(xiàn)rame中會(huì)攜帶StreamId锦担,當(dāng)客戶(hù)端同時(shí)接受多個(gè)響應(yīng)時(shí)可以通過(guò)StreamId將響應(yīng)進(jìn)行區(qū)分不同的響應(yīng)。

上面3步概括了HttpURLConnection的操作流程慨削,下面就來(lái)總結(jié)一下請(qǐng)求在寫(xiě)入到Socket/SSLSocket之前都做了哪些處理洞渔,首先通過(guò)下圖看一下HTTP請(qǐng)求和響應(yīng)的內(nèi)部結(jié)構(gòu):

HTTP請(qǐng)求和響應(yīng)的內(nèi)部結(jié)構(gòu)

上圖中請(qǐng)求的結(jié)構(gòu)是被寫(xiě)入到Socket/SSLSocket中的最終格式套媚,那么請(qǐng)求的最終格式是怎么得到的,那就通過(guò)下圖了解一下具體的流程:

上圖中TLS行之上對(duì)請(qǐng)求的處理都是HttpURLConnection所要實(shí)現(xiàn)的磁椒,下面列舉一下上面用到的處理請(qǐng)求的操作:
對(duì)于HTTP/1.1(上圖左側(cè))堤瘤,對(duì)請(qǐng)求沒(méi)有做任何的操作,直接按照HTTP請(qǐng)求和響應(yīng)的內(nèi)部結(jié)構(gòu)圖中的結(jié)構(gòu)將請(qǐng)求寫(xiě)入Socket/SSLSocket中浆熔;
對(duì)于HTTP/2本辐,是沒(méi)有請(qǐng)求行的,請(qǐng)求行被拆分成鍵值對(duì)放到了請(qǐng)求頭中医增,首先會(huì)對(duì)請(qǐng)求頭進(jìn)行頭部壓縮(在Hpack.java中實(shí)現(xiàn))得到請(qǐng)求頭塊慎皱,那么請(qǐng)求行內(nèi)容也會(huì)被壓縮泊窘,有興趣的同學(xué)可以參考:
HPACK: Header Compression for HTTP/2 -- 官方專(zhuān)門(mén)為頭部壓縮給出的規(guī)范
HTTP/2 頭部壓縮技術(shù)介紹
接著將請(qǐng)求頭塊進(jìn)行分割得到多個(gè)請(qǐng)求頭塊片段延刘,每個(gè)請(qǐng)求頭塊片段對(duì)應(yīng)一個(gè)Frame,具體細(xì)節(jié)可以請(qǐng)參考2.1吐限,具體實(shí)現(xiàn)在Http2.java中忽刽。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末天揖,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子跪帝,更是在濱河造成了極大的恐慌今膊,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件歉甚,死亡現(xiàn)場(chǎng)離奇詭異万细,居然都是意外死亡扑眉,警方通過(guò)查閱死者的電腦和手機(jī)纸泄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)腰素,“玉大人聘裁,你說(shuō)我怎么就攤上這事」В” “怎么了衡便?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)洋访。 經(jīng)常有香客問(wèn)我镣陕,道長(zhǎng),這世上最難降的妖魔是什么姻政? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任呆抑,我火速辦了婚禮,結(jié)果婚禮上汁展,老公的妹妹穿的比我還像新娘鹊碍。我一直安慰自己厌殉,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布侈咕。 她就那樣靜靜地躺著公罕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪耀销。 梳的紋絲不亂的頭發(fā)上楼眷,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音熊尉,去河邊找鬼摩桶。 笑死,一個(gè)胖子當(dāng)著我的面吹牛帽揪,可吹牛的內(nèi)容都是我干的硝清。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼转晰,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼芦拿!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起查邢,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蔗崎,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后扰藕,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體缓苛,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年邓深,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了未桥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡芥备,死狀恐怖冬耿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情萌壳,我是刑警寧澤亦镶,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站袱瓮,受9級(jí)特大地震影響缤骨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜尺借,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一绊起、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧褐望,春花似錦勒庄、人聲如沸串前。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)荡碾。三九已至,卻和暖如春局装,著一層夾襖步出監(jiān)牢的瞬間坛吁,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工铐尚, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拨脉,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓宣增,卻偏偏與公主長(zhǎng)得像玫膀,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子爹脾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

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