的四次学习实践,源码分析

议论 HTTP/2 的协商协商机制

2016/04/16 · 基础技术 ·
HTTP/2

本文小编: 伯乐在线 –
JerryQu
。未经小编许可,禁止转发!
迎接加入伯乐在线 专辑小编。

文章目录

  • HTTP Upgrade
  • ALPN 扩展
  • 小结

在过去的多少个月里,我写了不可计数关于 HTTP/2
的稿子,也做过一些场有关分享。我在向我们介绍 HTTP/2
的进程中,有部分题目时常会被问到。例如要布局 HTTP/2 一定要先晋级到 HTTPS
么?升级到 HTTP/2 之后,不帮衬 HTTP/2
的浏览器仍可以健康访问么?本文重点介绍 HTTP/2
的情商机制,精通了服务端和客户端如何协商出最后利用的 HTTP
协议版本,那多个问题就一挥而就了。

亚洲必赢官网 1

亚洲必赢官网 2

1 概述

在Android应用中大多会选取Http协议来访问网络,
Android首要提供了二种格局(HttpURLConnection、HttpClient)来展开Http操作,那么应该使用那种办法访问网络呢?
关于那个话题大家可以产考Android访问网络,使用HttpURLConnection仍然HttpClient?,
最后我要补充一句,现在的利用最低版本都不会低于Android2.3,所以据悉上边小说的下结论,应该使用HttpURLConnection进行网络请求,并且在Android6.0版本中HttpClient已经被移除。

在TCP/IP的五层协商模型中,Socket是对传输层协议的包装,Socket本身并不是说道,而是一个调用接口(API),如今传输层协议有TCP、UDP商事三种,Socket可以指定使用的传输层协议,当使用TCP协议进行再而三时,该Socket连接就是一个TCP连接。HttpURLConnection建立的总是是TCP连接,TCP连接的创制须要经过四遍握手进度,然后初始慢启动。

HTTP Upgrade

为了更有利地布局新说道,HTTP/1.1 引入了 Upgrade
机制,它使得客户端和服务端之间可以借助已部分 HTTP
语法升级到任何协议。那一个机制在 RFC7230 的「6.7
Upgrade」这一节中有详细描述。

要发起 HTTP/1.1 协议升级,客户端必须在伏乞底部中指定那多少个字段:

Connection: Upgrade Upgrade: protocol-name[/protocol-version]

1
2
Connection: Upgrade
Upgrade: protocol-name[/protocol-version]

客户端通过 Upgrade
底部字段列出所希望升高到的商事和版本,多个协议时期用 ,(0x2C,
0x20)隔开。除了那七个字段之外,一般每种新协议还会须求客户端发送额外的新字段。

设若服务端不允许升级或者不扶助 Upgrade
所列出的磋商,直接忽略即可(当成 HTTP/1.1 请求,以 HTTP/1.1
响应);如若服务端统一升级,那么需求这样响应:

HTTP/1.1 101 Switching Protocols Connection: upgrade Upgrade:
protocol-name[/protocol-version] [… data defined by new protocol
…]

1
2
3
4
5
HTTP/1.1 101 Switching Protocols
Connection: upgrade
Upgrade: protocol-name[/protocol-version]
 
[… data defined by new protocol …]

可以看出,HTTP Upgrade 响应的状态码是
101,并且响应正文可以运用新协议定义的多寡格式。

如果我们以前使用过 WebSocket,应该已经对 HTTP Upgrade
机制有所明白。上边是创建 WebSocket 连接的 HTTP 请求:

GET ws://example.com/ HTTP/1.1 Connection: Upgrade Upgrade: websocket
Origin: Sec-WebSocket-Version: 13 Sec-WebSocket-Key:
d4egt7snxxxxxx2WcaMQlA== Sec-WebSocket-Extensions: permessage-deflate;
client_max_window_bits

1
2
3
4
5
6
7
GET ws://example.com/ HTTP/1.1
Connection: Upgrade
Upgrade: websocket
Origin: http://example.com
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: d4egt7snxxxxxx2WcaMQlA==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

那是服务端同意升级的 HTTP 响应:

HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: websocket
Sec-WebSocket-Accept: gczJQPmQ4Ixxxxxx6pZO8U7UbZs=

1
2
3
4
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: gczJQPmQ4Ixxxxxx6pZO8U7UbZs=

在那之后,客户端和服务端之间就可以使用 WebSocket
协议进行双向数据通信,跟 HTTP/1.1 没提到了。可以看来,WebSocket
连接的树立就是杰出的 HTTP Upgrade 机制。

眼看,那几个机制也足以用做 HTTP/1.1 到 HTTP/2 的协商升级。例如:

GET / HTTP/1.1 Host: example.com Connection: Upgrade, HTTP2-Settings
Upgrade: h2c HTTP2-Settings:

1
2
3
4
5
GET / HTTP/1.1
Host: example.com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings:

在 HTTP Upgrade 机制中,HTTP/2 的情商名称是 h2c,代表 HTTP/2
ClearText。倘诺服务端不支持 HTTP/2,它会忽视 Upgrade 字段,直接再次来到HTTP/1.1 响应,例如:

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

1
2
3
4
5
HTTP/1.1 200 OK
Content-Length: 243
Content-Type: text/html
 

倘若服务端帮助 HTTP/2,那就足以回复 101
状态码及对应底部,并且在响应正文中可以直接行使 HTTP/2 二进制帧:

HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: h2c [
HTTP/2 connection … ]

1
2
3
4
5
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: h2c
 
[ HTTP/2 connection … ]

以下是因而 HTTP Upgrade 机制将 HTTP/1.1 升级到 HTTP/2 的 Wireshark
抓包(两张图能够对照来看):

亚洲必赢官网 3

亚洲必赢官网 4

据悉 HTTP/2 协议中的描述,额外补充几点:

  • 41 号包中,客户端发起的合计升级请求中,必须通过 HTTP2-Settings
    指定一个经过 Base64 编码过的 HTTP/2 SETTINGS 帧;
  • 45 号包中,服务端同意协商升级,响应正文中务必包括 HTTP/2 SETTING
    帧(二进制格式,不须求 Base64 编码);
  • 62 号包中,客户端可以起来发送种种 HTTP/2 帧,但首先个帧必须是 Magic
    帧(内容稳定为 PRI * HTTP/2.0rnrnSMrnrn),做为协议升级的最后肯定;

HTTP Upgrade
机制自我没什么问题,但很不难受网络中间环节影响。例如不可以正确处理
Upgrade 头部的代办节点,很可能导致最后升任战败。之前大家计算过
WebSocket 的接入情状,发现大量家喻户晓辅助 WebSocket
的浏览器却一筹莫展升迁,只能选用降级方案。

前边的稿子也涉嫌了当下的移动端网络常见性能问题,以及相应的优化策略,假设把HTTP1.1
替换为 HTTP2.0,可以说是网络性能优化的一步大棋。这几天对 iOS HTTP2.0
进行了简易的调研、测试,在此做个简易的下结论

眼前的小说也事关了近日的移位端网络常见性能问题,以及对应的优化策略,要是把HTTP1.1
替换为 HTTP2.0,可以说是网络性能优化的一步大棋。这几天对 iOS HTTP2.0
进行了简便的调研、测试,在此做个大概的下结论

2 预备知识

ALPN 扩展

HTTP/2 探讨本身并不曾须求它必须依据HTTPS(TLS)计划,不过由于以下三个原因,实际使用中,HTTP/2 和 HTTPS
大约都是松绑在同步:

  • HTTP 数据精通传输,数据很简单被中间节点窥视或篡改,HTTPS
    可以保障数据传输的保密性、完整性和不被假冒;
  • 正因为 HTTPS 传输的数额对中级节点保密,所以它装有更好的连通性。基于
    HTTPS 安顿的新协议抱有更高的连年成功率;
  • 方今主流浏览器,都只帮助基于 HTTPS 布署的 HTTP/2;

假定前方五个原因还不足以说服你,最终这些相对有说服力,除非你的 HTTP/2
服务只打算给自己客户端用。

上边介绍在 HTTPS 中,浏览器和服务端之间怎么协商是或不是采纳 HTTP/2。

基于 HTTPS 的协议协商格外容易,多了 TLS 之后,双方必须等到成功建立 TLS
连接之后才能发送应用数据。而要建立 TLS 连接,本来就要拓展 CipherSuite
等参数的商事。引入 HTTP/2 之后,需求做的只是在原先的协商机制中把对 HTTP
协议的磋商加进去。

谷歌 在 SPDY 切磋中支付了一个名为 NPN(Next Protocol
Negotiation,下一代协议协商)的 TLS 扩张。随着 SPDY 被 HTTP/2 取代,NPN
也被合法修订为 ALPN(Application Layer Protocol
Negotiation,应用层协议协商)。二者的目标和促成原理基本一致,那里只介绍后者。如图:

亚洲必赢官网 5

能够见见,客户端在建立 TLS 连接的 Client Hello 握手中,通过 ALPN
增加列出了团结支持的种种应用层协议。其中,HTTP/2 协议名称是 h2

亚洲必赢官网 6

假诺服务端援救 HTTP/2,在 Server Hello 中指定 ALPN 的结果为 h2
就可以了;倘使服务端不接济 HTTP/2,从客户端的 ALPN
列表中选一个投机辅助的即可。

并不是持有 HTTP/2 客户端都辅助 ALPN,理论上建立 TLS
连接后,依旧得以再经过 HTTP Upgrade
举办研究升级,只是那样会额外引入三遍往返。

本文的大概思路是介绍 HTTP1.1 的弊端、HTTP2.0 的优势、HTTP2.0
的商谈机制、iOS 客户端如何衔接
HTTP2.0,以及怎么着对其展开调节。首要仍旧加剧纪念、方便中期查阅,文末的资料相比较本文或许是更有价值的。

