OkHttp 手把手教你网络编程

作者: 木木玩Android | 来源:发表于2020-08-20 17:52 被阅读0次

    1、简介

    官网 有相关介绍,但是从一个开发者角度上来说,这些远远不够,应该从专业的角度知道它是啥?

    一个java的网络请求库;建立在tcp连接的基础上,使用Socket接口组进行请求;实现了两种协议的网络请求http,websocket;可采用TLS协议进行加密;也可实现代理、证书校验、basic auth验证等

    因此基本设计了网络传输层、应用层的常见的方方面面;涉及知识方向;

    • http协议
    • websock协议
    • TLS 协议
    • tcp 协议
    • socket
    • 证书
    • 基本认证、第三方认证
    • 加密、编码

    这些基础知识,就是授人以渔的渔;这些大部分都是前辈大能者规定的东西,我们现在只有慢慢理解;其实okhttp源码阅读的最大收获在于,让你如何完成一套协议的实现,这个思路很重要,完成这些思路所必须知道的基础也很重要

    2、基础

    2.1 网络分层

    我觉得网络分层很重要,因为它规定了大家都遵守的一套基本准则,有利于大家一起协作开发;也有利于新的解决痛点的协议推广;

    上图是三种分层模型已经相应关系;基本上我们都是使用五层或者四层模型;分层模型有效了解耦了各个层级的依赖;下面稍微说下每层所做的事情:

    1. 物理层

    模型的最低层。是网络通信的数据传输介质,由连接不同结点的电缆与设备共同构成。主要功能是:利用传输介质为数据链路层提供物理连接,负责处理数据传输并监控数据出错率,以便数据流的透明传输。

    2. 数据链路层

    主要功能是:在物理层提供的服务基础上,在通信的实体间建立数据链路连接,传输以“帧”为单位的数据包,并采用差错控制与流量控制方法,使有差错的物理线路变成无差错的数据链路。

    3. 网络层

    主要功能是:为数据在结点之间传输创建逻辑链路,通过路由选择算法为分组通过通信子网选择最适当的路径,以及实现拥塞控制、网络互联等功能。

    4. 传输层

    主要功能是向用户提供可靠的端到端(End-to-End)服务,处理数据包错误、数据包次序,以及其他一些关键传输问题。传输层向高层屏蔽了下层数据通信的细节,因此,它是计算机通信体系结构中关键的一层。

    5. 会话层

    主要功能是:负责维护两个结点之间的传输链接,以便确保点到点传输不中断,以及管理数据交换等功能。

    6. 表示层

    主要功能是:用于处理在两个通信系统中交换信息的表示方式,主要包括数据格式变换、数据加密与解密、数据压缩与恢复等功能。

    7. 应用层

    主要功能是:为应用软件提供了很多服务,例如文件服务器、数据库服务、电子邮件与其他网络软件服务。

    从java角度来看,已经提供了基于tcp、udp抽象的socket接口组,而且大部分定制网络需求可以从socket上下功夫,所以,一般来说,需要特别关注的是socket,上层协议;

    2.2 tcp/ip协议

    不是只有tcp、ip协议,只是利用 IP 进行通信时所必须用到的协议群的统称。具体来说,IP 或 ICMP、TCP 或 UDP、TELNET 或 FTP、以及 HTTP 等都属于 TCP/IP 协议。常遇到的协议就是ip、tcp、udp、websocket协议,协议都有协议报文格式;

    ip协议

    网络层协议;不保证分组的交付时限和可靠性,所传送分组有可能出现丢失、重复、延迟或乱序等问题。主要包含三方面内容:IP编址方案、分组封装格式及分组转发规则

    TCP 协议

    传输层协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议;旨在适应支持多网络应用的分层协议层次结构。 连接到不同但互连的计算机通信网络的主计算机中的成对进程之间依靠TCP提供可靠的通信服务。

    UDP协议

    传输层协议,一种无连接的传输层协议,输数据之前源端和终端不建立连接,提供面向事务的简单不可靠信息传送服务

    http协议

    应用层协议;一个基于TCP/IP通信协议来传递数据(HTML 文件, 图片文件, 查询结果等);由于其简捷、快速的方式,适用于分布式超媒体信息系统

    websocket协议

    应用层协议,一种在单个TCP连接上进行全双工通信的协议;WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。Websocket 通过HTTP/1.1 协议的101状态码进行握手。

    TLS协议

    安全传输层协议;独立于应用协议,用于在两个通信应用程序之间提供保密性和数据完整性;其首先进行握手,确认服务器或者服务器-客户端是否是预期的,并确定通信加密方式以及密钥,后续有加密方式+密钥来进行数据传输

    2.3 http 协议

    定义Web客户端如何从Web服务器请求Web页面,以及服务器如何把Web页面传送给客户端。HTTP协议采用了请求/响应模型。客户端向服务器发送一个请求报文,请求报文包含请求的方法、URL、协议版本、请求头部和请求数据。服务器以一个状态行作为响应,响应的内容包括协议的版本、成功或者错误代码、服务器信息、响应头部和响应数据。

    2.3.1 http请求报文

    请求行+请求头部+请求体

    有以下特点:

    • 每一行均以回车符+换行符结束
    • 请求头部每一行都是相同格式
    • 请求头部结束后需要增加一行,仅含有回车+换行
    • 请求体并不是必须的

    2.3.2 http响应报文

    响应行+响应头部+响应体

    有以下特点:

    • 每一行均以回车符+换行符结束
    • 响应头部每一行都是相同格式
    • 响应头部结束后需要增加一行,仅含有回车+换行
    • 响应体并不是必须的

    2.3.3 请求方法

    • GET

    向指定的资源发出“显示”请求。使用GET方法应该只用在读取数据,而不应当被用于产生“副作用”的操作中

    • HEAD

    与GET方法一样,都是向服务器发出指定资源的请求。只不过服务器将不传回资源的本文部分。它的好处在于,使用这个方法可以在不必传输全部内容的情况下,就可以获取其中“关于该资源的信息”(元信息或称元数据)。

    • POST

    向指定资源提交数据,请求服务器进行处理(例如提交表单或者上传文件)。数据被包含在请求本文中。这个请求可能会创建新的资源或修改现有资源,或二者皆有。

    • PUT

    向指定资源位置上传其最新内容。

    • DELETE

    请求服务器删除Request-URI所标识的资源。

    • TRACE

    回显服务器收到的请求,主要用于测试或诊断。

    • OPTIONS

    这个方法可使服务器传回该资源所支持的所有HTTP请求方法。用'*'来代替资源名称,向Web服务器发送OPTIONS请求,可以测试服务器功能是否正常运作。

    • CONNECT

    HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。通常用于SSL加密服务器的链接(经由非加密的HTTP代理服务器)。

    2.3.4 认证

    basic auth:基本认证是一种用来允许网页浏览器或其他客户端程序在请求时提供用户名和口令形式的身份凭证的一种登录验证方式;基本上所有流行的网页浏览器都支持基本认证,但基本在可信网络环境下使用;如果连接不可信,认证则不安全; http请求报文中增加头信息:

    Authorization: Basic YWRtaW46YWRtaW4= 
    // Authorization: "Basic 用户名和密码的base64加密字符串"
    复制代码
    

    而且上述代码可以来解决401错误;如果是407错误,则需要使用http 属性Proxy-Authenticate,对应值形式一致;服务器端如果人证失败,则使用响应头字段WWW-Authenticate来说明原因

    OAuth认证:OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是OAUTH的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此OAUTH是安全的。 基本流程如下图

    2.3.5 http三次握手进行

    2.3.6 四次挥手

    2.3.7 http缓存

    http缓存有:强缓存、协商缓存;

    http缓存指的是: 当客户端向服务器请求资源时,会先抵达浏览器缓存,如果浏览器有“要请求资源”的副本,就可以直接从浏览器缓存中提取而不是从原始服务器中提取这个资源。常见的http缓存只能缓存get请求响应的资源,对于其他类型的响应则无能为力,所以后续说的请求缓存都是指GET请求。

    http缓存都是从第二次请求开始的。第一次请求资源时,服务器返回资源,并在respone header头中回传资源的缓存参数;第二次请求时,浏览器判断这些请求参数,命中强缓存就直接200,否则就把请求参数加到request header头中传给服务器,看是否命中协商缓存,命中则返回304,否则服务器会返回新的资源;http缓存头部参数详细情况如下图:

    2.3.8 url编码

    url请求的查询部分、请求体中表单值对,要以ASCII 码来显示;只有字母和数字[0-9a-zA-Z]、一些特殊符号“$-_.+!*'(),”[不包括双引号]、以及某些保留字,才可以不经过编码直接用于URL。RFC 1738没有规定具体的编码方法,所以很混乱; 那么不合法的字符均需要处理;考虑到中文的情况;下面是Okhttp的处理方式

    字符串 -----> utf8编码 ------> 然后name/value进行字符串确认 ------> 不满足条件转换为 %16进制(一位变两位) 以下的字符均需要转换为16进制%分号形式

    1. 小于32的特殊字符
    2. 127的特殊字符
    3. "':;<=>@[]^`{}|/?#&!$(),~ 这些特殊字符串
      • -->

    最终值对,以&进行分割值对,name=value书写;例如下

    https://www.baidu.com/s?wd=ascii%E7%A0%81%E5%AF%B9%E7%85%A7%E8%A1%A8&rsv_spt=1&rsv_iqid=0x9cde902e00267942&issp=1&f=3&rsv_bp=1&rsv_idx=2&ie=utf-8&rqlang=cn&tn=baiduhome_pg&rsv_enter=1&rsv_dl=ts_1&oq=url%25E7%25BC%2596%25E7%25A0%2581&rsv_btype=t&inputT=4274&rsv_t=1e26hAToVzHm03kNMXEMGa5yj9Lg%2FGLlPiWj6bRe%2FMMtSzQdI2ys8h8qjdP3vsqEwb4a&rsv_pq=b5c4fa78002cb7bd&rsv_sug3=37&rsv_sug1=33&rsv_sug7=100&rsv_sug2=1&prefixsug=AS%2526lt%253Bii&rsp=1&rsv_sug4=4816
    复制代码
    

    2.3.9 http2

    SPDY是Google开发的基于TCP的传输层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验。SPDY并不是一种用于替代HTTP的协议,而是对HTTP协议的增强。新协议的功能包括数据流的多路复用、请求优先级以及HTTP报头压缩。

    HTTP/2以SPDY/2 为基础进行开发的 相比 HTTP/1.x,HTTP/2 在底层传输做了很大的改动和优化:

    • HTTP/2 采用二进制格式传输数据,而非 HTTP/1.x 的文本格式。
    • HTTP/2 对消息头采用 HPACK 进行压缩传输,能够节省消息头占用的网络的流量。
    • 多路复用,直白的说就是所有的请求都是通过一个 TCP 连接并发完成。
    • Server Push:服务端能够更快的把资源推送给客户端

    Frame 是 HTTP/2 二进制格式的基础,基本可以把它理解为它 TCP 里面的数据包一样。HTTP/2 之所以能够有如此多的新特性,正是因为底层数据格式的改变。 Frame 的基本格式如下(来源okhttp)

            0                   1                   2                   3
            0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
           +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
           |                 Length (24)                   |
           +---------------+---------------+---------------+
           |   Type (8)    |   Flags (8)   |
           +-+-+-----------+---------------+-------------------------------+
           |R|                 Stream Identifier (31)                      |
           +=+=============================================================+
           |                   Frame Payload (0...)                      ...
           +---------------------------------------------------------------+
    复制代码
    
    • Length: 表示 Frame Payload 部分的长度,另外 Frame Header 的长度是固定的 9 字节(Length + Type + Flags + R + Stream Identifier = 72 bit)。
    • Type: 区分这个 Frame Payload 存储的数据是属于 HTTP Header 还是 HTTP Body;另外 HTTP/2 新定义了一些其他的 Frame Type,例如,这个字段为 0 时,表示 DATA 类型(即 HTTP/1.x 里的 Body 部分数据)
    • Flags: 共 8 位, 每位都起标记作用。每种不同的 Frame Type 都有不同的 Frame Flags。例如发送最后一个 DATA 类型的 Frame 时,就会将 Flags 最后一位设置 1(flags &= 0x01),表示 END_STREAM,说明这个 Frame 是流的最后一个数据包。
    • R: 保留位。
    • Stream Identifier: 流 ID,当客户端和服务端建立 TCP 链接时,就会先发送一个 Stream ID = 0 的流,用来做些初始化工作。之后客户端和服务端从 1 开始发送请求/响应。

    Frame 由 Frame Header 和 Frame Payload 两部分组成。不论是原来的 HTTP Header 还是 HTTP Body,在 HTTP/2 中,都将这些数据存储到 Frame Payload,组成一个个 Frame,再发送响应/请求。通过 Frame Header 中的 Type 区分这个 Frame 的类型。

    HTTP/2 头压缩使用HPACK,使用一份索引表来定义常用的 HTTP Header。把常用的 HTTP Header 存放在表里。请求的时候便只需要发送在表里的索引位置即可。 不仅仅通过索引键值对来降低数据量,同时还会将字符串进行霍夫曼编码来压缩字符串大小。

    2.4 TLS协议

    提起这个协议很多人容易搞混SSL;其实SSL是TSL的前身,TSL是在SSL的基础上发展而来的;大致历程 SSL 1.0 -> SSL 2.0 -> SSL 3.0 = TSL 1.0 -> TSL 1.1 -> TSL 1.2 -> TSL 1.3 现在说SSL也基本上是指TLS

    该协议由两层组成: TLS 记录协议(TLS Record)和 TLS 握手协议(TLS Handshake)

    TLS记录协议用于封装各种高层协议。作为这种封装协议之一的握手协议允许服务器与客户机在应用程序协议传输和接收其第一个数据字节前彼此之间相互认证,协商加密算法和加密密钥。

    TLS 握手协议提供的连接安全

    TLS 握手

    图(非作者所有,来源于网络)中基本包括了握手过程所有的操作;流程是这样,但是还有很多细节未名,如有需要,与君一起深入了解

    TLS证书

    遵守TLS协议的一种数字证书,由全球信任的证书颁发机构(CA)验证服务器身份后颁发。用于身份验证和数据加密传输。

    数字证书的基本架构是公开密钥PKI,即利用一对密钥实施加密和解密。其中密钥包括私钥和公钥,私钥主要用于签名和解密,由用户自定义,只有用户自己知道;公钥用于签名验证和加密,可被多个用户共享。

    电脑、手机,其系统都存在根证书;

    2.5 加解密

    加密算法非为对称加密、非对称加密;

    对称加密:加密和解密使用相同密钥的加密算法。对称加密算法的优点在于加解密的高速度和使用长密钥时的难破解性

    常见的对称加密算法:DES、3DES、DESX、Blowfish、IDEA、RC4、RC5、RC6和AES

    非对称加密:加密和解密使用不同密钥的加密算法,也称为公私钥加密。假设两个用户要加密交换数据,双方交换公钥,使用时一方用对方的公钥加密,另一方即可用自己的私钥解密。

    常见的非对称加密算法:RSA、ECC(移动设备用)、Diffie-Hellman、El Gamal、DSA(数字签名用)

    Hash算法: 一种单向算法,用户可以通过Hash算法对目标信息生成一段特定长度的唯一的Hash值,却不能通过这个Hash值重新获得目标信息。因此Hash算法常用在不可还原的密码存储、信息完整性校验等。

    常见的Hash算法:MD2、MD4、MD5、HAVAL、SHA、SHA-1、HMAC、HMAC-MD5、HMAC-SHA1

    与加解密容易混淆的概念:编解码、压缩解压缩

    编解码:编码是信息从一种形式或格式转换为另一种形式的过程,解码是还原信息过程;由此可见加解密是编解码,但编解码不一定是加解密;常见的编码:字符编码、pcm编码、加密

    压缩解压缩 :压缩是一种通过特定的算法来减小计算机文件大小的机制。 解压缩还原文件过程;分为有损压缩、无损压缩;常见压缩格式有rar,zip,tar,jar,iso,gzip,bz2

    2.6 websocket协议

    websocket的连接分为两部,第一步通过http/https进行握手,第二部,建立长连接后,可以按照websocket报文互相发送消息

    握手请求

    http请求报文、和答复报文基本固定

    GET url HTTP/1.1
    Host: host
    Connection: Upgrade
    Origin:null
    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
    Sec-WebSocket-Protocol: chat, superchat
    Sec-WebSocket-Version: 13
    复制代码
    

    服务器同意答复

    HTTP/1.1 101 Switching Protocols
    Connection:Upgrade
    Server:beetle websocket server
    Upgrade:WebSocket
    Date:Mon, 26 Mon 2020 23:42:44 GMT
    Access-Control-Allow-Credentials:true
    Access-Control-Allow-Headers:content-type
    Sec-WebSocket-Accept:FCKgUr8c7OsDsLFeJTWrJw6WO8Q= 
    复制代码
    

    websocket报文

    图片来自于网络;内容对应大致如下:

    FIN:1bit,是否为信息的最后一帧
    RSV 1-3:1bit,备用,默认为0
    opcode:4bit,帧类型
                             0x00 连续消息分片
                             0x01 文本消息分片
                             0x02 二进制消息分片
                             0x03 ~ 0x07 为将来的非控制消息片段保留测操作吗
                             0x08 连接关闭
                             0x09 心跳检查 ping
                             0x0a 心跳检查pong
                             0x0b ~ 0x0f 为将来的控制消息片段保留的操作码
    MASK:定义传输的数据是否有加掩码,如果设置为1,掩码键必须放在masking-key区域,客户端发送给服务端的所有消息,此位的值都是1
    payload length:7bit,传输数据长度,以字节为单位。当这个长度为7bit数字为126时,紧随其后的2个字节也是表示数据长度。当这个长度为7bit数字为127时,紧随其后的8个字节也是表示数据长度。
    Masking-key:0或者4bit,只有当MASK设置为1时才有效。
    Playload data:负载数据,为扩展数据和应用数据之和,Extension data + Application data。
    Extension data:扩展数据,如果客户端和服务端没有特殊的约定,那么扩展数据长度始终为0
    Application data:应用数据,
    复制代码
    

    2.7 Socket

    套接字是通信的基石,是支持TCP/IP协议的路通信的基本操作单元。可以将套接字看作不同主机间的进程进行双间通信的端点,它构成了单个主机内及整个网络间的编程界面。以看成是两个网络应用程序进行通信时,各自通信连接中的端点,这是一个逻辑上的概念;分为三种套接字

    1. 流套接字(SOCK_STREAM) 流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP协议 。流程大致如下图

    1. 数据报套接字(SOCK_DGRAM) 数据报套接字提供一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP协议进行数据的传输。不需要连接,可以直接发送和接收消息

    2. 原始套接字(SOCK_RAW) 原始套接字可以读写内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。

    2.8 java中的代理

    也就是代理服务器,是一种重要的服务器安全功能,它的工作主要在开放系统互联(OSI)模型的会话层,从而起到防火墙的作用,也可以达到翻墙、加速等作用;java中主要相关的两个类Proxy,ProxySelector

    Proxy

    Proxy(Proxy.Type type, SocketAddress sa)
    复制代码
    

    proxy有三种type:DIRECT(直连,不使用代理)、HTTP(高级协议代理,HTTP、FTP等的代理)、SOCKS(SOCKS V4、V5的代理) Proxy在Socket构造方法、URL.openConnection方法均可以传入;

    ProxySelector,抽象类,该类的对象可以根据你要连接的URL自动选择最合适的代理,但是该类是抽象类,需要自己继承该类实现个性化、先进化的代理自动选择;主要有下面两个抽象方法

    • List select(URI uri):给定一个URI(URL的父类,但实际中只用URL)返回一个最适合访问该URI的代理服务器列表,其中会首选列表的第一个代理,如果不行则用第二个,如果全部不行就会调用connectFailed方法
    • connectFailed:处理连接失败问题

    系统已经提供了默认选择器;我们也可以自己设置,

    ProxySelector.setDefault(ProxySelector ps)
    复制代码
    

    除了这两种方式,java还提供了虚拟机环境变量的方式:对应变量格式为: 协议.xxx;协议可以为http,https,sock,socks等;xxx只能为以下三种

    • proxyHost:代理的IP地址
    • proxyPort:代理服务器的端口
    • nonProxyHosts:不需要使用代理就可以访问的网址(主机),可以指定多个,多个网址之间用|隔开,允许使用通配符*表示

    相关设置方法

      static Properties System.getProperties(); // 获得系统属性表对象
      synchronized Object Properties.setProperty(String key, String value); // 设置属性值,并返回旧值
    复制代码
    

    关于基本知识就差不多了,下面要进行okhttp的架构流程和一些细节处理

    3、okhttp 架构流程

    分为http请求和websocket请求;websocket的请求由两部分请求http请求协议切换建立websocket长连接,然后进行信息交互;

    http请求又可以再来分下,请求内容、请求连解、响应内容

    3.1 Request请求对象

    里面包含了http请求报文的内容;利用了构建者模式,进行请求信息封装

      final HttpUrl url;
      final String method;
      final Headers headers;
      final @Nullable RequestBody body;
      final Map<Class<?>, Object> tags;
      private volatile @Nullable CacheControl cacheControl; 
    复制代码
    

    HttpUrl:URL信息封装;query中值对信息要进行url编码

      http://username:password@hostname:port/path/?query#fragment
    复制代码
    

    method:请求方法 headers:请求头部 RequestBody:请求体 tags:不是请求报文的东西;是开发者自己做的标记,可以在自定义拦截器中接收处理 CacheControl:是对CacheControl字段信息的收集封装

    RequestBody RequestBody 抽象类;包括Content-type类型(由MediaType类来解析),长度,以及如何内容写入; 类中提供了create静态方法添加对象;okhttp类提供了两个实现类,不过也可以自定义,实现下面方法

      public abstract @Nullable MediaType contentType();
    
      public long contentLength() throws IOException {
        return -1;
      }
    
      public abstract void writeTo(BufferedSink sink) throws IOException;
    复制代码
    

    FormBody:表单内容;其实是一系列name、value值对;每一个值对中间使用&分割,其name、value需要进行URL编码

    MultipartBody:多个内容体;每个内容开始形式:--boundary\r\n 结束: \r\n--boundary--\r\n;中间内容由许多头部格式内容,但是肯定有Content-Length、Content-Type头部,并且头部和下方内容需要\r\n行; 每个内容可以有自己的头部信息,和单个请求体相比,只是增加了开始和结尾标志

    3.2 Response相应对象

    里面包含了请求信息、协议、响应码、响应信息、握手信息、响应头、响应体、请求时间戳、响应时间戳、请求-事件管家、响应缓存信息

      final Request request;
      final Protocol protocol;
      final int code;
      final String message;
      final @Nullable Handshake handshake;
      final Headers headers;
      final @Nullable ResponseBody body;
      final @Nullable Response networkResponse;
      final @Nullable Response cacheResponse;
      final @Nullable Response priorResponse;
      final long sentRequestAtMillis;
      final long receivedResponseAtMillis;
      final @Nullable Exchange exchange;
      private volatile @Nullable CacheControl cacheControl;
    复制代码
    

    Handshake:TLS握手信息,tls版本、协商加密套件、服务证书、本地证书

    ResponseBody:也是个抽象类,自定义须实现,也即提供内容类别长度,和写出方式

      public abstract @Nullable MediaType contentType();
    
      public abstract long contentLength();
    
      public abstract BufferedSource source();
    复制代码
    

    Exchange:请求信息写入、响应信息写出、可用socket查找、事件调用、是否处理超时请求

    3.3 http请求

    OkHttpClient对象提供了完成请求封装的api;

    newCall方法生成一个call对象,并进行了一些初始化信息

      public Call newCall(Request request) {
        return RealCall.newRealCall(this, request, false);
      }
    
      static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
        RealCall call = new RealCall(client, originalRequest, forWebSocket);
        call.transmitter = new Transmitter(client, call);
        return call;
      }
    复制代码
    

    enqueue方法进行异步请求;未请求过的,生成异步任务,并放入调度器中调度

    public void enqueue(Callback responseCallback) {
      synchronized (this) {
        if (executed) throw new IllegalStateException("Already Executed");
        executed = true;
      }
      transmitter.callStart();
      client.dispatcher().enqueue(new AsyncCall(responseCallback));
    }
    复制代码
    

    异步任务加入队列,并且如果是不是websocket请求,则查找同服务请求的个数,未找到则为0,找到则设置

    void enqueue(AsyncCall call) {
      synchronized (this) {
        readyAsyncCalls.add(call);
        if (!call.get().forWebSocket) {
          AsyncCall existingCall = findExistingCallWithHost(call.host());
          if (existingCall != null) call.reuseCallsPerHostFrom(existingCall);
        }
      }
      promoteAndExecute();
    }
    复制代码
    

    查找ready的所有任务,符合要求的AsyncCall添加到运行队列中,并进行运行;请求的最大个数默认64个,同一个主机最大个数默认5个,这个可以进行自己调整;dispatch的promoteAndExecute方法,我引用了之后,换行编写就会出现问题,大家见谅;

    调用AsyncCall的execute法进行处理

    protected void execute() {
        boolean signalledCallback = false;
        transmitter.timeoutEnter();
        try {
          Response response = getResponseWithInterceptorChain();
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);
        } catch (IOException e) {
          if (signalledCallback) {
            Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
          } else {
            responseCallback.onFailure(RealCall.this, e);
          }
        } catch (Throwable t) {
          cancel();
          if (!signalledCallback) {
            IOException canceledException = new IOException("canceled due to " + t);
            canceledException.addSuppressed(t);
            responseCallback.onFailure(RealCall.this, canceledException);
          }
          throw t;
        } finally {
          client.dispatcher().finished(this);
        }
      }
    复制代码
    

    RealCall 调用execute进行同步任务处理

    public Response execute() throws IOException {
      synchronized (this) {
        if (executed) throw new IllegalStateException("Already Executed");
        executed = true;
      }
      transmitter.timeoutEnter();
      transmitter.callStart();
      try {
        client.dispatcher().executed(this);
        return getResponseWithInterceptorChain();
      } finally {
        client.dispatcher().finished(this);
      }
    }
    复制代码
    

    同步任务和异步任务,都会把任务执行时都会把任务加入执行队列,但是异步任务可能被限制执行;都条用getResponseWithInterceptorChain方法执行具体请求,并在结束后移除队列、并检查异步任务执行

    getResponseWithInterceptorChain方法执行的动作,就是大家熟知的职责链了,按照数据有如下职责:RetryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor、ConnectInterceptor、自定义职责链、CallServerInterceptor;由Interceptor.Chain.proceed(originalRequest),开启整个过程

    Response getResponseWithInterceptorChain() throws IOException {
      List<Interceptor> interceptors = new ArrayList<>();
      interceptors.addAll(client.interceptors());
      interceptors.add(new RetryAndFollowUpInterceptor(client));
      interceptors.add(new BridgeInterceptor(client.cookieJar()));
      interceptors.add(new CacheInterceptor(client.internalCache()));
      interceptors.add(new ConnectInterceptor(client));
      if (!forWebSocket) {
        interceptors.addAll(client.networkInterceptors());
      }
      interceptors.add(new CallServerInterceptor(forWebSocket));
    
      Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
          originalRequest, this, client.connectTimeoutMillis(),
          client.readTimeoutMillis(), client.writeTimeoutMillis());
    
      boolean calledNoMoreExchanges = false;
      try {
        Response response = chain.proceed(originalRequest);
        if (transmitter.isCanceled()) {
          closeQuietly(response);
          throw new IOException("Canceled");
        }
        return response;
      } catch (IOException e) {
        calledNoMoreExchanges = true;
        throw transmitter.noMoreExchanges(e);
      } finally {
        if (!calledNoMoreExchanges) {
          transmitter.noMoreExchanges(null);
        }
      }
    }
    复制代码
    

    RealInterceptorChain的proceed方法:调用当前索引对应的拦截器的intercept方法,并且把new下个索引的RealInterceptorChain对象

    public Response proceed(Request request) throws IOException {
      return proceed(request, transmitter, exchange);
    }
    
    public Response proceed(Request request, Transmitter transmitter, @Nullable Exchange exchange)
        throws IOException {
      if (index >= interceptors.size()) throw new AssertionError();
    
      calls++;
      。。。。
      RealInterceptorChain next = new RealInterceptorChain(interceptors, transmitter, exchange,
          index + 1, request, call, connectTimeout, readTimeout, writeTimeout);
      Interceptor interceptor = interceptors.get(index);
      Response response = interceptor.intercept(next);
    
      。。。。。
    
      return response;
    }
    复制代码
    

    RetryAndFollowUpInterceptor:重定向或者失败重试机制;进行请求的一些对象进行初始化,然后进行请求,并对结果进行处理

    BridgeInterceptor: 增加必要的http报文请求头部信息

    CacheInterceptor:首先查看是否有效缓存结果,有直接返回,无的时候,则进行继续调用职责链,职责链结束后,响应进行缓存处理

    ConnectInterceptor:复用socket链接,进行请求,包括tls握手过程

    CallServerInterceptor:写入请求报文,并解析响应报文

    3.3.1 RetryAndFollowUpInterceptor

    public Response intercept(Chain chain) throws IOException {
      Request request = chain.request();
      RealInterceptorChain realChain = (RealInterceptorChain) chain;
      Transmitter transmitter = realChain.transmitter();
    
      int followUpCount = 0;
      Response priorResponse = null;
      while (true) {
        transmitter.prepareToConnect(request);
    
        if (transmitter.isCanceled()) {
          throw new IOException("Canceled");
        }
    
        Response response;
        boolean success = false;
        try {
          response = realChain.proceed(request, transmitter, null);
          success = true;
        } catch (RouteException e) {
          if (!recover(e.getLastConnectException(), transmitter, false, request)) {
            throw e.getFirstConnectException();
          }
          continue;
        } catch (IOException e) {
          boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
          if (!recover(e, transmitter, requestSendStarted, request)) throw e;
          continue;
        } finally {
          if (!success) {
            transmitter.exchangeDoneDueToException();
          }
        }
    
        if (priorResponse != null) {
          response = response.newBuilder()
              .priorResponse(priorResponse.newBuilder()
                      .body(null)
                      .build())
              .build();
        }
    
        Exchange exchange = Internal.instance.exchange(response);
        Route route = exchange != null ? exchange.connection().route() : null;
        Request followUp = followUpRequest(response, route);
    
        if (followUp == null) {
          if (exchange != null && exchange.isDuplex()) {
            transmitter.timeoutEarlyExit();
          }
          return response;
        }
    
        RequestBody followUpBody = followUp.body();
        if (followUpBody != null && followUpBody.isOneShot()) {
          return response;
        }
    
        closeQuietly(response.body());
        if (transmitter.hasExchange()) {
          exchange.detachWithViolence();
        }
    
        if (++followUpCount > MAX_FOLLOW_UPS) {
          throw new ProtocolException("Too many follow-up requests: " + followUpCount);
        }
    
        request = followUp;
        priorResponse = response;
      }
    }
    复制代码
    

    采用死循环来处理重试,先进行后续职责链获取处理结果,然后根据结果来决定是否跳出循环

    while死循环处理;跳出条件:

    • 请求成功
    • 请求次数超标
    • 请求内容RequestBody对象只能使用一次,默认是false,可以多次使用
    • 一些返回结果码表示不需要重试
    • 在处理过程出现的一些异常,且不是连接可修复的

    对于错误码的处理,一下情况才会重新尝试

    • 407错误,代理服务器是http代理,OkHttpClient.proxyAuthenticator对象验证返回有效Reqeust对象,默认返回无效对象
    • 401错误,OkHttpClient.authenticator对象验证返回有效Reqeust对象,默认返回无效对象
    • 301-303 或者 307/308且是get或者head请求,从响应头Location提取重定向url成功
    • 408 错误 OkhttpClient.retryOnConnectionFailure为true(默认为false)、请求体为空或者可重复利用、首次发生408错误、Retry-After响应头不存在或者值小于等于0
    • 503错误 首次503错误且Retry-After响应头不存在或者值等于0

    3.3.2 BridgeInterceptor

      public Response intercept(Chain chain) throws IOException {
        Request userRequest = chain.request();
        Request.Builder requestBuilder = userRequest.newBuilder();
    
        RequestBody body = userRequest.body();
        if (body != null) {
          MediaType contentType = body.contentType();
          if (contentType != null) {
            requestBuilder.header("Content-Type", contentType.toString());
          }
    
          long contentLength = body.contentLength();
          if (contentLength != -1) {
            requestBuilder.header("Content-Length", Long.toString(contentLength));
            requestBuilder.removeHeader("Transfer-Encoding");
          } else {
            requestBuilder.header("Transfer-Encoding", "chunked");
            requestBuilder.removeHeader("Content-Length");
          }
        }
    
        if (userRequest.header("Host") == null) {
          requestBuilder.header("Host", hostHeader(userRequest.url(), false));
        }
    
        if (userRequest.header("Connection") == null) {
          requestBuilder.header("Connection", "Keep-Alive");
        }
    
        boolean transparentGzip = false;
        if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
          transparentGzip = true;
          requestBuilder.header("Accept-Encoding", "gzip");
        }
    
        List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
        if (!cookies.isEmpty()) {
          requestBuilder.header("Cookie", cookieHeader(cookies));
        }
    
        if (userRequest.header("User-Agent") == null) {
          requestBuilder.header("User-Agent", Version.userAgent());
        }
    
        Response networkResponse = chain.proceed(requestBuilder.build());
    
        HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
    
        Response.Builder responseBuilder = networkResponse.newBuilder()
            .request(userRequest);
    
        if (transparentGzip
            && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
            && HttpHeaders.hasBody(networkResponse)) {
          GzipSource responseBody = new GzipSource(networkResponse.body().source());
          Headers strippedHeaders = networkResponse.headers().newBuilder()
              .removeAll("Content-Encoding")
              .removeAll("Content-Length")
              .build();
          responseBuilder.headers(strippedHeaders);
          String contentType = networkResponse.header("Content-Type");
          responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
        }
    
        return responseBuilder.build();
      }
    复制代码
    

    根据请求内容增加必要的请求头信息

    • 请求体不为空:增加请求编码方式Content-Type,长度Content-Length 或者ransfer-Encoding: chunked
    • 增加Host请求头信息
    • 增加长连接请求头Connection: Keep-Alive
    • 增加编码方式,如果用户请求时未设置Accept-Encoding和Range,则设置Accept-Encoding: gzip
    • 增加用户标识:User-Agent
    • 添加url 缓存头部,默认缓存无

    头部信息添加完毕后,则进行后续职责链处理;并且对结果进行处理:

    1. 对Set-Cookie进行处理,如果cookie处理对象是默认,则不做任何处理
    • 解析值对、过期信息expires、max-age、domain、path、secure、httponly
    1. 如果请求接收gzip,且结果返回也是,则响应头部移除Content-Encoding、Content-Length
    2. 返回响应体

    3.3.3 CacheInterceptor

    Response intercept(Chain chain) throws IOException {
      Response cacheCandidate = cache != null
          ? cache.get(chain.request())
          : null;
    
      long now = System.currentTimeMillis();
    
      CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
      Request networkRequest = strategy.networkRequest;
      Response cacheResponse = strategy.cacheResponse;
    
      if (cache != null) {
        cache.trackResponse(strategy);
      }
    
      if (cacheCandidate != null && cacheResponse == null) {
        closeQuietly(cacheCandidate.body());
      }
    
      if (networkRequest == null && cacheResponse == null) {
        return new Response.Builder()
            .request(chain.request())
            .protocol(Protocol.HTTP_1_1)
            .code(504)
            .message("Unsatisfiable Request (only-if-cached)")
            .body(Util.EMPTY_RESPONSE)
            .sentRequestAtMillis(-1L)
            .receivedResponseAtMillis(System.currentTimeMillis())
            .build();
      }
    
      if (networkRequest == null) {
        return cacheResponse.newBuilder()
            .cacheResponse(stripBody(cacheResponse))
            .build();
      }
    
      Response networkResponse = null;
      try {
        networkResponse = chain.proceed(networkRequest);
      } finally {
        if (networkResponse == null && cacheCandidate != null) {
          closeQuietly(cacheCandidate.body());
        }
      }
    
      if (cacheResponse != null) {
        if (networkResponse.code() == HTTP_NOT_MODIFIED) {
          Response response = cacheResponse.newBuilder()
              .headers(combine(cacheResponse.headers(), networkResponse.headers()))
              .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
              .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
              .cacheResponse(stripBody(cacheResponse))
              .networkResponse(stripBody(networkResponse))
              .build();
          networkResponse.body().close();
    
          cache.trackConditionalCacheHit();
          cache.update(cacheResponse, response);
          return response;
        } else {
          closeQuietly(cacheResponse.body());
        }
      }
    
      Response response = networkResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .networkResponse(stripBody(networkResponse))
          .build();
    
      if (cache != null) {
        if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
          CacheRequest cacheRequest = cache.put(response);
          return cacheWritingResponse(cacheRequest, response);
        }
    
        if (HttpMethod.invalidatesCache(networkRequest.method())) {
          try {
            cache.remove(networkRequest);
          } catch (IOException ignored) {
          }
        }
      }
    
      return response;
    }
    复制代码
    
    1. 进行缓存处理,默认下是不需要的;如果缓存设置了only-if-cached,存在则返回缓存结果,否则返回504错误
    2. 进行剩下的职责链请求
    3. 返回码非304则进行缓存;304则关闭响应体;返回处理结果;304服务器端未修改资源,缓存可用

    3.3.4 ConnectInterceptor

    public Response intercept(Chain chain) throws IOException {
      RealInterceptorChain realChain = (RealInterceptorChain) chain;
      Request request = realChain.request();
      Transmitter transmitter = realChain.transmitter();
    
      boolean doExtensiveHealthChecks = !request.method().equals("GET");
      Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
    
      return realChain.proceed(request, transmitter, exchange);
    }
    复制代码
    

    Transmitter.newExchange方法进行socket连接、tls握手

    Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
      synchronized (connectionPool) {
        if (noMoreExchanges) {
          throw new IllegalStateException("released");
        }
        if (exchange != null) {
          throw new IllegalStateException("cannot make a new request because the previous response "
              + "is still open: please call response.close()");
        }
      }
    
      ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);
      Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);
    
      synchronized (connectionPool) {
        this.exchange = result;
        this.exchangeRequestDone = false;
        this.exchangeResponseDone = false;
        return result;
      }
    }
    复制代码
    

    ExchangeFinder.find方法中继续处理

    public ExchangeCodec find(
        OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
      int connectTimeout = chain.connectTimeoutMillis();
      int readTimeout = chain.readTimeoutMillis();
      int writeTimeout = chain.writeTimeoutMillis();
      int pingIntervalMillis = client.pingIntervalMillis();
      boolean connectionRetryEnabled = client.retryOnConnectionFailure();
    
      try {
        RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
            writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
        return resultConnection.newCodec(client, chain);
      } catch (RouteException e) {
        trackFailure();
        throw e;
      } catch (IOException e) {
        trackFailure();
        throw new RouteException(e);
      }
    }
    复制代码
    

    RealConnection.newCodec返回socket输入输出信息的编解码;其内部是由是否是h2或者h2_prior_knowledge协议来决定的;Http2ExchangeCodec或者Http1ExchangeCodec

    findHealthyConnection方法继续 --- findConnection 方法继续

    private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
        int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
      boolean foundPooledConnection = false;
      RealConnection result = null;
      Route selectedRoute = null;
      RealConnection releasedConnection;
      Socket toClose;
      synchronized (connectionPool) {
      。。。。。。。。寻找已经建立连接的可用RealConnection对象;如果没有则新建,并进行连接
    
      result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
          connectionRetryEnabled, call, eventListener);
      。。。。。。。。。
      return result;
    }
    复制代码
    

    首先获取RealConnection对象,优先从池中获取,然后新建;然后调用RealConnection.connect进行处理

    public void connect(int connectTimeout, int readTimeout, int writeTimeout,
        int pingIntervalMillis, boolean connectionRetryEnabled, Call call,
        EventListener eventListener) {
      if (protocol != null) throw new IllegalStateException("already connected");
    
      RouteException routeException = null;
      List<ConnectionSpec> connectionSpecs = route.address().connectionSpecs();
      ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);
    
      if (route.address().sslSocketFactory() == null) {
        。。。。。。。。。。 省略
    
      while (true) {
        try {
          if (route.requiresTunnel()) {
            connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
              break;
            }
          } else {
            connectSocket(connectTimeout, readTimeout, call, eventListener);
          }
          establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
          eventListener.connectEnd(call, route.socketAddress(), route.proxy(), protocol);
          break;
        } catch (IOException e) {
          。。。。。。省略
        }
      }
    
      。。。。。。。。省略
    }
    复制代码
    

    connectTunnel方法是处理https请求且使用了http代理的,其内部也是使用了connectSocket方法

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

    获取Socket对象,并调用Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout)进行socket.connect连接;如果成功,则获取输入输出流到成员变量中

    establishProtocol方法在socket连接成功后,有可能进行TLS连接;并且对协议进行解析,决定http报文解析方式;connectTls进行tls握手

    private void establishProtocol(ConnectionSpecSelector connectionSpecSelector,
        int pingIntervalMillis, Call call, EventListener eventListener) throws IOException {
      if (route.address().sslSocketFactory() == null) {
        if (route.address().protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE)) {
          socket = rawSocket;
          protocol = Protocol.H2_PRIOR_KNOWLEDGE;
          startHttp2(pingIntervalMillis);
          return;
        }
    
        socket = rawSocket;
        protocol = Protocol.HTTP_1_1;
        return;
      }
    
      eventListener.secureConnectStart(call);
      connectTls(connectionSpecSelector);
      eventListener.secureConnectEnd(call, handshake);
    
      if (protocol == Protocol.HTTP_2) {
        startHttp2(pingIntervalMillis);
      }
    }
    复制代码
    

    conectTls方法中,通过SSLSocket.startHandshake进行握手,成功后可以获取握手信息SSLSession

        sslSocket.startHandshake();
        SSLSession sslSocketSession = sslSocket.getSession();
    复制代码
    

    3.3.5 CallServerInterceptor

    自定义处理器就不用讲了;而且自定义处理器值对http有效;

    public Response intercept(Chain chain) throws IOException {
      RealInterceptorChain realChain = (RealInterceptorChain) chain;
      Exchange exchange = realChain.exchange();
      Request request = realChain.request();
    
      long sentRequestMillis = System.currentTimeMillis();
    
      exchange.writeRequestHeaders(request);
    
      boolean responseHeadersStarted = false;
      Response.Builder responseBuilder = null;
      if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
    
        if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
          exchange.flushRequest();
          responseHeadersStarted = true;
          exchange.responseHeadersStart();
          responseBuilder = exchange.readResponseHeaders(true);
        }
    
        if (responseBuilder == null) {
          if (request.body().isDuplex()) {
    
            exchange.flushRequest();
            BufferedSink bufferedRequestBody = Okio.buffer(
                exchange.createRequestBody(request, true));
            request.body().writeTo(bufferedRequestBody);
          } else {
    
            BufferedSink bufferedRequestBody = Okio.buffer(
                exchange.createRequestBody(request, false));
            request.body().writeTo(bufferedRequestBody);
            bufferedRequestBody.close();
          }
        } else {
          exchange.noRequestBody();
          if (!exchange.connection().isMultiplexed()) {
    
            exchange.noNewExchangesOnConnection();
          }
        }
      } else {
        exchange.noRequestBody();
      }
    
      if (request.body() == null || !request.body().isDuplex()) {
        exchange.finishRequest();
      }
    
      if (!responseHeadersStarted) {
        exchange.responseHeadersStart();
      }
    
      if (responseBuilder == null) {
        responseBuilder = exchange.readResponseHeaders(false);
      }
    
      Response response = responseBuilder
          .request(request)
          .handshake(exchange.connection().handshake())
          .sentRequestAtMillis(sentRequestMillis)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    
      int code = response.code();
      if (code == 100) {
        response = exchange.readResponseHeaders(false)
            .request(request)
            .handshake(exchange.connection().handshake())
            .sentRequestAtMillis(sentRequestMillis)
            .receivedResponseAtMillis(System.currentTimeMillis())
            .build();
    
        code = response.code();
      }
    
      exchange.responseHeadersEnd(response);
    
      if (forWebSocket && code == 101) {
        response = response.newBuilder()
            .body(Util.EMPTY_RESPONSE)
            .build();
      } else {
        response = response.newBuilder()
            .body(exchange.openResponseBody(response))
            .build();
      }
    
      if ("close".equalsIgnoreCase(response.request().header("Connection"))
          || "close".equalsIgnoreCase(response.header("Connection"))) {
        exchange.noNewExchangesOnConnection();
      }
    
      if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
        throw new ProtocolException(
            "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
      }
    
      return response;
    }
    复制代码
    
    1. 通过Exchange 写入头部信息

    2. 如果不是HEAD、GET请求,则写入请求体

    3. 返回结果100状态码,继续写入,并重新获取状态码

    4. 通过Exchange获取响应头部信息

    5. 101且是websocket,消息体为空,否则通过Exchange读取响应体

    6. 204、205抛出自定义异常

    7. 返回结果

      Exchange类

    • writeRequestHeaders方法 写入请求行和请求头部,并写入和body的分隔\r\n
    • createRequestBody方法 返回请求体的流
    • readResponseHeaders方法 读取头部
    • openResponseBody方法 获取含有响应体的ResponseBody对象

    3.4 websocket请求

    OkHttpClient.newWebsocket方法为入口开始;

        RealWebSocket webSocket = new RealWebSocket(request, listener, new Random(), pingInterval);
        webSocket.connect(this);
        return webSocket;
      }
    复制代码
    

    RealWebSocket 的connect方法进行连解;首先进行http连接,需要特殊的头部信息,这些信息告诉服务器我们要转Websocket协议,如果服务器回复101,则表示成功;在请求成功回调中进行了3件事:开启了定时ping心跳包;开启了读循环、可能开启写操作

    public void connect(OkHttpClient client) {
      client = client.newBuilder()
          .eventListener(EventListener.NONE)
          .protocols(ONLY_HTTP1)
          .build();
      final Request request = originalRequest.newBuilder()
          .header("Upgrade", "websocket")
          .header("Connection", "Upgrade")
          .header("Sec-WebSocket-Key", key)
          .header("Sec-WebSocket-Version", "13")
          .build();
      call = Internal.instance.newWebSocketCall(client, request);
      call.enqueue(new Callback() {
        @Override public void onResponse(Call call, Response response) {
          Exchange exchange = Internal.instance.exchange(response);
          Streams streams;
          try {
            checkUpgradeSuccess(response, exchange);
            streams = exchange.newWebSocketStreams();
          } catch (IOException e) {
            if (exchange != null) exchange.webSocketUpgradeFailed();
            failWebSocket(e, response);
            closeQuietly(response);
            return;
          }
    
          try {
            String name = "OkHttp WebSocket " + request.url().redact();
            initReaderAndWriter(name, streams);
            listener.onOpen(RealWebSocket.this, response);
            loopReader();
          } catch (Exception e) {
            failWebSocket(e, null);
          }
        }
    
        @Override public void onFailure(Call call, IOException e) {
          failWebSocket(e, null);
        }
      });
    }
    复制代码
    

    通过RealWebSocket.send方法发送消息;通过WebSocketListener监听可以收到消息,以及长连接开启关闭信息;

    checkUpgradeSuccess方法再次确认websocket连接成功

    initReaderAndWriter方法初始化websocket的输入输出流、并开启心跳包

    public void initReaderAndWriter(String name, Streams streams) throws IOException {
      synchronized (this) {
        this.streams = streams;
        this.writer = new WebSocketWriter(streams.client, streams.sink, random);
        this.executor = new ScheduledThreadPoolExecutor(1, Util.threadFactory(name, false));
        if (pingIntervalMillis != 0) {
          executor.scheduleAtFixedRate(
              new PingRunnable(), pingIntervalMillis, pingIntervalMillis, MILLISECONDS);
        }
        if (!messageAndCloseQueue.isEmpty()) {
          runWriter(); 
        }
      }
    
      reader = new WebSocketReader(streams.client, streams.source, this);
    }
    复制代码
    

    reader,writer即为读写流;pingIntervalMillis为心跳包频率,如果为0,就没有心跳包了;messageAndCloseQueue是消息队列,如果有则进行写入操作

    loopReader方法开启了读循环;读到的消息分为两种控制消息,其它消息;读到的消息通过回调传回

    public void loopReader() throws IOException {
      while (receivedCloseCode == -1) {
        reader.processNextFrame();
      }
    }
    
    void processNextFrame() throws IOException {
      readHeader();
      if (isControlFrame) {
        readControlFrame();
      } else {
        readMessageFrame();
      }
    }
    复制代码
    

    send方法发送消息;如果长连接正常,如果这个长连接发送的消息大于16MB,则关闭,发送失败;消息放入messageAndCloseQueue队列中,进行写入

    public boolean send(String text) {
      if (text == null) throw new NullPointerException("text == null");
      return send(ByteString.encodeUtf8(text), OPCODE_TEXT);
    }
    
    public boolean send(ByteString bytes) {
      if (bytes == null) throw new NullPointerException("bytes == null");
      return send(bytes, OPCODE_BINARY);
    }
    
    private synchronized boolean send(ByteString data, int formatOpcode) {
      if (failed || enqueuedClose) return false;
    
      if (queueSize + data.size() > MAX_QUEUE_SIZE) {
        close(CLOSE_CLIENT_GOING_AWAY, null);
        return false;
      }
    
      queueSize += data.size();
      messageAndCloseQueue.add(new Message(formatOpcode, data));
      runWriter();
      return true;
    }
    复制代码
    

    脉络就讲到这里了

    写在最后


    我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在IT学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多程序员朋友无法获得正确的资料得到学习提升,故此将并将重要的Android进阶资料包括自定义view、性能优化、MVC与MVP与MVVM三大框架的区别、NDK技术、阿里面试题精编汇总、常见源码分析等学习资料免费分享出来。

    知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。包含知识脉络 + 诸多细节,由于篇幅有限,下面只是以图片的形式给大家展示一部分。

    Android学习PDF+学习视频+面试文档+知识点笔记

    【Android高级架构视频学习资源】

    Android部分精讲视频领取学习后更加是如虎添翼!进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

    【Android进阶学习视频】、【全套Android面试秘籍】可以简信我【学习】查看免费领取方式!

    原文链接:https://juejin.im/post/6859911468536561671

    相关文章

      网友评论

        本文标题:OkHttp 手把手教你网络编程

        本文链接:https://www.haomeiwen.com/subject/xldejktx.html