okhttp搭建服务器
‘壹’ OkHttp源码解析 (三)——代理和路由
初看OkHttp源码,由于对Address、Route、Proxy、ProxySelector、RouteSelector等理解不够,读源码非常吃力,看了几遍依然对于寻找复用连接、创建连接、连接服务器、连接代理服务器、创建隧道连接等逻辑似懂非懂,本篇决定梳理一遍相关的概念及基本原理。
● HTTP/1.1(HTTPS)
● HTTP/2
● SPDY
一个http请求的流程(直连):
1、输入url及参数;
2、如果是url是域名则解析ip地址,可能对应多个ip,如果没有指定端口,则用默认端口,http请求用80;
3、创建socket,根据ip和端口连接服务器(socket内部会完成3次TCP握手);
4、socket成功连接后,发送http报文数据。
一个https请求的流程(直连):
1、输入url及参数;
2、如果是url是域名则解析ip地址,可能对应多个ip,如果没有指定端口,则用默认端口,https请求用443;
3、创建socket,根据ip和端口连接服务器(socket内部会完成3次TCP握手);
4、socket成功连接后进行TLS握手,可通过java标准款提供的SSLSocket完成;
5、握手成功后,发送https报文数据。
1、分类
● HTTP代理:普通代理、隧道代理
● SOCKS代理:SOCKS4、SOCKS5
2、HTTP代理分类及说明
普通代理
HTTP/1.1 协议的第一部分。其代理过程为:
● client 请求 proxy
● proxy 解析请求获取 origin server 地址
● proxy 向 origin server 转发请求
● proxy 接收 origin server 的响应
● proxy 向 client 转发响应
其中proxy获取目的服务器地址的标准方法是解析 request line 里的 request-URL。因为proxy需要解析报文,因此普通代理无法适用于https,因为报文都是加密的。
隧道代理
通过 Web 代理服务器用隧道方式传输基于 TCP 的协议。
请求包括两个阶段,一是连接(隧道)建立阶段,二是数据通信(请求响应)阶段,数据通信是基于 TCP packet ,代理服务器不会对请求及响应的报文作任何的处理,都是原封不动的转发,因此可以代理 HTTPS请求和响应。
代理过程为:
● client 向 proxy 发送 CONNET 请求(包含了 origin server 的地址)
● proxy 与 origin server 建立 TCP 连接
● proxy 向 client 发送响应
● client 向 proxy 发送请求,proxy 原封不动向 origin server 转发请求,请求数据不做任何封装,为原生 TCP packet.
3、SOCKS代理分类及说明
● SOCKS4:只支持TCP协议(即传输控制协议)
● SOCKS5: 既支持TCP协议又支持UDP协议(即用户数据包协议),还支持各种身份验证机制、服务器端域名解析等。
SOCK4能做到的SOCKS5都可得到,但反过来却不行,比如我们常用的聊天工具QQ在使用代理时就要求用SOCKS5代理,因为它需要使用UDP协议来传输数据。
有了上面的基础知识,下面分析结合源码分析OkHttp路由相关的逻辑。OkHttp用Address来描述与目标服务器建立连接的配置信息,但请求输入的可能是域名,一个域名可能对于多个ip,真正建立连接是其中一个ip,另外,如果设置了代理,客户端是与代理服务器建立直接连接,而不是目标服务器,代理又可能是域名,可能对应多个ip。因此,这里用Route来描述最终选择的路由,即客户端与哪个ip建立连接,是代理还是直连。下面对比下Address及Route的属性,及路由选择器RouteSelector。
描述与目标服务器建立连接所需要的配置信息,包括目标主机名、端口、dns,SocketFactory,如果是https请求,包括TLS相关的SSLSocketFactory 、HostnameVerifier 、CertificatePinner,代理服务器信息Proxy 、ProxySelector 。
Route提供了真正连接服务器所需要的动态信息,明确需要连接的服务器IP地址及代理服务器,一个Address可能会有很多个路由Route供选择(一个DNS对应对个IP)。
Address和Route都是数据对象,没有提供操作方法,OkHttp另外定义了RouteSelector来完成选择的路由的操作。
1、读取代理配置信息:resetNextProxy()
读取代理配置:
● 如果有指定代理(不读取系统配置,在OkHttpClient实例中指定),则只用1个该指定代理;
● 如果没有指定,则读取系统配置的,可能有多个。
2、获取需要尝试的socket地址(目标服务器或者代理服务器):resetNextInetSocketAddress()
结合Address的host和代理,解析要尝试的套接字地址(ip+端口)列表:
● 直连或者SOCK代理, 则用目标服务器的主机名和端口,如果是HTTP代理,则用代理服务器的主机名和端口;
● 如果是SOCK代理,根据目标服务器主机名和端口号创建未解析的套接字地址,列表只有1个地址;
● 如果是直连或HTTP代理,先DNS解析,得到InetAddress列表(没有端口),再创建InetSocketAddress列表(带上端口),InetSocketAddress与InetAddress的区别是前者带端口信息。
3、获取路由列表:next()
选择路由的流程解析:
● 遍历每个代理对象,可能多个,直连的代理对象为Proxy.DIRECT(实际是没有中间代理的);
● 对每个代理获取套接字地址列表;
● 遍历地址列表,创建Route,判断Route如果在路由黑名单中,则添加到失败路由列表,不在黑名单中则添加到待返回的Route列表;
● 如果最后待返回的Route列表为空,即可能所有路由都在黑名单中,实在没有新路由了,则将失败的路由集合返回;
● 传入Route列表创建Selection对象,对象比较简单,就是一个目标路由集合,及读取方法。
为了避免不必要的尝试,OkHttp会把连接失败的路由加入到黑名单中,由RouteDatabase管理,该类比较简单,就是一个失败路由集合。
1、创建Address
Address的创建在RetryAndFollowUpInteceptor里,每次请求会声明一个新的Address及StreamAllocation对象,而StreamAllocation使用Address创建RouteSelector对象,在连接时RouteSelector确定请求的路由。
每个Requst都会构造一个Address对象,构造好了Address对象只是有了与服务器连接的配置信息,但没有确定最终服务器的ip,也没有确定连接的路由。
2、创建RouteSelector
在StreamAllocation声明的同时会声明路由选择器RouteSelector,为一次请求寻找路由。
3、选择可用的路由Route
下面在测试过程跟踪实例对象来理解,分别测试直连和HTTP代理HTTP2请求路由的选择过程:
● 直连请求流程
● HTTP代理HTTPS流程
请求url: https://www.jianshu.com/p/63ba15d8877a
1、构造address对象
2、读取代理配置:resetNextProxy
3、解析目标服务器套接字地址:resetNextInetSocketAddress
4、选择Route创建RealConnection
5、确定协议
测试方法:
● 在PC端打开Charles,设置端口,如何设置代理,网上有教程,比较简单;
● 手机打开WIFI,选择连接的WIFI修改网络,在高级选项中设置中指定了代理服务器,ip为PC的ip,端口是Charles刚设置的端口;
● OkHttpClient不指定代理,发起请求。
1、构造address对象
2、读取代理配置:resetNextProxy
3、解析目标服务器套接字地址:resetNextInetSocketAddress
4、选择Route创建RealConnection
5、创建隧道
由于是代理https请求,需要用到隧道代理。
从图可以看出,建立隧道其实是发送CONNECT请求,header包括字段Proxy-Connection,目标主机名,请求内容类似:
6、确定协议,SSL握手
1、代理可分为HTTP代理和SOCK代理;
2、HTTP代理又分为普通代理和隧道代理;普通代理适合明文传输,即http请求;隧道代理仅转发TCP包,适合加密传输,即https/http2;
3、SOCK代理又分为SOCK4和SOCK5,区别是后者支持UDP传输,适合代理聊天工具如QQ;
4、没有设置代理(OkHttpClient没有指定同时系统也没有设置),客户端直接与目标服务器建立TCP连接;
5、设置了代理,代理http请求时,客户端与代理服务器建立TCP连接,如果代理服务器是域名,则解释代理服务器域名,而目标服务器的域名由代理服务器解析;
6、设置了代理,代理https/http2请求时,客户端与代理服务器建立TCP连接,发送CONNECT请求与代理服务器建立隧道,并进行SSL握手,代理服务器不解析数据,仅转发TCP数据包。
如何正确使用 HTTP proxy
OkHttp3中的代理与路由
HTTP 代理原理及实现(一)
‘贰’ OkHttp3-连接(Connections)
虽然通常你只需要提供一个 URL 给OkHttp,OkHttp就可以帮你完成其他事情。但是实际上OkHttp连接服务器需要三个条件:蔽圆 URL , Address , Route 。
提供一烂并绝个 URL (比如 https://github.com/square/okhttp )让Http去连接服务器是最基本的工作。还有一种渐渐普遍的文件定位方式称为 URN(同意名称定位符) ,它是利用一种分散式的命名方案去指定所需要访问的资源文件。
URLs 是抽象的:
URL 是具体的:
Address 在OKHttp中是一个对象,它为OkHttp提供静态配置!
地址指定了一个服务器(比如 github.com )以及连接此服务器所需要的静态配置:端口号, HTTPS 设置,以及指定的网络协议(比如 HTTP/2 或者 SPDY )。
相同地址的 URL 也可以共用相同的底层 TCP Socket 连接。共用相同的连接对于性能有很大的提升:更低的延迟,更大的吞吐量(复用连接,由于每个 TCP 启动的都需要较多的准备工作),更少的电能损耗。OkHttp使用一个连接池,来自动复用 HTTP/饥姿1.x connections 、 HTTP/2 、 SPDY 连接。
URL为地址提供了一些字段(比如域名、主机名、端口号),其他的字段都来自于 OkHttpClient 。
Routes 在OKHttp中是一个对象,它为OkHttp提供动态配置!
路由提供了实际连接到服务器所需要的动态配置。比如所指定用来尝试连接服务器的 IP 地址(从 DNS 服务商获得)、连接过程中实际所使用到的代理服务器(如果使用了 ProxySelector ),以及使用的是哪个版本的
TLS 协议(当使用 Https 协议连接时候需要)。
对于一个地址来说 ,可能存在有很多种路由的方式。比如,当一个服务器被托管在多个数据中心,这时路由从 DNS 供应商获取的响应中就可以获取到多个 IP 地址。
当你使用OkHttp去请求一个 URL 时,OkHttp为你做了如下事情:
如果在连接的过程中出现问题,那么OkHttp将会选择其他的路由进行重新连接。这意味着当一个服务器的某一个 IP 地址无法访问时,OkHttp可以尝试别的 IP 地址进行访问。或者当一个连接池过期或者你尝试连接的所用的 TLS 版本不受服务器支持时,这种重连机制也是非常有用的。
一旦客户端发来接收到来自服务器的响应,那么这个 Connection 将会被放置到连接池中以备于将来新的连接进行复用。 Connection 在长期不使用的情况下,将会从这个连接池中被移除。
··
‘叁’ webservice是基于soap协议的,在android端可以用okhttp建立连接吗
调用几主要:
1.创建HttpTransportSE传输象:HttpTransportSE ht = new HttpTransportSE(SERVICE_URL); SERVICE_URLwebservice提供服务url
2.使用SOAP1.1协议创建Envelop象:SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11); 设置SOAP协议版本号根据服务端WebService版本号设置
3.实例化SoapObject象:SoapObject soapObject = new SoapObject(SERVICE_NAMESPACE, methodName); 第参数表示WebService命名空间WSDL文档找WebService命名空间第二参数表示要调用WebService名
4.设置调用参数值没参数省略:例soapObject.addProperty("theCityCode", cityName);
5.记设置bodyout属性 envelope.bodyOut = soapObject;
6.调用webservice:ht.call(SERVICE_NAMESPACE+methodName, envelope);
7.获取服务器响应返SOAP消息:
SoapObject result = (SoapObject) envelope.bodyIn;
SoapObject detail = (SoapObject) result.getProperty(methodName+"Result");
‘肆’ OkHttp完全解析(一)
以 3.14.9版本为例,这应该是最后一个java版本了,后面的版本都是kotlin开发的。
implementation 'com.squareup.okhttp3:okhttp:3.14.9'
一个完整的url可蠢族以包含哪些内容?
看一下这个类的构造方法
前面几个数据拆解了url中的不同部分,包括:协议、用户名、密码、域名、端口、路径、参数、锚位。
builder.toString() 将各个部分组合为一个整体,如下:
http://username:[email protected]:80/path1/path2?Key1=value1&key2=value2#anchor
域名查询服务,通过域名获取锋档培主机IP地址。
InetAddress 表示一个IP地址,这个类没有开放的构造方法,必须使用getXX静态方法创建。
套接字工厂,SSL套接字工厂, java SDK 内容。
抢先验证和反应式验证
抢先验证:用http代理服务器实现https请求时,需要进行抢先验证。
反应式验证:服务端反馈401或407时,需要提交账号信息用于验证。
401: 用户没有[访问权限, 需要进行身份认证。
407:客户应首先通过代理服务器验证。
OkHttpClient 包含两个实例用于验证,分别为authenticator用于验证原始服务器账号、proxyAuthenticator 用于验证代理服务器账号。
这是个枚举类, 包含以下数据
http/1.0 默认情况下不使用持久套接字,明文请求,过时
http/1.1 包含持久连接,明文请求
spdy/3.1 OkHttp不再支持该协议,使用http2.0
h2 就是http2.0,支持请求头压缩、多路复用、服务推送。
h2_prior_knowledge 明文http2.0
quic 快速udp网络连接,okhttp不支持,但可以通过拦截器实现支持。
连接规定。
指定ssl的加密算法 - 或不指定加密算法。
加密算法
这个类里面只有一个算法的名称,没有具体的算法实现逻辑。
类会缓存算法名称对应的CipherSuite对象
TLS的版本号
代理服务器设置和代理服务器选择器,java SDK内容。
参考: https://blog.csdn.net/qq_33022345/article/details/53453585
锁定SSL证书。
给指定的域名提供固定的证书,那该域名只有使用这个固定证书时才能正常访问。如果服务端证书改变,将无法访问。
一个服务器地址的表示银唯。
本文以上提到的所有类,都被用于Address类中。
‘伍’ Android通过OKhttp从服务器端获取数据
①简单的异步Get请求
第一步,创建OKHttpClient对象
第二步,创建Request请求
第三步,创建一个Call对象
第四步,将请求添加到调度中
不多说,直接上代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//okHttp的基本使用 --- get方法
String url = "https://api.douban.com/v2/movie/top250?start=0&count=10";
//1,创建OKHttpClient对象
OkHttpClient mOkHttpClient = new OkHttpClient();
//2,创建一个Request
Request request = new Request.Builder().url(url).build();
//3,创建一个call对象
Call call = mOkHttpClient.newCall(request);
//4,将请求添加到调度中
call.enqueue(new Callback() {
@Override
public void onFailure(Request request, IOException e) {
}
@Override
public void onResponse(Response response) throws IOException {
if (response.isSuccessful()) {
final String message = response.body().string();
handler.post(new Runnable() {
@Override
public void run() {
tv_message.setText(message);
progressBar.setVisibility(View.GONE);
}
});
}
}
});
‘陆’ Okhttp3源码分析
在OkHttp3中,其灵活性很大程度上体现在可以 intercept 其任意一个环节,而这个优势便是okhttp3整个岁春请求响应架构体系的精髓所在,先放出一张主框架请求流程图,接着再分析源码。
这大概是一个最简单的一个例子了,在 new OkHttpClient() 内部使用构造器模式初始化了一些配置信息:支持协议、任务分发器(其内部包含一个线程池,执行异步请求)、连接池(其内部包含一个线程池,维护connection)、连接/读/写超时时长等信息。
第一行创建了一个 Dispatcher 任务调度器,它定义了三个双向任务队列,两个异步队列:准备执行的请求队列 readyAsyncCalls 、正在运行的请求队列 runningAsyncCalls ;一个正在运行的同步请求队列 runningSyncCalls ;
另外还有一个线程池 executorService ,这个线程池跟Android中的CachedThreadPool非常类似,这种类型的线程池,适用于大量的耗时较短的异步任务。 下一篇文章 将对OkHttp框架中的线程池做一个总结。
接下来接着看Request的构造,这个例子Request比较简单,指定了请求方式 GET 和请求 url
紧接着通过 OkHttpClient 和 Request 构造一个 Call 对象,它的实现是 RealCall
可以看到在 RealCall 的构造方法中创建了一个 RetryAndFollowUpInterceptor ,用于处理请求错误和重定向等,这是 Okhttp 框架的精髓 interceptor chain 中的一环,默认情况下也是第一个拦截器,除非调用 OkHttpClient.Builder#addInterceptor(Interceptor) 来添加全局的拦截器。关于拦截器链的顺序参见 RealCall#() 方法。
可以看到,一个 Call 只能执行一次,否宏祥则会抛异常,这里创建了一个 AsyncCall 并将Callback传入,接着再交给任务分发器 Dispatcher 来进一步处理。
从 Dispatcher#enqueue() 方法的策略可以看出,对于请求的入队做了一些限制,若正在执行的请求数量小于最大值(默认64),并且此请求所属主机的正在执行任务小于最大值(默认5),就加入正在运行的队列并通过线程池来执行该任务,否则加入准备执行队列中。
现在回头看看 AsyncCall ,它继承自 NamedRunnable ,而 NamedRunnable 实现了 Runnable 接口,它的作用有2个:
①采用模板方法的设计模式,让子类将具体的操作放在 execute() 方法中;
②给线程指定一个名字,比如传入模块名称,方便监控线程的活动状态;
先看注释②的行finally块中执行的 client.dispatcher().finished(this)
其中 promoteCalls() 为推动下一个任务执行,其实它做的也很简单,就是在条件满足的情况下,将 readyAsyncCalls 中的任务移动到 runningAsyncCalls 中,并交给线程池来蔽雀搏执行,以下是它的实现。
接下来就回到注释①处的响应内容的获取 ()
可以看这块重点就是 interceptors 这个集合,首先将前面的 client.interceptors() 全部加入其中,还有在创建 RealCall 时的 retryAndFollowUpInterceptor 加入其中,接着还创建并添加了 BridgeInterceptor、CacheInterceptor、ConnectInterceptor、CallServerInterceptor ,最后通过 RealInterceptorChain#proceed(Request) 来执行整个 interceptor chain ,可见把这个拦截器链搞清楚,整体流程也就明朗了。
从这段实现可以看出,是按照添加到 interceptors 集合的顺序,逐个往下调用拦截器的intercept()方法,所以在前面的拦截器会先被调用。这个例子中自然就是 RetryAndFollowUpInterceptor 了。
这个拦截器就如同它的名字 retry and followUp ,主要负责错误处理和重定向等问题,比如路由错误、IO异常等。
接下来就到了 BridgeInterceptor#intercept() ,在这个拦截器中,添加了必要请求头信息,gzip处理等。
这个拦截器处理请求信息、cookie、gzip等,接着往下是 CacheInterceptor
这个拦截器主要工作是做做缓存处理,如果有有缓存并且缓存可用,那就使用缓存,否则进行调用下一个拦截器 ConnectionInterceptor 进行网络请求,并将响应内容缓存。
这个拦截器主要是打开一个到目标服务器的 connection 并调用下一个拦截器 CallServerInterceptor ,这是拦截器链最后一个拦截器,它向服务器发起真正的网络请求。
从上面的请求流程图可以看出,OkHttp的拦截器链可谓是其整个框架的精髓,用户可传入的 interceptor 分为两类:
①一类是全局的 interceptor,该类 interceptor 在整个拦截器链中最早被调用,通过 OkHttpClient.Builder#addInterceptor(Interceptor) 传入;
②另外一类是非网页请求的 interceptor ,这类拦截器只会在非网页请求中被调用,并且是在组装完请求之后,真正发起网络请求前被调用,所有的 interceptor 被保存在 List<Interceptor> interceptors 集合中,按照添加顺序来逐个调用,具体可参考 RealCall#() 方法。通过 OkHttpClient.Builder#addNetworkInterceptor(Interceptor) 传入;
相关阅读
‘柒’ OkHttp之连接
尽管你只提供了URL,OkHttp在规划连接服务器的连李乎卜接时使用了三种类型:URL,Address和Route。
URLs是HTTP和网络的最基本哪穗的。除了作为一个通用的,网络资源的分散命名机制,它们也规定了如何访问网络资源。
URLs是抽象的:
它们也是具体的:每一个URL确定一个特定路径(像/square/okhttp)和查询(像顷慎?q=sharks&lang=en)。每个服务器有很多URL。
Addresses规定了服务器(像github.com)和所有连接服务器需要的静态配置:端口号,HTTPS设置和优先网络协议(像HTTP/2或SPDY)。
共享相同address的URLs也可能共享相同的下层TCP socket连接。共享一个连接有巨大的性能好处:低延迟,高吞吐量(因为TCP启动慢)和节省电源。OkHttp使用ConnectionPool来自动复用HTTP/1.X连接和多路传输HTTP/2和SPDY连接。
在OkHttp中,address的一些字段来自URL(机制,主机名,端口),剩下的来自OkHttpClient。
Routes提供了真正连接到服务器所需要的动态信息。这是明确的要尝试的IP地址(通过DNS查询发现),明确的要使用的代理服务器(如果使用了ProxySelector)以及什么版本的TLS来协商(针对HTTPS连接)。
对于一个地址有可能有很多路由,一个存在多个数据中心的网络服务器可能在它的DNS响应中产生多个IP地址。
当你使用OkHttp请求一个URL时,下面是它所做的:
当连接出现问题时,OkHttp会选择另外一个route进行尝试。这使得OkHttp可以在服务器部分地址无法访问时恢复。它同时对于当连接池过期或尝试的TLS版本不支持时有用。
一旦接收到响应,连接就会返回到池中,这样它可以在之后的请求复用。连接空闲一段时间会从池中移除。
原文链接:
https://github.com/square/okhttp/wiki/Connections
OkHttp官方文档系列文章:
‘捌’ 安卓okhttp和通知创建方法好像啊
OkHttp是一种HTTP客户端,它可以用于创建和发送HTTP请求,以及接收和处理HTTP响应。它是用Java编写的,可以在Android和Java应用程序茄睁中轻松使用。OkHttp可以用于构建高性能、可靠的HTTP客户端,可以改善网络连接的速度和可靠性。OkHttp可以用来支持像HTTP/2和WebSocket这样的新协议,以及支持身份验证、GZIP压缩和缓存的一些高级功能。
通知是指一种从应用程序发出的消息,可以提示用户关于特定事件或状态的变化。它可以被显示在不同位置,以提醒用户注意,例如在状态栏、桌大配面小部件和应用程序内部。它们可以使用多种不同的格式,包括文本、图像、音频和视频。
通知的创建方法取决于你使用的开发框架,但基本流程滚纳指大致相同。首先,需要创建一个通知对象,并使用该对象中的方法配置它,例如设置标题、文本和图标。然后,需要使用推送服务将通知发送到指定的目标设备。最后,需要设置一个回调,以便在通知被接收和处理之后执行某些操作。
‘玖’ 网络请求框架-OkHttp原理解析
okhttp是square公司贡献的一个处理网络请求的开源框架,是目前Android开发使用最广泛的一个网络框架,从Android4.4开始,httpURLconnection的底层实现采用的就是okhttp。内部实现就是利用java基础,对socket进行封装,实现http通信。最重要的两个关键点就是分发器和5个拦截器。
分发器 就是内部维护队列和线程池,完成请求分配,总结就是用于对异步任务加入队列管理,然后判断条件,控制数量,加入线程池执行异步请求任务。
五个默认拦截器 就是利用责任链模式对网络请求进行层层处理,完成整个请求过程,简单总结如下。
1.桥接拦截器对用户发出的请求添加缺少的请求配置字段,比如keep-alive等
2.缓存拦截器就是查询有没有符合判断条件的已缓存的网络请求,执行复用,直接返回response
3.连接拦截器就是创建请求,加入连接器 或者访问连接池,根据条件判断,是否能怼已创建的tcp请求进行复用
4.请求服务器拦截器就是对scoket进行操作,请求网络访问服务器,返回response,
5.重试和重定向拦截器就是对返回的response进行code判断,决定是否要重试或者重定向操作。
1.支持http2.0版本,并且允许对同一主机的所有请求共享一个套接字
2.即使不是http2.0版本,通过连接池,减少请求延迟
3.默认使用Gzip 压缩数据
4.响应缓存,避免重复请求网络
最简单的http请求案例
1.利用建造者模式构建okHttpClient实例对象,构建过程中可以动态配置参数,请求时间,响应时间,缓存信息等。
2.创建Request对象,设置请求方式,链接地址,参数等信息。
3.把request对象,传给client,通过newCall函数,得到RealCall对象。
4.RealCall 分为同步和异步执行
5.同步执行时,分发器只是做个记录,把请求任务加到队列中,然后直接通过拦截器访问服务器,返回response。
6.异步执行
6.1先对异步任务进一步封装,把任务放到AsyncCall对象中
2.分发器 把 封装后的异步任务 添加到等待运行的队列中
7. 通过拦截器,获取response
okhttp 默认提供5个拦截器 重试重定向拦截器,桥接拦截器,缓存拦截器,连接拦截器,访问服务器拦截器。还可以自定义拦截器。
自定义拦截器分为应用拦截器(通过addInterceptor 添加)和网络拦截器(通过addNetworkInterceptor拦截)
拦截器采用责任链的设计默认,让请求者和处理者解耦,最终请求从前往后,响应从后往前。
首先先判断用户是否取消了请求,如果没有取消,就把请求交个桥接拦截器。
在获得响应结果response的时候根据响应码,判断是否需要重试或者重定向, 重试不限制次数,重定向最多20次 ,如果需要重试或者重定向,那么会再一次重新执行所有拦截器。
有如下几种情况不会重试:IO异常,线路异常,配置client实例时配置不允许重试,协议异常,证书异常等等。
先获取用户发送的请求,判断条件用户是否已经配置过请求头字段,若用户没有配置,则将http协议必备的请求头字段补齐,比如Content-Type,Content-Length等,然后交给下一个拦截器。
在获得响应结果response之后,调用保存cookie的接口(也可以在配置client的时候,设置cookjar进行cookie回调数据),并且解析gzip数据
获取结果之后,对cookie进行保存,对返回的数据进行gzip解压
就是根据缓存策略从缓存中查找是否有合适的缓存response,如果有合适的缓存,直接返回给请求任务,不在继续执行后面的拦截器。
获得响应结果response后,根据条件判断,决定是否要缓存。
维护一个连接池,负责对连接的服务。在把请求交给下一个拦截器之前。会先在连接池中找到一个合适的连接(满足适配条件相同,并且没有正在被使用)或者新建一个连接,并且接入连接池,获得对应的socket流,把请求交给下一个拦截器。获得response结果后不会进行额外的处理。
连接池, 也称之为对象池,主要用来存放request请求连接,内部维护了一个LinkedQueue队列用来存放请求。在添加新的请求对象时,都会执行一个周期性任务,用以对连接池进行清理操作。
1.队列长度超过5,清理最近未被使用连接,LRE算法
2.存储的连接,5分钟未被复用,清理
拿到上一个拦截器返回的请求,真正的与服务器进行通信,向服务器发送数据,解析读取响应的数据,返回给上一个拦截器。
1.创建request =>OkHttpClient=>RealCall()
2.同步执行 ,分发器添加同步任务,执行拦截器,访问服务器,返回reponse,触发异步分发流程。
3.异步执行 ,封装任务= >AsyncCall ,实现runnable接口。添加任务到异步任务等待队列,执行分发任务,判断异步任务是否能加入正在执行的异步任务队列,满足两个条件
同时执行的异步任务数量不得大于64个
对同一个主机的访问任务,最多不得大于5个
4.加入正在执行的异步任务队列,通过线程池执行任务,经过5个默认拦截器访问服务器,返回response,执行异步任务分发。
分发器工作 分为同步任务和异步任务两种
同步任务 就是把任务加入同步任务队列,加个标记,执行结束之后,触发异步任务的分发操作。
异步任务 先封装任务到asyncCall对象,实现了runnable接口。把任务加入等待执行队列,执行分发操作。
先遍历等待任务队列,判断是否符合加入正在运行的异步任务队列,要同时满足两个条件。
同时执行的异步任务数量不得大于64个
对同一个主机的访问任务,最多不得大于5个
当满足条件后,从等待队列中删除任务,把任务加入正在执行的队列中,通过自定义的线程池,执行任务,任务执行结束后,再次执行分发操作。
拦截器采用了责任链设计默认,让请求者和执行者解耦,请求者只需要将请求发给责任链即可,无需关心请求过程和细节。okHttp 默认有5个拦截器,重试重定向拦截器,桥接拦截器,缓存拦截器,连接拦截器,请求服务拦截器。工作细节参考上面拦截器原理分析部分
1.位置的关系,应用拦截器 放在责任链最顶端,网络拦截器放在责任链倒数第二的位置。所以应用拦截器 最先拦截,最后响应,网络拦截器 倒数第二拦截,第二响应。如果打印请求日志的情况,应用拦截器打印的是用户请求信息,经过重试重定向,桥接,缓存,链接 等拦截器的层层包装,网络拦截器打印的是实际请求的信息。
2.应用拦截器一定会被执行,网络拦截器不一定被执行。
利用连接池,缓存所有的有效连接对象。
清理机制:垃圾连接
1.超过5分钟没有用过的链接
2.超过5个闲置链接后,从最久闲置的链接开始执行清理(LRU)
‘拾’ 重识OkHttp——更深入了解如何使用
OkHttp作为square公司出品的一个网络请求框架,应该算是目前Android端最火爆的网络框架了。我公司目前的项目中采用的都是Rxjava结合Retrofit进行网络请求的处理,对于底层真正实现网络请求的OkHttp关注的不是很多。最近探究了一下OkHttp的源码,对OkHttp的使用有了一些新的认识,在此做一下总结。
OkHttp作为当前Android端最火热的网络请求框架,必然有很多的优点。
对于客户端来讲,我们关注的就是把正确的请求发送到服务端并拿到结果来进行处理。在OkHttp中,我认为可以分为3个部分:
OkHttp中通过建造者模式来构建OkHttpClient、Request和Response。对于客户端来讲,我们不需要过多关注Response是如何构建的,因为这个是OkHttp对响应结果进行了封装处理。我们只关注请求Request和客户端OkHttpClient如何构建即可。
Request采用建造者模式来配置url,请求方法method、header、tag和cacheControl。
OkHttp采用POST方法向服务器发送一个请求体,在OkHttp中这个请求体是RequestBody。这个请求体可以是:
RequestBody有几个静态方法用于创建不同类型的请求体:
最终都是相当于重写了RequestBody的两个抽象方法来写入流,如果传递流类型的参数,只要重写这两个抽象方法即可。
例如,我们提交一个String:
提交File:
提交流:
对于提交表单和分块请求,OkHttp提供了两个RequestBody的子类, FormBody 和 MultipartBody
FormBody也是采用建造者模式, 这个很简单,添加key-value形式的键值对即可。
添加键值对有两个方法:
例如:
MultipartBody也是采用建造者模式,MultipartBody.Builder可以构建兼容Html文件上传表单的复杂请求体。每一部分的多块请求体都是它自身的请求体,并且可以定义它自己的请求头。如果存在的话,这些请求头用来描述这部分的请求体。例如Content-Disposition、Content-Length 和 Content-Type如果可用就会被自动添加到头。
MIME类型有:
有几个主要的方法:
例如提交一个图片文件:
OkHttpClient采用建造者模式,通过Builder可以配置连接超时时间、读写时间,是否缓存、是否重连,还可以设置各种拦截器interceptor等。
建议在一个App中,OkHttpClient保持一个实例。一个OkHttpClient支持一定数量的并发,请求同一个主机最大并发是5,所有的并发最大是64。这个与OkHttp中的调度器Dispatcher有关,可以设置并发数。本文不对Dispatcher进行讨论。
一个例子:
OkHttpClient支持单独配置,例如原来设置不同的请求时间,可以通过OkHttpClient的newBuilder()方法来重新构造一个OkHttpClient。例如:
上面已经讲了如何创建Request和OkHttpClient,剩下的就是发送请求并得到服务器的响应了。OkHttp发送请求可分为同步和异步。OkHttpClient首先通过Request构建一个Call,通过这个Call去执行同步或者异步请求。
同步方式,调用Call的execute()方法,返回Response,会阻塞当前线程:
异步方式,调用Call的enqueue(CallBack callBack)方法,会在另一个线程中返回结果。
为了缓存响应,需要一个可读写并且设置大小Size的缓存目录。缓存目录需要私有,其它不信任的应用不能访问这个文件。
如果同时有多个缓存访问同一个缓存目录会报错。所以最好只在App中初始化一次OkHttpClient,给这个实例配置缓存,在整个App生命周期内都用这一个缓存。否则几个缓存会相互影响,导致缓存出错,引起程序崩溃。
响应缓存采用Http头来配置,你可以添加这样的请求头 Cache-Control: max-stale=3600 。 max-age 指的是客户端可以接收生存期不大于指定时间(以 秒 为单位)的响应。
为了防止响应使用缓存,可以用 CacheControl.FORCE_NETWORK 。为了防止使用网络,采用 CacheControl.FORCE_CACHE 。
调用Call.cancel()方法可以立即取消一个网络请求。如果当前线程正在写request或者读response会报IO异常。如果不再需要网络请求,采用这种方法是比较方便的。例如在App中返回了上一页。无论是同步还是异步的请求都可以被取消。
可以通过Response的code来判断请求是否成功,如果服务器返回的有数据,可以通过Response的body得到一个ResponseBody读取。
如果采用ResponseBody的string()方法会一次性把数据读取到内存中,如果数据超过1MB可能会报内存溢出,所以对于超过1MB的数据,建议采用流的方式去读取,如ResponseBody的byteStream()方法。
需要说明的是:
OkHttp中的很多类都用到了建造者模式,可以根据需要灵活配置。采用建造者模式的有:
如果单独使用OkHttp进行网络请求,通常需要开发者自己再封装一下,如果不想重复造轮子,Github上面的有一些优秀开源库可以拿来使用(本文只列出star较多的几个):
OkHttp官方Wiki文档