享用从前我依旧要推荐下我要好建的iOS开发学习群:680565220,群里都是学ios开发的,假如您正在读书ios
,作者欢迎你进入,前些天享受的那么些案例已经上传到群文件,大家都是软件开发党,不定期分享干货(只有iOS软件开发相关的),包涵自家自己收拾的一份2017最新的iOS进阶资料和高档开发教程,欢迎进阶中和进想深切iOS的小伙伴。

2.1 HTTP的发展史

1> 1996年HTTP/1.0诞生
在发送请求从前,建立一个新的TCP连接,在那么些请求完结后,该TCP连接就会被关门,由此每发送一条请求都要求重新建立TCP连接,那样就会招致性能消耗和延迟,因而HttpURLConnection通过充足请求头字段Connection:
keep-alive来优化那个题目,但这并不曾赢得普遍协助,所以问题如故存在;HttpURLConnection通过连接池(ConnectionPool)来优化这么些题目,连接池中TCP连接的数量是从未上限的,理论上说可以并发处理无数个HTTP请求,不过服务端肯定是不容许那样的事务时有发生。

2> 1999年HTTP/1.1诞生
HTTP/1.1规范规定使用长连接(Persistent
Connection),那样的话可以在同一个TCP连接上发送多少个请求,一定水准上弥补了HTTP1.0每一次发送请求都要成立连接导致的习性消耗和推迟,如果要关闭连接,要求丰盛请求头字段Connection:
close;同时为了下降等待响应的耗时,HTTP/1.1正经提出管线化(Pipelining)来促成在在单个TCP连接上发送多个请求而不等待相应响应,可是服务器必须依据接收请求的顺序发送响应,那时多少个响应有可能会被阻塞的,通过下图你可以更进一步鲜明的知道Pipelining的落实原理:

亚洲必赢官网 7

HttpURLConnection近来是不支持管线化的,因此HttpURLConnection中选取上图左边的艺术处理
请求-响应的。

3>
2015年HTTP/2诞生
官方HTTP/2规范
中文版HTTP/2规范,谢谢作者的分享。
HTTP/2规范提出了多路复用(Multiplexing)来兑现多个请求在一个接连下面世,幸免了HTTP/1.1中的多少个响应可能会被卡住的题目,通过下图你可以更进一步清楚的敞亮多路复用的兑现原理:

亚洲必赢官网 8

上图中间有些代表TCP连接,4行代表4个互相的Stream,每一个四方可以清楚成一个Frame,在HTTP/2中呼吁或者响应会被分割成四个Frame被传送,官方正式HTTP
Frames描述了Frame的格式:

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

Frame由首部(9个字节长度)和负载(上图中的Frame
Payload空间,用来负载请求或者响应的半空中)两部分构成,首部由上图中的前5个区域整合:
Length:代表负载的长短,默许最大值为2^14
(16384),假设想将最大值设置为[2^14 + 1,2^24 –
1]限制中的值,那么可以经过发送
SETTINGS花色的Frame来表明发送方可以承受的最大负荷大小。
Type:表示帧的品类,官方HTTP/2规范合计定义了10种Frame类型,其中HEADERS、PUSH_PROMISE、CONTINUATION多个品类是用来负载请求头或者响应头的,具体可以参照Header
Compression and
Decompression;其中DATA的四次学习实践,源码分析。项目是用来负载请求体或者响应体的。
Flags:用来发挥一种语义,比如
END_HEADERS:在HEADERS、PUSH_PROMISE、CONTINUATION品类的Frame中,表明请求头或者响应头截至。
END_STREAM:表达请求或者响应甘休。
R: 保留位。
Stream Identifier: Stream标识,用来标识该Frame来自于那些Stream。

在 HTTP/2 中一对 请求-响应
就相应一个Stream,换句话说一个Stream的义务就是到位一对 请求-响应
的传导,然后就没用了;接收端通过Frame中带走的StreamId(即Stream
Identifier)来分别差其余呼吁或者响应,接着连接Frame得到拿到完全的伸手或者响应。

小心:请求头或者响应头必须作为一个连接的Frame种类传送,不能有其余其他类型或此外其他Stream上的交错Frame,。

小结

看来此间,相信你一定可以很好地回复本文开首提议的题材。

HTTP/2 要求依照 HTTPS 陈设是时下主流浏览器的必要。借使你的 HTTP/2
服务要扶助浏览器访问,那就非得依照 HTTPS
布署;借使只给协调客户端用,可以不安排HTTPS(其一页面历数了多如牛毛支撑
h2c 的 HTTP/2 服务端、客户端达成)。

支撑 HTTP/2 的 Web Server 基本都协理 HTTP/1.1。这样,尽管浏览器不扶助HTTP/2,双方也能够协商出可用的 HTTP 版本,没有包容性问题。如下表:

浏览器 服务器 协商结果
不支持 HTTP/2 不支持 HTTP/2 不协商,使用 HTTP/1.1
不支持 HTTP/2 支持 HTTP/2 不协商,使用 HTTP/1.1
支持 HTTP/2 不支持 HTTP/2 协商,使用 HTTP/1.1
支持 HTTP/2 支持 HTTP/2 协商,使用 HTTP/2

理所当然,本文切磋的是通用情形。对于团结完成的客户端和服务端,即使打算利用
HTTP/2 ClearText,由于 HTTP Upgrade
协商会扩充两次往返,可以须求双方必须匡助 HTTP/2,直接发送 HTTP/2
数据,不走协商。

打赏帮衬自己写出愈多好小说,谢谢!

打赏笔者

HTTP 1.1

  • 虽说 HTTP1.1 默认是翻开 Keep-Alive
    长连接的,一定水准上弥补了HTTP1.0老是请求都要创制连接的后天不足,不过依旧存在
    head of line
    blocking,要是出现一个较差的网络请求,会潜移默化连续的网络请求。为何呢?倘诺您发出1、2、3
    多少个网络请求,那么 Response 的依次 2、3
    要在首先个网络请求之后,以此类推

  • 本着同一域名,在央浼较多的动静下,HTTP1.1
    会开辟多个延续,据说浏览器一般是6-8
    个,较多连接也会招致延迟增大,资源消耗等问题

  • HTTP1.1 不安全,可能存在被曲解、被窃听、被伪装等题材。当然,前阵子
    Apple 推广 HTTPS 的时候,相信广大人早已接入 HTTPS

  • HTTP 的底部没有减掉,header
    的高低也是传输的负担,带来愈多的流量消耗和传导延迟。并且很多 header
    是如出一辙的,重复传输是绝非须求的。

  • 服务端不可以主动推送资源到客户端

  • HTTP1.1的格式是文本格式,基于文本做一些增加、优化相比较较艰难,但是文本格式易于阅读和调节,但HTTPS之后,也变成二进制格式了,这么些优势也荡然无存

正文的大体思路是介绍 HTTP1.1 的流弊、HTTP2.0 的优势、HTTP2.0
的合计机制、iOS 客户端怎样衔接
HTTP2.0,以及怎样对其进展调试。首要依然强化回想、方便中期查阅,文末的材料相比较本文或许是更有价值的。

2.2 Upgrade协商机制和APLN协商机制

用来协商使用的应用层协议:
1> Upgrade协商机制(HTTP/1.1引入的Upgrade 机制)
只有在不选取SSL/TLS的景况下,在情商使用更加应用层协议时才会用到Upgrade协商机制,客户端通过发送一个涵盖请求头字段为
Upgrade:协议名称列表 的HTTP/1.1呼吁来倡导应用层协议的协议,例如:

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

客户端通过Upgrade请求头字段中的协议列表(根据优先级降序排列)提议服务端切换来内部的某个协议,上边的哀告指出服务端使用h2c协议(HTTP/2协议直接运行在TCP协议之上,没有中间层SSL/TLS),假如服务端同意使用Upgrade列举的磋商,就会付给如下响应:

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

[ HTTP/2 connection ...

如果服务端不容许或者不援救Upgrade列举的协议,就会一贯忽略(当成 HTTP/1.1
请求,给出HTTP/1.1 响应):

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

...

2> APLN协商机制
率先通过下图来询问HTTPS HTTP SSL/TLS TCP之间的关系:

亚洲必赢官网 9

网景公司(Netscape)开发了原有的SSL(Secure Sockets
Layer)协议,由于协商中严重的安全漏洞没有公布1.0版;版本2.0在1995年九月宣布,由于包蕴了有些安然无恙缺陷,由此须要3.0本子的安排,SSL版本3.0于1996年颁发;SSL
2.0在二零一一年被RFC 6176禁用,SSL 3.0在二〇一五年十月也被RFC
7568禁用;1999年六月,TLS(Transport Layer Security) 1.0本子在RFC
2246中被定义为SSL Version 3.0的晋级版;TLS 1.1是在二零零六年十月的RFC
4346中定义的;TLS 1.2是在二〇〇八年六月的RFC 5246中定义的。

应用层协议协商Application-Layer Protocol
Negotiation
,简称ALPN)是一个TLS增加。ALPN用于协商使用的应用层协议,以防止额外的来回协商通讯;谷歌(Google)在 SPDY 合计中付出了一个名为 NPN(Next Protocol
Negotiation,下一代协议协商)的 TLS 扩充。随着 SPDY 被 HTTP/2
取代(二〇一五年7月,谷歌 发布了布署,移除对SPDY的接济,拥抱
HTTP/2,并将在Chrome 51中生效。),NPN 也被官方修订为 ALPN(Application
Layer Protocol
Negotiation,应用层协议协商);HttpURLConnection中动用ALPN进行应用层协议的商议,在TLS握手的Client
Hello中,客户端会通过ALPN
扩张列出自己支持的各类应用层协议,根据优先级降序排列,服务端即使帮助其中某个应用层协议,就会在Server
Hello中经过ALPN伸张指定协商的结果为该应用层协议。

