[源码]OKHttp及Http协议笔记

46 篇文章 0 订阅
40 篇文章 0 订阅

目的有二:学习一下著名开源项目的架构;大致了解一下http

硬知识

  • Cache-Control:RFC
    • 可以放在Cache-Control(http1.1以后)下,也可以放到Pragma(兼容http1.0)下
    • 可能会有多个
    • max-age:>0则使用缓存,否则请求服务端确认是否使用缓存
    • max-stale:请求时,能容许的超过freshness的最长时长,也就是接受服务端认为超时的缓存的时间
    • s-maxage:用于共享缓存(CDN),语义同max-age
    • min-fresh:请求时,仅接受min-fresh时间后还未过期的数据
    • no-cache:请求时,服务端评估缓存结果;返回时,服务端返回的结果必须经过服务端评估才能再次使用,返回的结果可以针对性的指定header的某些field不可重用
    • no-store:不缓存
    • no-transform:中间人不能修改数据
    • only-if-cached:请求只接受缓存的结果
    • must-revalidate:缓存超时后,一定要去服务端校验一次
    • private:仅返回用,私有缓存,仅存在对应用户名下的cache,不能存在share cache
    • public:使用share cache存储,细节比较多,还是看rfc吧

OkHttp功能

也可以说是Http1.1+WebDAV支持的功能(请求的路径):

  • Address:
    • 选择使用的协议——Protocol
    • 连接配置——ConnectionSpec(看起来是配置tls的)
    • DNS——Dns接口,可以替换
    • SSL——SSLSocketFactory和HostnameVerifier(Java rt中)、Certificate Pinning
    • 请求校验——Authenticator,对于407的返回码,为请求添加Authorization Header
    • Proxy和选择器——Proxy、ProxySelector(java.net)
  • Request:RFC
    • Methods:
      • GET:不能有request body
      • HEAD:不能有request body,服务端只返回header数据
      • POST:必须有request body,除非有明确的cache信息,否则不cache
      • PUT:必须有request body,替换服务端对应的数据
      • DELETE:可以有request body,删除服务端对应的数据
      • CONNECT:不能有request body,开启一个到对应端口的数据通道
      • OPTIONS:可以有request body,获得服务端支持的请求参数
      • TRACE:不能有request body,就是trace route的http版
      • WebDAV:一个对http1.1方法类型的扩展
    • Header:因为类型很多且可扩展,所以OkHttp直接暴露了kv接口,没有setter/builder
      • 控制类:
        • Cache-Control,见上
        • Expect:仅支持100-continue,如果服务端不能满足该行为,直接返回417
        • Host:目标地址
        • Max-Forwards:针对TRACE和OPTIONS方法,限制传播数量
        • Pragma:兼容1.0的Cache-Control
        • Range:指定希望获得的数据块位置,在多线程下载、断点续传时用
        • TE:除了chuncked之外额外支持的编码
      • 判断类:
        • If-Match:数据是否对应,必须使用强校验,内容是服务端和客户端的私有协议。主要用在改服务端数据
        • If-None-Match:数据是否不存在,必须使用弱校验。主要用来更新cache
        • If-Modified-Since:更新cache或减少Proxy转发次数
        • If-Unmodified-Since:更新服务端,同If-Match
      • 内容类型类,都可以用 q=小数 来说明对于指定类型的偏好程度(这个q还是很有借鉴意义的):
        • Accept:接受的媒体类型
        • Accept-Charset
        • Accept-Encoding
        • Accept-Language
      • 内容类:
        • From:来自的人
        • Referer:来自的URI
        • User-Agent:设备信息
  • Response:
    • Code(终于知道在哪找了):
      • 1XX:信息类
        • 100 Continue:继续发送下一步请求
        • 101 Switching Protocols
      • 2XX:成功类
        • 200 OK
        • 201 Created:满足客户端要求的资源已经被创建了
        • 202 Accepted
        • 203 Non-Authoritative Information:Proxy已经把原服务端的数据修改了
        • 204 No Content
        • 205 Reset Content:重置客户端的输入内容
      • 3XX:重定向
        • 300 Multiple Choices
        • 301 Moved Permanently
        • 302 Found:临时修改位置
        • 303 See Other:指定到另一个资源,可以换Method
        • 307 Temporary Redirect:跟302差不多,但是不能换Method
      • 4XX:客户端错误
        • 400 Bad Request
        • 402 Payment Required
        • 403 Forbidden
        • 404 Not Found
        • 405 Method Not Allowed
        • 406 Not Acceptable:user-agent跟资源八字不合
        • 408 Request Timeout
        • 409 Conflict:资源状态不相符
        • 410 Gone:永久性的404
        • 411 Length Required
        • 413 Payload Too Large
        • 414 URI Too Long
        • 415 Unsupported Media Type
        • 417 Expectation Failed
        • 426 Upgrade Required:必须升级协议
      • 5XX:服务端错误
        • 500 Internal Server Error
        • 501 Not Implemented
        • 502 Bad Gateway:代理服务器返回,表明被代理服务器坏掉了
        • 503 Service Unavailable:临时性的501
        • 504 Gateway Timeout
        • 505 HTTP Version Not Supported
    • Header
      • 控制类
        • Age:中间人返回的结果,代表结果的年龄
        • Cache-Control
        • Expires:结果的过期日期
        • Warning:返回码不能表征的细节消息,也是3位错误码

