HTTP版本发展
HTTP/0.9——单行协议
请求由单行指令构成,只支持GET方法,服务器只能回应HTML格式的字符串,不能回应别的格式,当服务器发送完毕,就关闭TCP连接。
HTTP/1.0——构建可扩展性
- 请求方式新增了POST,DELETE,PUT,HEADER等方式
- 增添了请求头和响应头的概念,在通信中指定了 HTTP 协议版本号,以及其他的一些元信息 (比如: 状态码、权限、缓存、内容编码)
- 扩充了传输内容格式,图片、音视频资源、二进制等都可以进行传输
不足:
- 无法复用连接
每次发送请求,都需要进行一次tcp连接(即3次握手4次挥手),使得网络的利用率非常低 - 队头阻塞
HTTP 1.0 规定在前一个请求响应到达之后下一个请求才能发送,如果前一个阻塞,后面的请求也给阻塞的
HTTP/1.1
- 引入了持久连接(persistent connection),连接可以复用,节省了多次打开 TCP 连接加载网页文档资源的时间。
- 引入了管道机制(pipelining),即在同一个TCP连接里面,客户端可以同时发送多个请求。这样就进一步改进了HTTP协议的效率。举例来说,客户端需要请求两个资源。以前的做法是,在同一个TCP连接里面,先发送A请求,然后等待服务器做出回应,收到后再发出B请求。管道机制则是允许浏览器同时发出A请求和B请求,但是服务器还是按照顺序,先回应A请求,完成后再回应B请求。新增了请求方式 PUT、PATCH、OPTIONS、DELETE 等。
- 支持响应分块。
- 引入额外的缓存控制机制。
- 新增了请求方式 PUT、PATCH、OPTIONS、DELETE 等。
- 支持断点传输,在上传/下载资源时,如果资源过大,将其分割为多个部分,分别上传/下载,如果遇到网络故障,可以从已经上传/下载好的地方继续请求,不用从头开始,提高效率
- 凭借
Host
标头,能够使不同域名配置在同一个 IP 地址的服务器上。
不足:
虽然1.1版允许复用TCP连接,但是同一个TCP连接里面,所有的数据通信是按次序进行的。服务器只有处理完一个回应,才会进行下一个回应。要是前面的回应特别慢,后面就会有许多请求排队等着。这称为”队头堵塞”(Head-of-line blocking)。
HTTP/2——为了更优异的表现
- 二进制分帧
- 多路复用: 在共享TCP链接的基础上同时发送请求和响应
- 头部压缩
- 服务器推送:服务器可以额外的向客户端推送资源,而无需客户端明确的请求
HTTP/3——基于 QUIC 的 HTTP
- 基于google的QUIC协议,而quic协议是使用udp实现的,QUIC 旨在为 HTTP 连接设计更低的延迟。
- 减少了tcp三次握手时间,以及tls握手时间;
- 解决了http 2.0中前一个stream丢包导致后一个stream被阻塞的问题;
- 优化了重传策略,重传包和原包的编号不同,降低后续重传计算的消耗;
- 连接迁移,不再用tcp四元组确定一个连接,而是用一个64位随机数来确定这个连接;
- 更合适的流量控制。
OkHttp
OkHttp 是一个默认高效的 HTTP 客户端:
- HTTP/2 支持允许对同一主机的所有请求共享套接字。
- 连接池可减少请求延迟(如果 HTTP/2 不可用)。
- 透明 GZIP 缩小了下载大小。
- 响应缓存完全避免了网络重复请求。
一次请求:
1 | less复制代码OkHttpClient client = new OkHttpClient(); |
请求整体流程
client.newCall(request).execute()
开始执行请求,这里的execute()
点进去是抽象方法:
1 | kotlin复制代码actual interface Call : Cloneable { |
向前查看client.newCall(request)
返回的Call
的实现:
1 | kotlin复制代码open class OkHttpClient internal constructor( |
返回的是RealCall
,查看enqueue()
的实现:
1 | kotlin复制代码override fun enqueue(responseCallback: Callback) { |
这里涉及两个类:Dispatcher
、AsyncCall
。Dispatcher
是一个调度器,里面有一个线程池ExecutorService
实现多线程的调度,maxRequests
是并发执行的最大请求数,maxRequestsPerHost
是每个主机并发执行的最大请求数。
1 | kotlin复制代码class Dispatcher() { |
enqueue
里调用promoteAndExecute()
,这个方法首先是选取符合条件的请求,未超负载且未执行的请求,然后执行:
1 | kotlin复制代码synchronized(this) { |
最后进入asyncCall.executeOn(executorService)
,调用 executorService.execute(this)
,execute()
的参数是Runable
执行的是Runable
的run
方法,继续查看当前类的run
方法:
1 | kotlin复制代码fun executeOn(executorService: ExecutorService) { |
1 | kotlin复制代码override fun run() { |
这里调用getResponseWithInterceptorChain
,然后拿到结果回调,流程结束。
总结:
RealCall
的enqueue
->Dispatcher
的enqueue
->promoteAndExecute
->AsyncCall
的executeOn
->run
->RealCall
的getResponseWithInterceptorChain
。
getResponseWithInterceptorChain 解析
上面了解到,最后的流程getResponseWithInterceptorChain
进入到这个函数,这个函数也是最核心的部分。
1 | kotlin复制代码internal fun getResponseWithInterceptorChain(): Response { |
这里有三个关键部分,第一部分是把拦截器放到列表里,然后生成一个拦截器的责任链,然后就开始链式执行。
这里就不得不说下责任链模式,程序员老王需要度蜜月请假,写假条给组长,组长把他的工作安排给别人,然后批假后给主管审批,主管根据项目进度决定批假后给老板审批,老板审批后还给主管,主管再还给组长,组长再交给组长:
每个人都有三部分工作:前置,中置和后置。组长的前置是去了解工作安排,中置是自己审批然后交给主管,等审批一圈主管传递回来后,后置是组长根据上级审批的结果在安排叮嘱老王安心渡假并注意门户。
接下来回到代码中,首先从第一个内置的拦截器开始,也就是索引为 0 那个,每次调用proceed
,索引加一,也就是执行下一个拦截器:
1 | ini复制代码val chain = RealInterceptorChain( |
1 | kotlin复制代码override fun proceed(request: Request): Response { |
RetryAndFollowUpInterceptor
重试和重定向拦截器
索引为零的第一个拦截器是RetryAndFollowUpInterceptor
,这里面的前置是为接下来的请求做准备,找到对应请求地址等,找到一个可以承载请求的连接,中置就是交给下一个拦截器,然后等返回结果,失败则选择重试或者重定向,成功返回结果。
1 | kotlin复制代码while (true) { |
BridgeInterceptor 桥接拦截器
BridgeInterceptor
主要是补全请求的请求头和元数据,并添加了gzip
压缩。
1 | java复制代码override fun intercept(chain: Interceptor.Chain): Response { |
CacheInterceptor 缓存拦截器
根据缓存策略,如果命中缓存,直接返回,否则交给下一个拦截器执行,拿到结果后,如果需要缓存就缓存数据。
1 | scss复制代码override fun intercept(chain: Interceptor.Chain): Response { |
ConnectInterceptor
这个拦截器没有后置,前置完成后直接交给前面的拦截器。
这里关键在于realChain.call.initExchange(realChain)
,生成一个Exchange
:编码解码,是否需要加密,写入数据流,找到可用连接,建立连接。
1 | kotlin复制代码object ConnectInterceptor : Interceptor { |
CallServerInterceptor
这个拦截器是和服务器交互。主要是 IO 操作。
本文转载自: 掘金