有关TLS握手阶段的流程,我们可以产考如下文章:
SSL/TLS协议运行机制的概述
TLS
握手优化详解

HTTP/2 协商本身并没有必要必须基于TLS布置,但是实际应用中,HTTP/2
和TLS大约都是松绑在一道,当前主流浏览器都只帮衬基于TLS陈设的 HTTP/2。

打赏援助自己写出越来越多好小说,谢谢!

任选一种支付办法

亚洲必赢官网 10
亚洲必赢官网 11

1 赞 1 收藏
评论

HTTP2.0

在 HTTP2.0中,上边的题材大致都不存在了。HTTP2.0 的安顿来源于 谷歌(Google) 的
SPDY 协议,若是对 SPDY 协议不打听的话,也足以先对 SPDY
举办打探,不过那不影响三番四回读书本文

  • HTTP 2.0
    使用新的二进制格式:基本的协议单位是帧,每个帧都有分歧的品种和用途,规范中定义了10种分化的帧。例如,报头(HEADERS)和数据(DATA)帧组成了主导的HTTP
    请求和响应;其余帧例如 设置(SETTINGS),窗口更新(WINDOW_UPDATE),
    和推送承诺(PUSH_PROMISE)是用来贯彻HTTP/2的别样职能。那多少个呼吁和响应的帧数据通过流来进行数据交流。新的二进制格式是流量控制、优先级、server
    push等效果的根底。

流(Stream):一个Stream是富含一条或多条新闻、ID和预先级的双向通道

音讯(Message):音信由帧组成

帧(Frame):帧有差其他类型,并且是犬牙相错的。他们通过stream
id被再度组建进音讯中

亚洲必赢官网 12

  • 多路复用:也就是一连共享,刚才说到 HTTP1.1的 head of line
    blocking,那么在多路复用的处境下,blocking 已经不设有了。每个连接中
    可以涵盖四个流,而种种流中交错包括着来自两端的帧。也就是说同一个连连中是来自不一致流的数额包混合在联名,如下图所示,每一块代表帧,而相同颜色块来自同一个流,每个流都有谈得来的
    ID,在接收端会按照 ID
    举行重装组合,就是通过那样一种方式来落到实处多路复用。

亚洲必赢官网 13

  • 单纯连接:刚才也说到 1.1 在伸手多的时候,会张开6-8个三番五次,而 HTTP2
    只会敞开一个接连,那样就收缩握手带来的推移。

  • 头部压缩:HTTP2.0 通过 HPACK
    格式来减弱尾部,使用了哈夫曼编码压缩、索引表来对底部大小做优化。索引表是把字符串和数字之间做一个格外,比如method: GET对应索引表中的2,那么只要此前发送过那么些值是,就会缓存起来,之后采取时发现前面发送过该Header字段,并且值相同,就会沿用以前的目录来代表那多少个Header值。具体实验数据足以参见那里:HTTP/2
    尾部压缩技术介绍

亚洲必赢官网 14

  • Server
    Push:就是服务端能够积极推送一些事物给客户端,也被喻为缓存推送。推送的资源得以备客户端日后之需,需求的时候一向拿出来用,升高了速率。具体的实验可以参照这里:iOS
    HTTP/2 Server Push 探索

亚洲必赢官网 15

除此之外上边讲到的风味,HTTP2.0
还有流量控制、流优先级和依赖等功效。愈多细节可以参照:Hypertext
Transfer Protocol Version 2
(HTTP/2)

HTTP 1.1

2.3 HTTP缓存策略

第一通过下图完全的看一下HTTP的缓存策略:

亚洲必赢官网 16

上面会基于源码来讲学HTTP的缓存策略。

有关小编:JerryQu

亚洲必赢官网 17

专注 Web 开发,关注 Web
性能优化与安全。 亚洲必赢官网,
个人主页 ·
我的文章 ·
2 ·
  

亚洲必赢官网 18

iOS 客户端接入HTTP 2.0

iOS 怎么样衔接 HTTP 2.0吗?其实很简短:

  • 有限支撑服务端襄助 HTTP2.0,并且注意下 NPN 或 ALPN
  • 客户端系统版本 iOS 9 +
  • 使用 NSURLSession 代替 NSURLConnection
  • 客户端是行使 h2c 仍然 h2,它们得以说是 HTTP2.0的四个版本,h2 是使用
    TLS 的HTTP2.0商谈,h2c是运作在明文 TCP 探究上的
    HTTP2.0商议。浏览器方今只接济h2,也就是说必须依照HTTPS安排,不过客户端可以不配备HTTPS,因为我司早已计划HTTPS,所以我那边的实施都是根据h2的

即使 HTTP1.1 默许是打开 Keep-Alive
长连接的,一定水准上弥补了HTTP1.0老是请求都要创设连接的毛病,可是照旧留存
head of line
blocking,如若出现一个较差的网络请求,会影响连续的网络请求。为何吧?假使您发出1、2、3
多个网络请求,那么 Response 的各种 2、3 要在率先个网络请求之后,以此类推

2.4 HttpURLConnection相关类的解析

HTTP 2.0的合计机制

地点说了一堆排行,什么NPN、ALPN呀,还有h2、h2c之类的,有点懵逼。NPN(Next
Protocol Negotiation)是一个 TLS 扩张,由 谷歌(Google) 在支付 SPDY
啄磨时提议。随着 SPDY 被 HTTP/2 取代,NPN 也被修订为 ALPN(Application
Layer Protocol
Negotiation,应用层协议协商)。二者目标一致,但完成细节不一样,互相不匹配。以下是它们首要出入:

  • NPN 是服务端发送所扶助的 HTTP 协议列表,由客户端选拔;而 ALPN
    是客户端发送所支撑的 HTTP 协议列表,由服务端拔取;
  • NPN 的商议结果是在 Change Cipher Spec 之后加密发送给服务端;而 ALPN
    的合计结果是因此 Server Hello 明文发给客户端

同时,如今众多地点起首为止对NPN的援救,仅协理ALPN,所以公司利用以来,最佳是一直运用 ALPN。

上面就径直来看望 ALPN 的商事进度是什么样的,ALPN 作为 TLS
的一个增加,其经过可以通过 WireShark 查看 TLS握手进度来查阅

亚洲必赢官网 19

上面通过 WireShark 来拓展调节,接入真机,然后终端输入
rvictl -s 设备 UDID来成立一个辉映到 索爱 的虚构网卡,UUID 可以在
iTunes 中获获得,运行命令后会看到成功创制 rvi0 虚拟网卡的,双击 rvi0
起初调剂。

亚洲必赢官网 20

进去之后,在四哥大上访问页面会有络绎不绝的乞请突显在 WireShark
的界面上,数据太多而不便宜我们针对调试,你可以过滤下域名,只关心您想测试的
ip 地址,比如: ip.addr==111.89.211.191 ,当然你的 ip 要支持HTTP2.0才会有预期的作用啊

亚洲必赢官网 21

上边,就起初通过查看 TLS 握手的历程分析HTTP2.0 的情商进程,刚才也说道
ALPN 协商结果是在 Client hello 和 Server hello
中突显的,那就先来看一下Client hello

亚洲必赢官网 22

可以观望客户端在 Client hello 中列出了和睦帮衬的各样应用层协议,比如
spdy3、h2。那么随着看 Server hello 是怎么复苏的

亚洲必赢官网 23

劳务端会按照 client hello
中的协议列表,发过去自己协助的网络协议,假如服务端支持h2,则直接重临h2,协商成功,假设不扶助h2,则赶回一个其他援救的协商,比如HTTP1.1、spdy3

其一是h2的合计进程,对于刚刚涉及的 h2c 的说道进度,与此不一致,h2c
利用的是HTTP Upgrade 机制,客户端会发送一个 http
1.1的伸手到服务端,那个请求中含有了 http2的升级换代字段,例如:

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

