• OkHttp框架设计<一>---http家族史、OkHttp源码分析、拦截器原理


    关于Okhttp在之前有过一篇https://www.cnblogs.com/webor2006/p/10513950.html源码的解读,这里准备再对它进行温故知新,并最终手写整个OkHttp拦截链这块的逻辑,巩固再巩固。

    http家族史【了解】:

    先来巩固下基础,毕境OkHttp是一个网络框架。

    网络分成模型:

    上面了解既可,关于网络分成的一个原因之前在这篇有写过:https://www.cnblogs.com/webor2006/p/10362197.html

    OSI各层解释:

    各层对应的设备:

    各层对应协议:

    这个图还是有点用,能够清楚知道我们常见的一些协议是处于哪一层的。

    TCP/IP 三次握手和四个挥手:

    这个有时可能面试会问到,了解一下。

    HTTP 1.1:

    建立在TCP协议之上的”超文本传输协议”(HyperText Transfer Protocol),关于这块的之前也已经研究过了:https://www.cnblogs.com/webor2006/p/10324182.html

    HTTPS:

    HTTP1.x在传输数据时,所有传输的内容都是明文,无法保证数据的安全性。
    网景在1994年创建了HTTPS,HTTPS就是安全版的HTTP。

    在通讯时多了一个SSL握手的过程:

    具体握手过程大致过程如下:

    具体整个Https的通信原理之前也有研究过:https://www.cnblogs.com/webor2006/p/10362197.html 

    当然对于Https的掌握肯定不是这么简单的,待之后再找个时间深入研究一下,这块在面试时也偶尔会被问到的。

    OkHttp源码再次梳理:

    接下来则正式入进OkHttp的探究。

    OkHttp版本选择:

    先上官网瞅一下如今最新的版本:

    但是!!这次分析肯定不是基于最新版本,为啥?

    不过从侧面来看Kotlin这门语言真的是越来越重要了,所以搞Android的学好Koltin势在必得,那学习选哪个版本呢?

    当然是选3.x这个版本喽,这里选择3.14.2这个版本。

    OkHttp简单使用:

    在正式源码分析之前,还是回顾一下它的简单使用,分为同步和异步,人人皆知的事,就不多说了,直接上代码:

    异步使用:

    其简单使用流程如下:

    这块简单过一下,接下来则重点就是分析它的源码了。

    主流程源码梳理:

    初始化OkHttpClient:

    先来分析同步的情况:

    我们知道它里面用了经典的构建者模式,这里在默认构中实例化了Builder,看后瞅一下这个Builder构造里面做了啥?

    最终再将此Builder中的数据再一一赋值给当前类的成员变量,如下:

    其中这个Builder中定义了很多的参数,下面就不一一看了,以代码注释的方式贴出来供没事回来复习用:

    public static final class Builder {
            Dispatcher dispatcher; //调度器
            /**
             * 代理类,默认有三种代理模式DIRECT(直连),HTTP(http代理),SOCKS(socks代理)
             */
            @Nullable Proxy proxy;
            /**
             * 协议集合,协议类,用来表示使用的协议版本,比如`http/1.0,`http/1.1,`spdy/3.1,`h2等
             */
            List<Protocol> protocols;
            /**
             * 连接规范,用于配置Socket连接层。对于HTTPS,还能配置安全传输层协议(TLS)版本和密码套件
             */
            List<ConnectionSpec> connectionSpecs;
            //拦截器,可以监听、重写和重试请求等
            final List<Interceptor> interceptors = new ArrayList<>();
            final List<Interceptor> networkInterceptors = new ArrayList<>();
            EventListener.Factory eventListenerFactory;
            /**
             * 代理选择类,默认不使用代理,即使用直连方式,当然,我们可以自定义配置,
             * 以指定URI使用某种代理,类似代理软件的PAC功能
             */
            ProxySelector proxySelector;
            //Cookie的保存获取
            CookieJar cookieJar;
            /**
             * 缓存类,内部使用了DiskLruCache来进行管理缓存,匹配缓存的机制不仅仅是根据url,
             * 而且会根据请求方法和请求头来验证是否可以响应缓存。此外,仅支持GET请求的缓存
             */
            @Nullable Cache cache;
            //内置缓存
            @Nullable InternalCache internalCache;
            //Socket的抽象创建工厂,通过createSocket来创建Socket
            SocketFactory socketFactory;
            /**
             * 安全套接层工厂,HTTPS相关,用于创建SSLSocket。一般配置HTTPS证书信任问题都需要从这里着手。
             * 对于不受信任的证书一般会提示
             * javax.net.ssl.SSLHandshakeException异常。
             */
            @Nullable SSLSocketFactory sslSocketFactory;
            /**
             * 证书链清洁器,HTTPS相关,用于从[Java]的TLS API构建的原始数组中统计有效的证书链,
             * 然后清除跟TLS握手不相关的证书,提取可信任的证书以便可以受益于证书锁机制。
             */
            @Nullable CertificateChainCleaner certificateChainCleaner;
            /**
             * 主机名验证器,与HTTPS中的SSL相关,当握手时如果URL的主机名
             * 不是可识别的主机,就会要求进行主机名验证
             */
            HostnameVerifier hostnameVerifier;
            /**
             * 证书锁,HTTPS相关,用于约束哪些证书可以被信任,可以防止一些已知或未知
             * 的中间证书机构带来的攻击行为。如果所有证书都不被信任将抛出SSLPeerUnverifiedException异常。
             */
            CertificatePinner certificatePinner;
            /**
             * 身份认证器,当连接提示未授权时,可以通过重新设置请求头来响应一个
             * 新的Request。状态码401表示远程服务器请求授权,407表示代理服务器请求授权。
             * 该认证器在需要时会被RetryAndFollowUpInterceptor触发。
             */
            Authenticator proxyAuthenticator;
            Authenticator authenticator;
            /**
             * 连接池
             *
             * 我们通常将一个客户端和服务端和连接抽象为一个 connection,
             * 而每一个 connection 都会被存放在 connectionPool 中,由它进行统一的管理,
             * 例如有一个相同的 http 请求产生时,connection 就可以得到复用
             */
            ConnectionPool connectionPool;
            //域名解析系统
            Dns dns;
            //是否遵循SSL重定向
            boolean followSslRedirects;
            //是否重定向
            boolean followRedirects;
            //失败是否重新连接
            boolean retryOnConnectionFailure;
            //回调超时
            int callTimeout;
            //连接超时
            int connectTimeout;
            //读取超时
            int readTimeout;
            //写入超时
            int writeTimeout;
            //与WebSocket有关,为了保持长连接,我们必须间隔一段时间发送一个ping指令进行保活;
            int pingInterval;
    
            public Builder() {
                dispatcher = new Dispatcher();
    
                protocols = DEFAULT_PROTOCOLS;
    
                connectionSpecs = DEFAULT_CONNECTION_SPECS;
                eventListenerFactory = EventListener.factory(EventListener.NONE);
                /**
                 * 代理选择类,默认不使用代理,即使用直连方式,当然,我们可以自定义配置,以指定URI使用某种代理,类似代理软件的PAC功能
                 */
                proxySelector = ProxySelector.getDefault();
                if (proxySelector == null) {
                    proxySelector = new NullProxySelector();
                }
                cookieJar = CookieJar.NO_COOKIES;
                socketFactory = SocketFactory.getDefault();
                hostnameVerifier = OkHostnameVerifier.INSTANCE;
                certificatePinner = CertificatePinner.DEFAULT;
                proxyAuthenticator = Authenticator.NONE;
                authenticator = Authenticator.NONE;
                connectionPool = new ConnectionPool();
                dns = Dns.SYSTEM;
                followSslRedirects = true;
                followRedirects = true;
                retryOnConnectionFailure = true;
                callTimeout = 0;
                connectTimeout = 10_000;
                readTimeout = 10_000;
                writeTimeout = 10_000;
                pingInterval = 0;
            }
    
            Builder(OkHttpClient okHttpClient) {
    this.dispatcher = okHttpClient.dispatcher;
                this.proxy = okHttpClient.proxy;
                this.protocols = okHttpClient.protocols;
    .....
        }
    }

    初始化Request:

    又是经典的建造者模式,大致瞅一下:

    创建Call:

    发起同步请求:

    其实分发器的作用是用来管理请求的,瞅一下它里面定义的成员变量就晓得了:

    而在请求前与请求后执行其实就是改变里面的状态,如下:

     

    接下来到最最核心的东东了,也是OkHttp框架的设计之魂,也是最终要手写来实现的功能,闪亮让它登场:

    关于拦截器的细节下一次再来分析,这里以全局的流程为重,先忽略细节,现在只要知道经过这个拦截器链之后reponse就返回了,整个请求就结了。

    发起异步请求:

    基于上同步差不多,这里只分析跟同步不一样的,当然就是发起请求这块喽:

    此时分发器又出现了,然后传了一个AsyncCall对像,一看不是一个实现了Runnable的类:

    也就是最终会执行:

    接下来线程的发起肯定是在分发器中的enqueue()方法中:

     

    然后这个线程池作为AsyncCall.executeOn()方法的参数,那接下来要干嘛想都不用想嘛:

    关于拦截器链发起请求的流程先不管,之后再分析,先来对上面整个同步和异步的请求流程做个总结:

    高清大图如:https://files.cnblogs.com/files/webor2006/okhttp%E4%B8%BB%E6%B5%81%E7%A8%8B%E6%97%B6%E5%BA%8F%E5%9B%BE.jpg.zip

    拦截器机制剖析:

    对于上面的流程分析中对于拦截器这块请求细节一带而过了,接下来则再来挼一挼它的整个流程:

    Response getResponseWithInterceptorChain() throws IOException {
        List<Interceptor> interceptors = new ArrayList();
            //用户添加的全局拦截器
            interceptors.addAll(this.client.interceptors());
            //错误、重定向拦截器
            interceptors.add(new RetryAndFollowUpInterceptor(this.client));
            //桥接拦截器,桥接应用层与网络层,添加必要的头
            interceptors.add(new BridgeInterceptor(this.client.cookieJar()));
            //缓存处理,Last-Modified、ETag、DiskLruCache等
            interceptors.add(new CacheInterceptor(this.client.internalCache()));
            //连接拦截器
            interceptors.add(new ConnectInterceptor(this.client));
            if (!this.forWebSocket) {
                //通过okHttpClient.Builder#addNetworkInterceptor()传进来的拦截器只对非网页的请求生效
                interceptors.addAll(this.client.networkInterceptors());
            }
            //真正访问服务器的拦截器
            interceptors.add(new CallServerInterceptor(this.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);
          }
        }
      }

    上面则是整个拦截器链方法的代码,不是很多,但是理解起来不是很容易,有多个拦截器组成,下面分析一下:

    拦截器【由于以前都详细分析过了,这里先暂且过一下,重点是分析整个拦截器链的流程,为手写做准备】:

    用户自定义应用拦截器:

    而它则是在我们生成OkHttpClient时添加的,实际是用得最多的,拿它做日志打印,头信息处理等等,使用如下:

     

    它有啥用?看一下它的请求时机就知道了,它是在我们发起请求之前的一个自定义拦截器,所以一般可以搞一些请求前的数据处理。

    RetryAndFollowUpInterceptor:错误、重定向拦截器

    关于它这里就不细看了,之前已经分析过:https://www.cnblogs.com/webor2006/p/10513950.html

    BridgeInterceptor:桥接拦截器,桥接应用层与网络层,添加必要的头

    它也略过了,其中关于gzip的处理就是在这个拦截器中处理的,面试时有可能会问到,如下:

    CacheInterceptor:缓存处理,Last-Modified、ETag、DiskLruCache等

    贴一个关键请求头的代码:

     

    也就是根据缓存相关的请求头来做一些缓存处理,细节也略过。

    ConnectInterceptor:连接拦截器

    关于它的具体下次再来细分,总之这一步骤会和服务端建立socket通信,也就是其实OkHttp底层是通过Socket来实现的。

    用户自定义的网络拦截器:

    CallServerInterceptor:真正访问服务器的拦截器

    这里就真正的会发起跟服务器的具体通信,最终返回Resonse了,这里也不多说了。 

    拦截器链原理:

    此时有个细节需要注意,传了一个index=0,很显然会先取第一个拦截器进行处理,那么下面来看一下整个链式的调用过程,直接来看一下它的procced方法:

     

    看具体子类:

    这里又创建了一个拦截器链对象,但是跟之前的不同的是:

    而包装了之后,接下来则会真正取出index的拦截器,然后再执行这个拦截器的intercept方法了,然后将新包装的拦截器链又传给这个在处理的拦截器的方法了:

    此时调用会转到第一个拦截器了:

     

    此时又回到了拦截器链了,同样的先包装index+1=2的新拦截器链,然后取出当前index=1的链接器进行调用:

    接着就取出第二个拦截器开始处理了:

    然后又用同样的套路:

    其它链接的流程也类似就不一一分析了,直到最后一个拦截器执行:

    在最后一个拦截器中可以发现,并没有责任链procced的代码了,而是处理完之后就返回response了,很简单,因为整个请求链条执行完了,当然不需要再往下链了,此时就得往上一层层返,最终整个拦截器链的response就返回了。关于整个链式的过程之后会手动完整的来敲一遍的,目前了解整个的链式调用的关系既可,等手动自己实现一遍之后,到那时对于OkHttp的拦截器链这块的东东就彻底的给掌握了,另外这里对于各个具体的拦截器只是一带而过了,因为不想重复再看了,之前对这块也已经详细研究过了,不过有一个非常核心的拦截器需要细看一下,那就是ConnectInterceptor,为啥?就是要看OkHttp底层的连接是通过啥方式来实现的,这块下次继续。

  • 相关阅读:
    异或运算实现两数交换
    安装Keepalived namespaces.c:187: error: ‘SYS_setns’ undeclared (first use in this function)
    安装keepalived OpenSSL is not properly installed on your system. !!!
    jackson json转实体对象 com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException
    solr集群Server refused connection at: http://127.0.0.1:6060/solr/ego 注册zookeepr自动使用内网ip
    Solr java.sql.SQLException: null, message from server: "Host 'xxx' is not allowed to connect to this MySQL server
    Jackson中@JsonProperty等常用注解
    java.lang.ClassNotFoundException: XXX (no security manager: RMI class loader disabled)
    ActiveMQ学习总结------Spring整合ActiveMQ 04
    为什么嵌入式开发用memcpy()而不用strncpy()
  • 原文地址:https://www.cnblogs.com/webor2006/p/12362710.html
Copyright © 2020-2023  润新知