OkHttp请求流程(仅以RealCall.execute为例,应该是覆盖了全部http流程)

  1. getResponseWithInterceptorChain():使用Interceptor修改Request。与Http相关的是Gzip压缩request body
  2. getResponse():填入内容类型类请求头。因为可能有gzip,所以这一步要在修改response的最后一步
  3. getResponse():初始化HttpEngine,其中,会根据request,生成一个地址对象,并使用连接池初始化一个StreamAllocation(细节见后)
  4. getResponse():while循环,开始处理包含的子请求(返回码)
    1. HttpEngine.networkRequest():填入连接相关的数据,不可cookie(可以用java的CookieHandler)
    2. 查看Cache的response是否可用(使用Request和Response里的判断类、控制类和Cache-Control信息)
    3. 开始连接
    4. 向流中写入Request Header[和body]
    5. 读取数据
    6. 看是否有Connection字段是否为close,关闭连接
    7. 特殊处理204/205,返回有数据就报错
    8. 更新cookie
    9. 合并或者更新cache
  5. 检查有没有后续请求(401/407/408/3XX)
  6. 检查重定向次数是否过多
  7. [重新开始新子请求]或[返回response]

StreamAllocation

  • 区分Http2和1.1的标志是是否支持长连接
  • 关于http2第一个答案

合理设计

  • 使用Builder把成员变量的setter从复杂的逻辑对象里剥离出来,让结构清晰一些,也做到了对象的immutable——OkHttpClient.Builder
    但是,可能有个FieldWrapper更加方便:Buildee中需要Builder配置的所有Field都放到FieldWrapper中。Builder在构造函数中new一个FieldWrapper,在build时,把FieldWrapper直接作为成员变量赋值给Buildee,这样可以做到单点修改
  • 使用Interceptor和InterceptorChain方式,层层过滤、修改请求和结果,基本做到了开闭。但是cache等功能其实也可以使用Interceptor来做,现在直接用的直接调用代码
  • 用了很多ActiveObject模式,其实大部分操作都是靠request、RealCall这些对象直接execute实现的
  • 极完整的隔离了数据链路层,HttpStream
  • 分层,对每层的接口都有针对性的mock和测试用例
  • 对外提供唯一功能接口对象,该对象以context形式对内提供所有相关功能
  • 以功能分类,有一种把函数封装成对象的既视感,其实就是把一个参数、逻辑极为复杂的函数封装成了一个对象,参数都靠成员变量,流程控制也更加灵活。保证了相关逻辑完全在同一个类中管控:
    • Dispatcher:所有跟任务调度和记录相关
    • OkHttpClient:对外接口
    • RealCall:请求所有逻辑
    • HttpEngine:一次请求的所有逻辑
  • 为用途不可预测的对象加一个Object的tag,方便扩展——Request、Android中的View
  • StreamAllocation作为一个中台,协调Connection、Stream和Call

技巧

  • 用空interface来进行类型区分——Collections.unmodifiableList——RandomAccess接口
  • 统一对外接口(OkHttpClient)不是单例,保证了调用方可以方便的扩展出多个不同的请求方法,包括分级qos等
  • HttpEngine每次子请求发起前会查看是否cancel。实现多次不可中断操作构成的大操作的中断功能,大概都是这个思路

可能的问题

  • static的field比较多,而且都是构造出来的对象,会不会在cinit的时候有性能问题
  • Interceptor.Chain的默认实现,会对每个Interceptor new出一个新的Chain出来,没有get到好处。似乎是为了保证请求间的数据(Interceptor的成员变量)不会相互影响。但是,方法的局部变量也可以做到啊。。。
  • 对于HttpEngine,一个engine仅对应着一次request,每redirect一次都会new出来一个。似乎开发者很喜欢这个用法
  • 看起来okHttp对内存应该是各种不在乎的
  • strategy似乎用的不太完美,对于webSocket各种if做兼容
  • 对于enqueue的AsyncCall,没有找到调用Dispatcher.finished的地方。。。略尴尬。。。这些逻辑暴露出来也不太好
  • OkHttpClient不是单例,但是每个client都会覆盖Internal的单例,可能有坑啊
  • 貌似http2使用了一个单独的线程搞的数据传输,而请求只是在等待,没看太懂
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值