服务端收到这一个请求后,即使辅助 Upgrade 中 列举的合计,这里是
h2c,就会重返接济的响应:

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

  [ HTTP/2 connection ...

本来,不帮忙的话,服务器会回到一个不包括 Upgrade 的报头字段的响应。

本着同一域名,在央浼较多的状态下,HTTP1.1
会开辟四个三番五次,据说浏览器一般是6-8
个,较多连接也会促成延迟增大,资源消耗等问题

2.4.1 ConnectionPool

源码地址Android源码中内嵌的okhttp源码,该类用来治本TCP连接(RealConnection是对TCP连接的卷入,所以那边就一向叫TCP连接)的复用以减少网络延迟,上面就来看望是怎么来管理的:
此类中有三个万分首要的分子变量:
maxIdleConnections
:ConnectionPool中空闲TCP连接的最大数据,默许为5个。
keepAliveDurationNs:ConnectionPool中TCP连接最长的闲暇时长,默认为5分钟。
1> 首先看ConnectionPool中维系一而再和取得连接的进度:

/** 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()方法返回的是每一个TCP连接上最大的Stream数量,每一个Stream对应一对 请求-响应,那么该方法的返回值代表同一时刻同一个TCP连接上最多可以处理多少对 请求-响应,
    // 对于HTTP/1.x,最大的Stream数量是1,对于HTTP/2,最大的Stream数量是4,具体实现可以直接参考该方法。
    // address.equals(connection.getRoute().address相同代表URL字符串中的scheme、host、port是相同的,即只有scheme、host、port相同的情况下,才有可能复用同一个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()) { 
    // 当给连接池中放入第一个TCP连接,会在后台开启一个后台的清理线程,用于轮询连接池中的所有的TCP连接,关掉符合清理条件的TCP连接。
    executor.execute(cleanupRunnable);
  }
  connections.add(connection);
}

2> 接下来看一下cleanupRunnable:

private Runnable cleanupRunnable = new Runnable() {
@Override public void run() {
  while (true) {
    long waitNanos = cleanup(System.nanoTime());
    // cleanup方法的返回值是-1代表连接池中为空,此时后台清理线程结束
    if (waitNanos == -1) return;
    // cleanup方法返回值大于0代表需要暂停后台清理线程,暂停时长为返回值的大小,返回值为0时,就会立刻进入下一轮的轮询。
    if (waitNanos > 0) {
      long waitMillis = waitNanos / 1000000L;
      waitNanos -= (waitMillis * 1000000L);
      synchronized (ConnectionPool.this) {
        try {
          ConnectionPool.this.wait(waitMillis, (int) waitNanos);
        } catch (InterruptedException ignored) {
        }
      }
    }
  }
}
};

3> 接下来看一下轮询的进度,即cleanup方法的落到实处:

long cleanup(long now) {
  // 记录连接池中处于使用状态的TCP连接的数量
  int inUseConnectionCount = 0;
  // 记录连接池中处于空闲状态的TCP连接的数量
  int idleConnectionCount = 0;
  // 记录连接池中空闲时长最长的TCP连接
  RealConnection longestIdleConnection = null;
  // 记录连接池中所有TCP连接中最长的空闲时长
  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连接处于使用状态
      if (pruneAndGetAllocationCount(connection, now) > 0) {
        // inUseConnectionCount加1,然后继续轮询下一个TCP连接
        inUseConnectionCount++;
        continue;
      }

      // 可以运行到这里,说明该TCP连接处于空闲状态,此时idleConnectionCount加1
      idleConnectionCount++;

      long idleDurationNs = now - connection.idleAtNanos;
      // 下面条件成立,代表该TCP的空闲时间比其前面的连接的空闲时长都长
      if (idleDurationNs > longestIdleDurationNs) {
        // 记录连接池中所有TCP连接中最长的空闲时长
        longestIdleDurationNs = idleDurationNs;
        // 记录连接池中空闲时长最长的TCP连接
        longestIdleConnection = connection;
      }
    }

    // longestIdleDurationNs >= this.keepAliveDurationNs成立代表连接池中所有TCP连接中最长的空闲时长大于5分钟。
    // idleConnectionCount > this.maxIdleConnections成立接池中处于空闲状态的TCP连接的数量大于5
    if (longestIdleDurationNs >= this.keepAliveDurationNs
        || idleConnectionCount > this.maxIdleConnections) {
      // 将longestIdleConnection从连接池中移除
      connections.remove(longestIdleConnection);

    } else if (idleConnectionCount > 0) {
      // 此时连接池中所有TCP连接中最长的空闲时长没有达到5分钟时
      return keepAliveDurationNs - longestIdleDurationNs;

    } else if (inUseConnectionCount > 0) {
      // 所有的TCP连接都处于使用状态
      return keepAliveDurationNs;

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

  // 关闭掉从连接池中移除TCP连接
  Util.closeQuietly(longestIdleConnection.getSocket());

  // 立刻进入下一轮的轮询
  return 0;
}

自我的客户端援救了呢?

方方面面准备妥当之后,也是时候对结果开展表达了,除了刚才波及的 WireShark
之外,你还足以接纳上面的多少个工具来对 HTTP 2.0 举行测试

  • Chrome 上的一个插件,HTTP/2 and SPDY
    indicator
    会在你拜访 http2.0 的网页的时候,以小雷暴的款型举办指令

亚洲必赢官网 24

点击小闪电,会进来一个页面,列举了眼前浏览器访问的凡事
http2.0的乞请,所以,你可以把您想要测试的客户端接口在浏览器访问,然后在那个页面验证下是不是协理http2.0

亚洲必赢官网 25

  • charles:这几个大家应该都用过,4.0 以上的新本子对
    HTTP2.0做了帮助,为了便利,你也足以在 charles
    上拓展调剂,然则自己发现接近存在 http2.0的一对
    bug,近来还没搞明白哪些来头

  • 利用 nghttp2 来调节,那是一个 C 语言完结的
    HTTP2.0的库,具体应用办法可以参考:使用 nghttp2 调试 HTTP/2
    流量

  • 还要简单严酷,直接在 iOS 代码中打印,_CFURLResponse 中蕴藏了
    httpversion,获取方式就是依据 CFNetwork 相关的 API
    来做,那里一向丢出主要代码,完整代码可以参考
    getHTTPVersion

      #import "NSURLResponse+Help.h"
      #import <dlfcn.h>
      @implementation NSURLResponse (Help)
      typedef CFHTTPMessageRef (*MYURLResponseGetHTTPResponse)(CFURLRef response);
    
      - (NSString *)getHTTPVersion {
          NSURLResponse *response = self;
          NSString *version;
          NSString *funName = @"CFURLResponseGetHTTPResponse";
          MYURLResponseGetHTTPResponse originURLResponseGetHTTPResponse =
          dlsym(RTLD_DEFAULT, [funName UTF8String]);
          SEL theSelector = NSSelectorFromString(@"_CFURLResponse");
          if ([response respondsToSelector:theSelector] &&
              NULL != originURLResponseGetHTTPResponse) {
              CFTypeRef cfResponse = CFBridgingRetain([response performSelector:theSelector]);
              if (NULL != cfResponse) {
                  CFHTTPMessageRef message = originURLResponseGetHTTPResponse(cfResponse);
                  CFStringRef cfVersion = CFHTTPMessageCopyVersion(message);
                  if (NULL != cfVersion) {
                      version = (__bridge NSString *)cfVersion;
                      CFRelease(cfVersion);
                  }
                  CFRelease(cfResponse);
              }
          }
          if (nil == version || 0 == version.length) {
              version = @"获取失败";
          }
          return version;
      }
      @end      
    

HTTP1.1 不安全,可能存在被篡改、被窃听、被伪装等问题。当然,前阵子 Apple
推广 HTTPS 的时候,相信广大人早已接入 HTTPS

2.4.2 ConfigAwareConnectionPool

此类是单例的,用于提供一个共享的ConnectionPool,该类会监听网络安排改变事件,当网络布局暴发转移,ConnectionPool对象就会被作废,之后可以通过get方法创造一个新的ConnectionPool对象。

大礼包

  • Jerry
    Qu的HTTP2.0合辑
  • http2-协议协商进度
  • h2-13
    中文版
  • Hypertext Transfer Protocol Version 2
    (HTTP/2)
  • HPACK: Header Compression for
    HTTP/2
  • Wireshark抓包iOS入门教程
  • iOS HTTP/2 Server Push
    探索
  • HTTP/2 on
    iOS
  • HTTPS 与 HTTP2
    研讨分析&version=12020110&nettype=WIFI&fontScale=100&pass_ticket=v4f3j82l8ughtmSZjfn5%2FFRoI%2BM4ntCq8S9SgIaAiDpg6FDq6D9dXVa3Hs9kv2R4)
  • http2讲解
  • How to get HTTP protocol
    version from a given
    NSHTTPURLResponse?

HTTP 的头顶没有收缩,header
的大大小小也是传输的负担,带来更多的流量消耗和传导延迟。并且很多 header
是一致的,重复传输是不曾需要的。

3 源码分析

第一放个例子:

if (NETWORK_GET.equals(action)) {
    //发送GET请求
    url = new URL("https://www.jianshu.com/recommendations/notes?category_id=56&utm_medium=index-banner-s&utm_source=desktop");
    conn = (HttpURLConnection) url.openConnection();
    //HttpURLConnection默认就是用GET发送请求,所以下面的setRequestMethod可以省略
    conn.setRequestMethod("GET");
    // 表示是否可以读取响应体中的数据,默认为true。
    conn.setDoInput(true);
    //用setRequestProperty方法设置一个自定义的请求头字段
    conn.setRequestProperty("action", NETWORK_GET);
    //禁用网络缓存
    conn.setUseCaches(false);
    //在对各种参数配置完成后,通过调用connect方法建立TCP连接,但是并未真正获取数据
    //conn.connect()方法不必显式调用,当调用conn.getInputStream()方法时内部也会自动调用connect方法
    conn.connect();
    //调用getInputStream方法后,服务端才会收到完整的请求,并阻塞式地接收服务端返回的数据
    InputStream is = conn.getInputStream();
} else if (NETWORK_POST_KEY_VALUE.equals(action)) {
    //用POST发送键值对数据
    url = new URL("https://www.jianshu.com/recommendations/notes");
    conn = (HttpURLConnection) url.openConnection();
    //通过setRequestMethod将conn设置成POST方法
    conn.setRequestMethod("POST");
    //表示是否可以通过请求体发送数据给服务端,默认为false。
    conn.setDoOutput(true);
    //用setRequestProperty方法设置一个自定义的请求头:action
    conn.setRequestProperty("action", NETWORK_POST_KEY_VALUE);
    //获取conn的输出流
    OutputStream os = conn.getOutputStream();
    //获取两个键值对name=孙群和age=27的字节数组,将该字节数组作为请求体
    requestBody = new String("category_id=56&utm_medium=index-banner-s&utm_source=desktop").getBytes("UTF-8");
    //将请求体写入到conn的输出流中
    os.write(requestBody);
    //记得调用输出流的flush方法
    os.flush();
    //关闭输出流
    os.close();
    //当调用getInputStream方法时才真正将请求体数据上传至服务器
    InputStream is = conn.getInputStream();
}

上边是经过HttpURLConnection发起GET/POST请求的兑现代码,接着通过上面的时序图完全的看一下流程:

亚洲必赢官网 26

接下去就是按照上图一步步分析。

服务端不能主动推送资源到客户端

3.1 创建URL对象

第1步,通过URL字符串创设URL对象:

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的构造方法很简短,主要做了如下几件工作:
1> 解析出URL字符串中的协议,即上边代码中的protocol
2> 通过getURLStreamHandler方法获取处理protocol
协议对应的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是对HTTP协议的达成,所以上面只关注com.android.okhttp.HttpHandler和com.android.okhttp.HttpsHandler,HttpsHandler继承至HttpHandler,差别在于添加了对TLS的协理,即在建立TCP连接后会执行TLS握手;接下去就是下载Android源码中内嵌的okhttp源码,就足以找到HttpHandler和HttpsHandler的源码,不过发现了一个很想得到的场景,HttpHandler和HttpsHandler的包名是com.squareup.okhttp,但是地方getURLStreamHandler方法中却是com.android.okhttp,那是怎么回事呢?那时我就看见源码中有一个文本有点眼熟:

亚洲必赢官网 27

看样子此间我们应该明了为什么了吧。

再有一个题目,曾几何时Android中开端应用okhttp,我相比了一下Android
4.4(左)和Android 4.3的URL.java,如下图所示:

亚洲必赢官网 28

可以看到从Android 4.4从头使用okhttp处理HTTP协议。

随着看第3步,调用URLStreamHandler的parseURL方法来解析URL字符串,然后用分析后取得的结果初步化URL对象。为了更好的了解parseURL方法的法则,那就要清楚URL字符串的协会:

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

上面的三种格式就是对URL中的authority部分的逐步细分,目前Android API 26中URL.java中细分到了第二种格式。
具体每部分是什么意思,大家可以参考https://en.wikipedia.org/wiki/URL

对于上面例子中的URL字符串 https://www.jianshu.com/recommendations/notes?category_id=56&utm_medium=index-banner-s&utm_source=desktop:
scheme:https
authority:www.jianshu.com
host:www.jianshu.com
path:/recommendations/notes
query:category_id=56&utm_medium=index-banner-s&utm_source=desktop
其中userInfo、port和fragment是没有的。

解析URL字符串用是URLStreamHandler的parseURL方法,该方式就是根据地点的URL字符串的布局来分析出每一局地的值,然后用这个值来开始化URL对象,具体的底细大家可以团结查看parseURL方法源码。

HTTP1.1的格式是文本格式,基于文本做一些扩展、优化相对相比较困难,可是文本格式易于阅读和调试,但HTTPS之后,也改为二进制格式了,那几个优势也一无往返

3.2 创建HttpURLConnection实例

接下去看第5步,调用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的默认端口号为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);

    // 仅允许明文传输(针对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> 调用createHttpOkUrlFactory方法制造OkUrlFactory实例:
createHttpOkUrlFactory方法中首先创制OkHttpClient实例,并且为OkHttpClient实例设置了有的参数:
读写超时:0,代表超时时间为无穷大,即没有过期;可以透过调用HttpURLConnection的setRead提姆(Tim)eout方法设置该值。
连日超时:0,代表超时时间为无穷大,即没有过期;可以通过调用HttpURLConnection的setConnect提姆eout方法设置该值。
ConnectionSpecs:CLEARTEXT_ONLY,代表不必要TLS,即公开传输。
继而以OkHttpClient实例为参数成立OkUrlFactory实例,接着将OkUrlFactory实例的urlFilter字段设置为CleartextURLFilter(用于判断是不是可以与指定host的服务器进行明文通讯),最终回到OkUrlFactory实例。
2>
接着为OkHttpClient实例设置ConnectionPool并且重临OkUrlFactory实例,关于ConnectionPool越发密切的讲解可以参见2.4.1。
3> 接着调用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方法分析达成,接下去就要看看HttpsHandler,HttpsHandler重写了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握手阶段时,通过ALPN协商应用层协议时客户端ClientHello携带的应用层协议列表
private static final List<Protocol> HTTP_1_1_ONLY =
        Collections.singletonList(Protocol.HTTP_1_1);

private final ConfigAwareConnectionPool configAwareConnectionPool =
        ConfigAwareConnectionPool.getInstance();

// https的默认端口号为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改写了第7步中的createHttpOkUrlFactory方法:
第一调用父类HttpHandler的createHttpOkUrlFactory方法重回OkUrlFactory实例,接着将OkUrlFactory实例的urlFilter字段设置为null;接着为OkHttpClient实例设置有些参数:
ConnectionSpecs:TLS_CONNECTION_SPEC,代表必要协助TLS,即密文传输。
protocols:HTTP_1_1_ONLY,TLS握手阶段时,通过ALPN协商应用层协议时客户端ClientHello率领的应用层协议列表。
sslSocketFactory:HttpsURLConnection.getDefaultSSLSocketFactory()。

经过地点的剖析可见URL的openConnection方法并不曾成立TCP连接,只是成立了HttpURLConnectionImpl实例或者HttpsURLConnectionImpl实例。

HTTP2.0

3.3 调用HttpURLConnection的connect方法与服务端建立TCP连接

HttpsURLConnectionImpl使用了装饰者形式对HttpURLConnectionImpl举行了装修,HttpsURLConnectionImpl对connect方法没有其它的装裱,所以平昔看HttpURLConnectionImpl中connect方法,即第12步:

  @Override public final void connect() throws IOException {
    initHttpEngine();
    boolean success;
    do {
      // execute方法用于发送请求,如果请求成功执行,则返回true,如果请求可以重试,则返回false,
      // 如果请求永久失败,则抛出异常。
      success = execute(false);
    } while (!success);
  }

HttpURLConnectionImpl中的initHttpEngine方法是私有方法,所以不会被装饰,所以一直看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并且呼吁方法是GET的情形下,就会将呼吁方法强制替换成POST,那是怎么吗?首先让大家了解doInput和doOutput是用来干嘛的?
doInput:表示是或不是足以读取服务端重回的响应体中的数据,默许为true。
doOutput:表示是不是足以由此请求体发送数据给服务端,默认为false。
对于请求方法是GET的伸手是尚未请求体的,而请求方法是POST的请求是有请求体的(具体怎么请求方法有请求体,可以参考HttpMethod.permitsRequestBody方法),所以在doOutput为true并且呼吁方法是GET的场所下,就会将呼吁方法强制替换成POST。

鉴于HttpURLConnectionImpl中的newHttpEngine方法是个体方法,即不会被点缀,所以平素看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());
    // 根据请求的url、method、请求头(通过HttpURLConnection的setRequestProperty方法设置的)
    // 创建Request实例
    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)) { // 判断请求是否有请求体
      // HTTP/1.1以及之后的版本都是长连接的,对于请求有请求体的情况,就需要告诉服务端请求体何时结束,
      // 有两种方式来告诉服务端请求体已经结束:Content-Length、Transfer-Encoding,
      // 具体可以参考[HTTP 协议中的 Transfer-Encoding](https://imququ.com/post/transfer-encoding-header-in-http.html)
      if (fixedContentLength != -1) { 
        // 调用了HttpURLConnection的setFixedLengthStreamingMode来设置请求体的结束方式为Content-Length
        builder.header("Content-Length", Long.toString(fixedContentLength));
      } else if (chunkLength > 0) { 
        // 调用了HttpURLConnection的setChunkedStreamingMode来设置请求体的结束方式为Transfer-Encoding
        builder.header("Transfer-Encoding", "chunked");
      } else {
        // 没有设置请求体结束方式的情况
        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);
  }

接下去看一下HttpEngine的构造方法以及有关代码:

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时,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方法是个人方法,即不会被点缀,所以平素看HttpURLConnectionImpl中的execute方法,即第14步:

private boolean execute(boolean readResponse) throws IOException {
  boolean releaseConnection = true;
  // 由第7步可知,在scheme为http情况下,urlFilter为CleartextURLFilter类型的实例,
  // CleartextURLFilter用于判断是否可以与指定host的服务器进行明文通信。
  if (urlFilter != null) {
    urlFilter.checkURLPermitted(httpEngine.getRequest().url());
  }
  try {
    // 发起请求
    httpEngine.sendRequest();
    Connection connection = httpEngine.getConnection();
    if (connection != null) {
      route = connection.getRoute();
      handshake = connection.getHandshake();
    } else {
      route = null;
      handshake = null;
    }
    if (readResponse) {
      // 读取响应
      httpEngine.readResponse();
    }
    releaseConnection = false;

    return true;
  } 
  ......
}

接下去看HttpEngine的sendRequest方法,即第15步:

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

  // 为请求添加默认的请求头
  Request request = networkRequest(userRequest);

  // 从缓存中获取该请求对应的缓存的响应,当调用HttpURLConnection的setUseCaches方法
  // 将useCaches字段(默认值为true)设置为false时,responseCache为null
  InternalCache responseCache = Internal.instance.internalCache(client);
  Response cacheCandidate = responseCache != null
      ? responseCache.get(request)
      : null;

  long now = System.currentTimeMillis();
  // 给出请求和请求对应的缓存的响应,然后根据缓存策略得出符合缓存策略的请求和响应
  cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
  // 当得出的请求不为null时(即networkRequest不为null),代表该请求对应的缓存的响应不存在或者已经过期,
  // 此时需要从服务端获取,否则使用请求对应的缓存的响应
  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.
  }

  // 当networkRequest不等于null时,代表该请求对应的缓存响应不存在或者已经过期,需要从新从服务端获取
  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步中说过,在请求体存在并且没有设置请求体结束的方式的情况下,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) {
          // 请求体的长度知道的情况(即请求头中包含头字段Content-Length),当通过HttpURLConnection的setRequestProperty方法设置了Content-Length时,这种情况才会发生,
          // writeRequestHeaders最终将请求头写入到RealBufferedSink实例中,该RealBufferedSink实例是下面第21步中的提到的sink字段。
          httpStream.writeRequestHeaders(networkRequest);
          // 创建RetryableSink类型的实例requestBodyOut,RetryableSink中有一个Buffer类型的字段,
          // 用于缓存请求体,因此通过RetryableSink类型的requestBodyOut实例写入请求体,只不过是将其缓存到内存中
          requestBodyOut = new RetryableSink((int) contentLength);
        } else {
          // 请求体的长度未知的情况(即请求头中没有正确设置头字段Content-Length),此时就必须在整个请求体准备好之后才能写请求头。
          requestBodyOut = new RetryableSink();
        }
      } else {
        // 有请求体并且设置了请求体的结束方式为Content-Length和Transfer-Encoding其中之一(即请求头中包含头字段Content-Length和Transfer-Encoding其中之一),
        // writeRequestHeaders最终将请求头写入到RealBufferedSink实例中,该RealBufferedSink实例是下面第21步中的提到的sink字段。
        httpStream.writeRequestHeaders(networkRequest);
        // createRequestBody方法中会根据请求头中头字段Content-Length或者Transfer-Encoding创建
        // 不同类型的Sink实例,通过该Sink实例写入的请求体休息最终会被写入到RealBufferedSink实例中,该RealBufferedSink实例是下面第21步中的提到的sink字段。
        requestBodyOut = httpStream.createRequestBody(networkRequest, contentLength);
      }
    }

  } else { // 代表使用该请求对应的缓存的响应
    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();
    }

    // 当响应头包含头字段Content-Encoding并且值为gzip时,unzip中会利用GzipSource将响应体进行解压。
    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));
  }

  // 告诉服务端使用长连接
  if (request.header("Connection") == null) {
    result.header("Connection", "Keep-Alive");
  }

  // 告诉服务端客户端支持的压缩类型
  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类,该类已毕了2.3中的缓存策略,上边就来分析一下CacheStrategy的利用流程:

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

  if (cacheResponse != null) {
    // 当请求对应的缓存的响应不为空时,获取缓存的响应的一些信息,后面在判断缓存的响应是否新鲜时会用到
    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代表服务端发送缓存响应的日期和时间,比如 Date: Tue, 15 Nov 1994 08:12:31 GMT
        servedDate = HttpDate.parse(value);
        servedDateString = value;
      } else if ("Expires".equalsIgnoreCase(fieldName)) { 
        // Expires代表一个日期和时间,超过该时间则认为此回应已经过期,比如 Expires: Thu, 01 Dec 1994 16:00:00 GMT
        expires = HttpDate.parse(value);
      } else if ("Last-Modified".equalsIgnoreCase(fieldName)) {
        // Last-Modified代表缓存响应的最后修改日期,比如 Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT
        lastModified = HttpDate.parse(value);
        lastModifiedString = value;
      } else if ("ETag".equalsIgnoreCase(fieldName)) {
        // ETag代表对于缓存响应的某个特定版本的一个标识符,比如 ETag: "737060cd8c284d8af7ad3082f209582d"
        etag = value;
      } else if ("Age".equalsIgnoreCase(fieldName)) {
        // Age代表缓存响应在缓存中存在的时间,以秒为单位
        ageSeconds = HeaderParser.parseSeconds(value, -1);
      } else if (OkHeaders.SENT_MILLIS.equalsIgnoreCase(fieldName)) {
        // SENT_MILLIS字段是Okhttp中添加的字段,用于记录请求头被发送时的时间点
        sentRequestMillis = Long.parseLong(value);
      } else if (OkHeaders.RECEIVED_MILLIS.equalsIgnoreCase(fieldName)) {
        // RECEIVED_MILLIS字段是Okhttp中添加的字段,用于记录客户端接收到响应的时间点
        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) {
    // 请求对应的缓存响应为null,对应于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);
  }

  // 可以执行到这里,代表进入到2.3中第1步肯定情况下的程
  // isCacheable用于判断请求对应的响应是否允许被缓存,下面条件语句成立代表不允许缓存的情况,
  // 对应于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());
  }

  // 可以执行到这里,代表进入到2.3中第2步肯定情况下的流程
  // 下面条件语句如果成立的话,代表缓存的响应还是新鲜的,说明请求对应的缓存的响应没有过期,
  // 对应于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();

  // 如果可以执行到这里,说明请求对应的缓存响应已经过期,代表进入到2.3中第3步否定情况下的流程,
  // 这时就需要与服务端进行验证是否还可用,验证方式根据请求对应的缓存的响应决定,有如下三种
  if (etag != null) { 
    // 如果请求对应的缓存的响应包含字段ETag,则使用请求头字段If-None-Match验证是否还可用
    conditionalRequestBuilder.header("If-None-Match", etag);
  } else if (lastModified != null) {
    // 如果请求对应的缓存的响应包含字段Last-Modified,则使用请求头字段If-Modified-Since验证是否还可用
    conditionalRequestBuilder.header("If-Modified-Since", lastModifiedString);
  } else if (servedDate != null) {
    // 如果请求对应的缓存的响应包含字段Date,则使用请求头字段If-Modified-Since验证是否还可用
    conditionalRequestBuilder.header("If-Modified-Since", servedDateString);
  }

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

对于请求头和响应头中字段的含义,能够参见HTTP头字段。

接下去看一下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);
}

接下去看StreamAllocation的newStream方法,即第17步:

public HttpStream newStream(int connectTimeout, int readTimeout, int writeTimeout,
    boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)
    throws RouteException, IOException {
  try {
    // 寻找一个健康的连接
    RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
        writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);

    // 在寻找到的连接上设置HttpStream,HttpStream是用来发送请求和接收响应的
    HttpStream resultStream;
    if (resultConnection.framedConnection != null) { // 对应于HTTP/2协议
      resultStream = new Http2xStream(this, resultConnection.framedConnection);
    } else { // 对应于HTTP/1.x协议
      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);
  }
}

接下去看StreamAllocation的findHealthyConnection方法,即第18步:

/**
 * 循环寻找一个健康的连接,直到找到为止
 */
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是一个全新的连接(即连接上面Steam的个数为0),则跳过下面的健康检查直接返回。
    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();
  }
}

接下去看StreamAllocation的findConnection方法,即第19步:

/**
 * 如果没有找到,就创建一个新的连接,首先将新创建的连接实例放到连接池,然后通过新创建的连接实例与
 * 服务端建立Socket连接,最后将新创建的连接返回。
 */
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;
    }

    // 从连接池中寻找,如果之前已经通过相同的Address(只要URL字符串中的scheme、host、port相同,
    // Address一般都是相同的,具体可以看Address的equals方法)创建过连接并且该连接上Stream没有达到上限(对于HTTP/1.x,Stream的上限为1,对于HTTP/2,Stream的上限为4),
    // 那就找到了可复用的连接
    RealConnection pooledConnection = Internal.instance.get(connectionPool, address, this);
    if (pooledConnection != null) {
      // 在连接池中找到了可复用的连接,直接返回
      this.connection = pooledConnection;
      return pooledConnection;
    }

    if (routeSelector == null) {
      // RouteSelector的构造方法中会调用resetNextProxy方法,该方法中会获取系统默认的ProxySelector,
      // 然后调用ProxySelector的select方法(以address的url为参数)获取http或者https协议对应的
      // 代理列表,默认情况下代理列表是空的,可以通过http://www.blogs8.cn/posts/EU5L296中提供的方式设置代理。
      // 接着将代理列表保存到proxies字段中,最后在proxies的末尾添加一个Proxy.NO_PROXY代理,
      // Proxy.NO_PROXY代理的type是Type.DIRECT类型,即直接连接,不使用代理,这也是默认的方式。
      routeSelector = new RouteSelector(address, routeDatabase());
    }
  }

  // RouteSelector的next方法中首先获取proxies中的第一个代理(在没有设置代理的情况下,
  // 该代理为Proxy.NO_PROXY),然后用该代理创建Route实例
  Route route = routeSelector.next();
  RealConnection newConnection = new RealConnection(route);
  acquire(newConnection);

  // 将新创建的连接放到连接池中以备后用
  synchronized (connectionPool) {
    Internal.instance.put(connectionPool, newConnection);
    this.connection = newConnection;
    if (canceled) throw new IOException("Canceled");
  }

  // 通过新创建的连接与服务端建立Socket连接
  newConnection.connect(connectTimeout, readTimeout, writeTimeout, address.getConnectionSpecs(),
      connectionRetryEnabled);
  routeDatabase().connected(newConnection.getRoute());

  return newConnection;
}

接下去就来探视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时,成功建立Socket连接,protocol就会被设置为Protocol.HTTP_1_1
  // 2> scheme为https时,成功建立Socket连接并且TLS握手成功,protocol就会被设置为Protocol.HTTP_1_1
  // 那么在HttpURLConnection中,无论scheme为https还是http,协议版本都是HTTP/1.1。
  // 具体原因第21步会详细说明
  while (protocol == null) {
    try {
      // 根据上面findConnection的注释可知,默认情况下不设置代理,即proxy.type() == Proxy.Type.DIRECT是成立的,
      // 因此通过address.getSocketFactory().createSocket()创建一个Socket实例
      rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
          ? address.getSocketFactory().createSocket()
          : new Socket(proxy);
      // 利用rawSocket实例根据指定host和port发起与服务端的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;
      }
    }
  }
}

接下去就来看望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实例根据指定host和port的服务端建立TCP连接
    Platform.get().connectSocket(rawSocket, route.getSocketAddress(), connectTimeout);
  } catch (ConnectException e) {
    throw new ConnectException("Failed to connect to " + route.getSocketAddress());
  }
  // 为rawSocket的InputStream建立RealBufferedSource实例,为rawSocket的OutputStream建立RealBufferedSink实例
  // 这两个实例就是用来读取响应和发送请求的,具体原理可以参考https://www.jianshu.com/p/0bc80063afb3
  source = Okio.buffer(Okio.source(rawSocket));
  sink = Okio.buffer(Okio.sink(rawSocket));

  // 只有scheme为https的情况下,下面条件才会成立
  if (route.getAddress().getSslSocketFactory() != null) {
    // 进行TLS握手
    connectTls(readTimeout, writeTimeout, connectionSpecSelector);
  } else { // 对于scheme为http情况下,则将协议版本设置为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.

    // 当协议版本为HTTP/2时,framedConnection才会被初始化,framedConnection针对HTTP/2中的Frame结构。
    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基础上创建SSLSocket实例,SSLSocket用于发送请求时的加密和解析响应时的解密
    sslSocket = (SSLSocket) sslSocketFactory.createSocket(
        rawSocket, address.getUriHost(), address.getUriPort(), true /* autoClose */);

    // 为sslSocket配置ciphers、 TLS版本、和extensions,为TLS握手做准备
    ConnectionSpec connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket);
    // 能运行到这里,scheme一定为https,那么connectionSpec为
    // HttpsHandler.TLS_CONNECTION_SPEC(具体原因可以参考第7步),那么下面一定是成立的
    if (connectionSpec.supportsTlsExtensions()) {
      // 下面方法的第三个参数就是TLS握手时通过ALPN协商使用哪个应用层协议时建议的协议列表,
      // 由于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握手相关的代码,有兴趣的同学可以自己研究下,执行到这里说明握手成功,
    // 这是就会保存ALPN协商后的应用层协议名称
    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);
    }
  }
}

在 HTTP2.0中,下边的题目大概都不存在了。HTTP2.0 的规划来源于 谷歌 的
SPDY 协议,倘使对 SPDY 协议不了然的话,也得以先对 SPDY
举办问询,但是那不影响连续读书本文

3.4 在有请求体的情景下往请求体中写入内容

是因为HttpsURLConnectionImpl对HttpURLConnectionImpl中getOutputStream方法没有其他装饰,所以一贯看HttpURLConnectionImpl中的getOutputStream方法,即第25步:

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

  // 通过OutputStream写入数据时,首先会写入到下面sink实例中,该sink实例是通过第15步中的requestBodyOut创建的。
  // 通过第15步中可知,当OutputStream的类型为RetryableSink时,通过下面的sink实例写入的数据会保存到内存中;
  // 否则对于HTTP/1.x,通过sink实例会将数据写入到第21步中提到的sink字段中,对于HTTP/2,通过sink实例写入的数据在达到一Frame(默认值为16KB)时,就会将该Frame写入到第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();
}

接下去看一下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;
}

HTTP 2.0
使用新的二进制格式:基本的商事单位是帧,每个帧都有不一致的种类和用途,规范中定义了10种分歧的帧。例如,报头(HEADERS)和数码(DATA)帧组成了骨干的HTTP
请求和响应;其他帧例如 设置(SETTINGS),窗口更新(WINDOW_UPDATE),
和推送承诺(PUSH_PROMISE)是用来落实HTTP/2的别样职能。那一个呼吁和响应的帧数据通过流来进行数据调换。新的二进制格式是流量控制、优先级、server
push等效果的基础。

3.5 从服务端获取响应

是因为HttpsURLConnectionImpl对HttpURLConnectionImpl中getInputStream方法没有其余装饰,所以一贯看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方法是个体方法,即不会被点缀,所以间接看HttpURLConnectionImpl中的getResponse方法,即第30步:

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

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

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

    ......
  }
}

鉴于HttpURLConnectionImpl中的execute方法是个体方法,即不会被装饰,所以一直看HttpURLConnectionImpl中的execute方法,即第31步:

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

接下去看下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,因此会运行到这里
    // 将bufferedRequestBody中剩下的数据(即不够一个Segment的数据)全部移动到requestBodyOut中。
    if (bufferedRequestBody != null && bufferedRequestBody.buffer().size() > 0) {
      bufferedRequestBody.emit();
    }

    if (sentRequestMillis == -1) { 
      // sentRequestMillis等于-1代表请求头还没有写入到第21步中的提到的sink字段,没有写入的原因是请求体的长度是未知的。
      if (OkHeaders.contentLength(networkRequest) == -1
          && requestBodyOut instanceof RetryableSink) {
        long contentLength = ((RetryableSink) requestBodyOut).contentLength();
        networkRequest = networkRequest.newBuilder()
            // 添加头字段Content-Length,用于告诉服务端请求体的长度
            .header("Content-Length", Long.toString(contentLength))
            .build();
      }
      // writeRequestHeaders最终将请求头写入到RealBufferedSink实例中,该RealBufferedSink实例是下面第21步中的提到的sink字段,对应第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步可知,当requestBodyOut的类型是RetryableSink时,写入到requestBodyOut中的请求体数据是保持在内存中,下面的方法就是将内存中的请求体数据写入到第21步中的提到的sink字段中,对应第34步。
        httpStream.writeRequestBody((RetryableSink) requestBodyOut);
      }
    }

    // 从服务端读取响应
    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)) {
      // 执行到这里,说明过期的缓存的响应仍然是可用的,这时直接使用缓存的响应,对应于2.3中第5步肯定的情况
      userResponse = cacheResponse.newBuilder()
          .request(userRequest)
          .priorResponse(stripBody(priorResponse))
          // combine方法用于更新缓存的响应的新鲜度,比如修改Last-Modified字段,对应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());
    }
  }

  // 运行到这里,说明缓存响应已经不新鲜了,这时使用networkResponse,对应于2.3中第5步否定的情况
  userResponse = networkResponse.newBuilder()
      .request(userRequest)
      .priorResponse(stripBody(priorResponse))
      .cacheResponse(stripBody(cacheResponse))
      .networkResponse(stripBody(networkResponse))
      .build();

  if (hasBody(userResponse)) {
    // maybeCache是用于对网络响应的缓存处理,对应于2.3中的第8步,可以看出只有有响应体的响应才会被缓存
    maybeCache();
    userResponse = unzip(cacheWritingResponse(storeRequest, userResponse));
  }
}

/**
 * 返回 true 代表应该使用缓存响应,否则使用服务端返回的响应
 * 下面这个方法就是用来判断过期的缓存的响应是否还可用
 */
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;
}

接下去看下HttpEngine的readNetworkResponse方法,即第35步:

private Response readNetworkResponse() throws IOException {
  // 请求的所有数据都被写入到了第21步中的提到的sink字段中,
  // 下面的finishRequest方法是将sink字段中剩余的数据(即不够一个Segment的数据)写入到Socket的OutputStream中。
  httpStream.finishRequest();

  // 通过httpStream读取状态行和响应头,并且将OkHeaders.SENT_MILLIS、OkHeaders.RECEIVED_MILLIS记录在响应头中。
  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) {
    // 创建RealResponseBody类型的请求体实例,通过该实例的byteStream读取的数据最终来自于第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)) {
    // 走到这里说明网路缓存不允许缓存,对应2.3中第8步否定的情况
    if (HttpMethod.invalidatesCache(networkRequest.method())) {
      try {
        // 移除过期的缓存响应
        responseCache.remove(networkRequest);
      } catch (IOException ignored) {
        // The cache cannot be written.
      }
    }
    return;
  }

  // 运行的到这里说明该网络响应可以缓存,对应于2.3中的第8步肯定的情况
  storeRequest = responseCache.put(stripBody(userResponse));
}

到此地,HttpURLConnection的源码分析已毕。

流(Stream):一个Stream是富含一条或多条音讯、ID和事先级的双向通道

4 总结

率先通过下图概括一下HttpURLConnection的操作流程:
1>
首先通过Socket接口与服务端建立TCP连接,连接的最大空闲时间为5分钟。
持有建立的TCP连接都会放到连接池中举办集中管理,具体细节请参见2.4.1。
2> 接着就是确定使用的应用层协议
对此scheme为http的情况,直接选用HTTP/1.1磋商。
对于scheme为https的图景,通过ALPN和服务端协商使用的应用层协议,协商的底细请参见2.2,协商的结果一定是运用HTTP/1.1。
3> 接着就是在TCP连接上发出请求和经受响应,如下图:

亚洲必赢官网 29

上图依据HTTP/1.1制图的,OkHttp方今不协助Pipelining的风味,由此HttpURLConnection中的达成与上图的左侧相同,一个TCP连接在同样时刻只可以服务于1对请求-响应,要是在一个伸手的响应没有回来的情形下发起另一个伸手,则会新建一个TCP连接。
即便HttpURLConnection默许使用的是HTTP/1.1,可是依旧通过下图说爱他美(Aptamil)下对此HTTP/2在TCP连接上爆发请求和经受响应的进度:

亚洲必赢官网 30

一个TCP连接在平等时刻最多可以服务于4对
请求-响应(具体原因请参考2.4.1),上图中的4行代表4个彼此的Stream(用StreamId举行标识),每一个四方代表一个Frame(请求和响应都会被剪切成多少个Frame,具体可以参考2.1),Frame中会带领StreamId,当客户端同时接受几个响应时方可因而StreamId将响应进行区分不一致的响应。

下面3步概括了HttpURLConnection的操作流程,上面就来计算一下伸手在写入到Socket/SSLSocket之前都做了什么处理,首先通过下图看一下HTTP请求和响应的内部结构:

亚洲必赢官网 31

HTTP请求和响应的内部结构

上图中呼吁的构造是被写入到Socket/SSLSocket中的最终格式,那么请求的尾声格式是怎么获得的,那就通过下图明白一下实际的流程:

亚洲必赢官网 32

上图中TLS行之上对请求的处理都是HttpURLConnection所要完结的,下边列举一下方面用到的拍卖请求的操作:
对于HTTP/1.1(上图左边),对请求没有做其余的操作,直接根据HTTP请求和响应的内部结构图中的结构将呼吁写入Socket/SSLSocket中;
对此HTTP/2,是尚未请求行的,请求行被拆分成键值对停放了请求头中,首先会对请求头举行尾部压缩(在Hpack.java中落实)得到请求头块,那么请求行内容也会被减去,有趣味的同校可以参照:
HPACK: Header Compression for
HTTP/2
— 官方专门为尾部压缩给出的正儿八经
HTTP/2
尾部压缩技术介绍
紧接着将请求头块举办分割得到几个请求头块片段,每个请求头块片段对应一个Frame,具体细节可以请参见2.1,具体落到实处在Http2.java中。

音讯(Message):信息由帧组成

帧(Frame):帧有分歧的花色,并且是混合的。他们通过stream
id被重复组建进信息中

亚洲必赢官网 33

多路复用:也就是连接共享,刚才说到 HTTP1.1的 head of line
blocking,那么在多路复用的意况下,blocking 已经不设有了。每个连接中
可以包涵两个流,而各类流中交错包涵着来自两端的帧。也就是说同一个连接中是来源于分化流的数额包混合在一道,如下图所示,每一块代表帧,而同等颜色块来自同一个流,每个流都有谈得来的
ID,在接收端会依照 ID 举行重装组合,就是通过如此一种格局来贯彻多路复用。

亚洲必赢官网 34

单纯连接:刚才也说到 1.1 在呼吁多的时候,会敞开6-8个三番五次,而 HTTP2
只会开启一个连接,那样就收缩握手带来的推迟。

头顶压缩:HTTP2.0 通过 HPACK
格式来压缩尾部,使用了哈夫曼编码压缩、索引表来对底部大小做优化。索引表是把字符串和数字之间做一个突出,比如method:
GET对应索引表中的2,那么只要从前发送过这么些值是,就会缓存起来,之后选拔时意识前面发送过该Header字段,并且值相同,就会沿用之前的目录来代替这一个Header值。具体实验数据足以参见那里:HTTP/2
底部压缩技术介绍

亚洲必赢官网 35

Server
Push:就是服务端可以主动推送一些东西给客户端,也被称之为缓存推送。推送的资源得以备客户端日后之需,须求的时候一向拿出来用,提高了速率。具体的试行可以参见那里:iOS
HTTP/2 Server Push 探索

亚洲必赢官网 36

除却下面讲到的性状,HTTP2.0
还有流量控制、流优先级和凭借等职能。更加多细节可以参见:Hypertext
Transfer Protocol Version 2
(HTTP/2)

iOS 客户端接入HTTP 2.0

iOS 怎么着衔接 HTTP 2.0吧?其实很粗略:

保障服务端援助 HTTP2.0,并且注意下 NPN 或 ALPN

客户端系统版本 iOS 9 +

使用 NSURLSession 代替 NSURLConnection

客户端是应用 h2c 如故 h2,它们可以说是 HTTP2.0的多个版本,h2 是选拔 TLS
的HTTP2.0磋商,h2c是运作在明文 TCP 合计上的
HTTP2.0商谈。浏览器近日只接济h2,也就是说必须依照HTTPS陈设,可是客户端可以不配备HTTPS,因为我司早已安插HTTPS,所以我那边的举行都是根据h2的

HTTP 2.0的商谈机制

地点说了一堆名次,什么NPN、ALPN呀,还有h2、h2c之类的,有点懵逼。NPN(Next
Protocol Negotiation)是一个 TLS 增加,由 谷歌(Google) 在开发 SPDY
磋商时提出。随着 SPDY 被 HTTP/2 取代,NPN 也被修订为 ALPN(Application
Layer Protocol
Negotiation,应用层协议协商)。二者目的一致,但贯彻细节分化,互相不般配。以下是它们主要分化:

NPN 是服务端发送所支持的 HTTP 协议列表,由客户端接纳;而 ALPN
是客户端发送所援助的 HTTP 协议列表,由服务端拔取;

NPN 的说道结果是在 Change Cipher Spec 之后加密发送给服务端;而 ALPN
的协商结果是经过 Server Hello 明文发给客户端

并且,近年来无数地点早先甘休对NPN的接济,仅支持ALPN,所以集团采纳以来,最佳是直接行使 ALPN。

下边就平素来探望 ALPN 的情商进程是何等的,ALPN 作为 TLS
的一个恢宏,其进度可以透过 WireShark 查看 TLS握手进程来查看

亚洲必赢官网 37

上面通过 WireShark 来拓展调试,接入真机,然后终端输入

rvictl -s 设备 UDID来创制一个映射到 华为 的虚拟网卡,UUID 可以在
iTunes 中得到到,运行命令后会看到成功开创 rvi0 虚拟网卡的,双击 rvi0
开头调试。

亚洲必赢官网 38

跻身之后,在小弟大上访问页面会有接连不断的哀告呈现在 WireShark
的界面上,数据太多而不便宜大家本着调试,你可以过滤下域名,只关怀你想测试的
ip 地址,比如: ip.addr==111.89.211.191 ,当然你的 ip 要协助HTTP2.0才会有预料的成效啊

亚洲必赢官网 39

下边,就开端通过查阅 TLS 握手的进程分析HTTP2.0 的说道进度,刚才也说道
ALPN 协商结果是在 Client hello 和 Server hello
中呈现的,那就先来看一下Client hello

亚洲必赢官网 40

可以看出客户端在 Client hello 中列出了上下一心援助的各样应用层协议,比如
spdy3、h2。那么随着看 Server hello 是怎么样回复的

亚洲必赢官网 41

服务端会根据 client hello
中的协议列表,发过去要好帮衬的网络协议,如若服务端接济h2,则一直回到h2,协商成功,尽管不帮助h2,则赶回一个任何襄助的商事,比如HTTP1.1、spdy3

这一个是h2的商议进度,对于刚刚事关的 h2c 的协议进度,与此不一致,h2c
利用的是HTTP Upgrade 机制,客户端会发送一个 http
1.1的呼吁到服务端,那一个请求中蕴涵了 http2的升级字段,例如:

GET /default.htmHTTP/1.1Host: server.example.comConnection: Upgrade,
HTTP2-Settings  Upgrade: h2c  HTTP2-Settings:

服务端收到这么些请求后,若是协理 Upgrade 中 列举的商议,那里是
h2c,就会回去帮助的响应:

HTTP/1.1101Switching Protocols  Connection:Upgrade  Upgrade:h2c  [
HTTP/2connection …

自然,不援助的话,服务器会回去一个不含有 Upgrade 的报头字段的响应。

自己的客户端扶助了呢?

整套准备妥当之后,也是时候对结果开展表明了,除了刚才波及的 WireShark
之外,你还足以采用上边的多少个工具来对 HTTP 2.0 进行测试

Chrome 上的一个插件,HTTP/2 and SPDY
indicator会在你拜访 http2.0
的网页的时候,以小雷暴的款式举行指令

亚洲必赢官网 42

点击小雷暴,会进来一个页面,列举了当前浏览器访问的全套
http2.0的伸手,所以,你可以把您想要测试的客户端接口在浏览器访问,然后在那个页面验证下是或不是协助http2.0

亚洲必赢官网 43

charles:这些大家应该都用过,4.0 以上的新本子对
HTTP2.0做了支撑,为了有利于,你也可以在 charles
上展开调剂,可是自己发现类似存在 http2.0的一部分 bug,近期还没搞领会怎样原因

应用 nghttp2 来调节,那是一个 C 语言完毕的
HTTP2.0的库,具体使用办法可以参照:使用 nghttp2 调试 HTTP/2
流量

再就是不难无情,直接在 iOS 代码中打印,_CFURLResponse 中带有了
httpversion,获取情势就是基于 CFNetwork 相关的 API
来做,那里一向丢出重大代码,完整代码可以参照getHTTPVersion

#import”NSURLResponse+Help.h”#import@implementationNSURLResponse(Help)typedefCFHTTPMessageRef(*MYURLResponseGetHTTPResponse)(CFURLRefresponse);
 – (NSString*)getHTTPVersion {NSURLResponse*response
=self;NSString*version;NSString*funName
=@”CFURLResponseGetHTTPResponse”;      MYURLResponseGetHTTPResponse
originURLResponseGetHTTPResponse =      dlsym(RTLD_DEFAULT, [funName
UTF8String]);      SEL theSelector
=NSSelectorFromString(@”_CFURLResponse”);if([response
respondsToSelector:theSelector] &&NULL!=
originURLResponseGetHTTPResponse) {CFTypeRefcfResponse
=CFBridgingRetain([response performSelector:theSelector]);if(NULL!=
cfResponse) {CFHTTPMessageRefmessage =
originURLResponseGetHTTPResponse(cfResponse);CFStringRefcfVersion
=CFHTTPMessageCopyVersion(message);if(NULL!= cfVersion) {              
   version = (__bridgeNSString*)cfVersion;CFRelease(cfVersion);      
       }CFRelease(cfResponse);          }      }if(nil== version ||0==
version.length) {          version =@”获取失利”;      }returnversion;
 }@end

迎接我们与自身交换,分享新鲜的技艺~

网站地图xml地图