• 从OkHttp的源码来看 HTTP


     先来了解一下OkHttp的历史,最早是square公司觉得Android给的HttpClient这块的库不太好用,于是乎做了一层包装,再后来他们包装的这个库被Android官方给收回去了,而Android内部的HttpUrlConnection的实现用的其实是OkHttp的代码,而Okhttp是完全重新写的一套HTTP库,包含TCP、TLS的实现,最初是依赖于Google的那套网络框架,而现在完全不依赖了,最终还被Google给收购了,所以,好好了解OkHttp的本质是毋庸置疑的。

    先上一下OkHttp的官网瞅下:

    看一下它的简单用法:

    好,咱们还是以上一节【https://www.cnblogs.com/webor2006/p/10502230.html】获取github的仓库为例,简单用一下OkHttp,如下:

    首先增加OkHttp的依赖:

    然后来访问这个接口“https://api.github.com/users/webor2006/repos”为例,看下OkHttp是如何来请求的:

    运行一下:

    其实原因是在build中木有指定Java的编译版本为Java8导致,因为OkHttp库中如今用到了Lambda表达式,所以指定一下:

    再次运行:

    ok,关于用法就到这,关于OkHttp的具体使用这里就不详细赘述啦,用一用就会了,重点是分析它的原理机制,直接开始看源码了,首先先看下它:

    其中Request又是采用Builder模式来构建的,真的是无处不见Builder模式:

    点进去看一下源码:

    可以发现是由RealCall来返回的,很明显RealCall是Call的具体实现,所以点击RealCall.newRealCall看下它的源码:

    其中为啥第二个参数名叫originalRequest,也就是在之后会对这个Request进行转型,所以这是一个最初始的状态;第三个参数涉及到了WebSocket,那啥叫WebSocket呢,百度百科一下:

    也就是说它其实是HTTP的一种扩展,正常的HTTP服务器端是不可能主动给客户端消息推送的,而WebSocket一般是炒股交易的软件会用,因为消息会比较实时,通常软件 是不会用到它的,了解一下既可。

    接下来再继续:

    那这个eventListener有啥用呢?HTTP请求过程中有各种状态,如TCP连接、SSL连接、请求等状态,它主要是用来记录这些状态用的,不重要,貌似这个方法比较简单,接着来就需要分析一下它了:

    走进去:

    很明显它的实现应该是在RealCall里面如下:

    分析核心代码,首先调用eventListener中的一个状态方法:

    然后接下来一句就是核心代码了:

    这句代码涉及到三个东东:dispatcher()、enqueue()、AsyncCall,所以分别来了解一下:

    dispatcher():

    那瞅一下Dispatcher类:

    而它用到了线程池:

    假如想要所有的请求一个个先后执行,那只要将上面的max设置为1既可。

    enqueue():

    接下来则看一下它的源码:

    这里有三个队列定义于Dispatcher类中,如下:

    所以说Dispatcher类就是一个任务调度类。

    AsyncCall:

    enqueue方法参数中还涉及到此类,先来瞅一下:

    貌似对于Runnable应该里面会有一个run()方法吧,可AsyncCall中木有看到,如它的结构所示:

    那有可能定义在它的父类NamedRunnable中,所以瞅一下:

    final class AsyncCall extends NamedRunnable {
        private final Callback responseCallback;
        private volatile AtomicInteger callsPerHost = new AtomicInteger(0);
    
        AsyncCall(Callback responseCallback) {
          super("OkHttp %s", redactedUrl());
          this.responseCallback = responseCallback;
        }
    
       ...
    
        @Override protected void execute() {
          boolean signalledCallback = false;
          timeout.enter();
          try {
            Response response = getResponseWithInterceptorChain();
            if (retryAndFollowUpInterceptor.isCanceled()) {
              signalledCallback = true;
              responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
            } else {
              signalledCallback = true;
              responseCallback.onResponse(RealCall.this, response);
            }
          } catch (IOException e) {
            e = timeoutExit(e);
            if (signalledCallback) {
              // Do not signal the callback twice!
              Platform.get().log(INFO, "Callback failure for " + toLoggableString(), e);
            } else {
              eventListener.callFailed(RealCall.this, e);
              responseCallback.onFailure(RealCall.this, e);
            }
          } finally {
            client.dispatcher().finished(this);
          }
        }
      }

    那这里面应该就是整个请求处理的关键,下面来搞定它:

    不过这里暂且不深入分析它,先来总结一下enqueue()的整个流程:它会去调用Dispather.enqueue()方法,而这个方法会生成一个AsyncCall对象,最终会执行AsyncCall.execute()方法,然后最终再真正请求给出结果反馈。

    另外对于OkHttp的经典使用除了enqueue()异常请求之外,还有一个execute()方法,它是同步的,也来大致瞅一下它的实现:

    也就是enqueue()和execute()就是是否是同步的区别而已。

    好,接下来则需要研究两个东东了:

    一是OkHttpClient在实用角度(理解OkHttp跟Http的关系)需要理解一下它的配置项;

    二是上面提到的拦截链。

    好,先来瞅一下OkHttpClient这个类:

    这个就是客户端告诉OkHttp支持HTTP的版本,就像浏览器能支持HTTP1.0、HTTP1.2,这个属性就是干这个用的。

    所以这个是告诉OkHttp是否支持HTTP,还是支持HTTPS,而HTTPS要怎么支持,也就是对于HTTP的底层支持OkHttp全部都实现了,这就可以看出跟Retrofit的一个区别了,好,继续!

    关于这俩在之后再详说,跟拦截器链有关,往下继续:

    这个还记得么,回顾一下:

    也就是它是用来创建eventListener的东东,往下:

    关于它的使用场景在之前HTTPS中已经学习过了,这里简单点击瞅一下:

    它是JDK中的接口,其中OkHttp的实现如下:

    就不细看了,继续往下看:

    对于有自签名的需求用它是非常合适的,这里来简单了解一下它的用法,文档上有描述,如下:

    咱们来贴过来试一下:

    其中增加了一个验证的选项,这样就不会使用本地根证书进行验证了,就会使用咱们自己的验证方式了,如下:

    此时肯定会验证失败,运行如下:

    03-15 23:01:48.172 24248-24267/com.okhttpstudy.study E/cexo: Failed!!!Certificate pinning failure!
          Peer certificate chain:
            sha256/uL6J3XldY7njtfGsRP7HZgYsrNPrDZXpG7kT7wfg1m0=: CN=www.baidu.cn,OU=service operation department,O=BeiJing Baidu Netcom Science Technology Co.\, Ltd,L=Beijing,C=CN
            sha256/5kJvNEMw0KjrCAu7eXY5HZdvyCS13BbA0VJG1RSP91w=: CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US
            sha256/r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=: CN=DigiCert Global Root CA,OU=www.digicert.com,O=DigiCert Inc,C=US
          Pinned certificates for baidu.com:
            sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=

    其会在错误中会将此网站的所有签名信息都会列出来,此时要想自签名让其通过验证,则将错误提示的三个sha值添加到okhttpclient上,如下:

    这个当有自签名的需求时还是比较好用滴。再继续:

    那具体是怎么用它的呢?

    简单瞅下它:

    至此整个OkHttpClient的字段都过了一遍,很明显通过了解这些字段的含义能知道OkHttp底层做了好多事,清楚的能看到到处有http的身影。

    接下来则需要研究Okhttp最核心的拦截器链啦,如下:

    点进去瞅瞅它的实现:

    其中首先是准备各种interceptor,如下:

    接着将其生成一个InterceptorChain对像,如下:

    用图来表达一下拦截器链:

    最后执行拦截器链的proceed(),此时其实是这么个过程:

    为啥要一个链呢?这里可以举一个汉堡相关的例子用来理解:有人打电话进来订了汉堡的餐,然后店老板将汉堡做好了并且给负责分发的员工,接着送餐员到店取了汉堡骑着电动送到订餐人的家里,然后送餐员将汉堡给了订餐的人,与此同时订餐人将钱给了送餐员, 送餐员又拿着钱回到了店里,并从中拿了1块钱的提成,然后将剩下的钱全部交给了负责分发的店员,然后店员又将钱交给了店长,这就是一个链,店长是链的起点【做汉堡并分配给店员】和终点【从店员中拿到钱】。当一个事件比较复杂的时候采用链的方式会职责清晰,每一个链则就是拦截器。

    其中关于proceed()这里还是以上面举的汉堡的店长为例进一步阐述一下:其实对于店长的proceed()过程就是“做汉堡并分发给店员--->等待收钱---->收钱之后装进自己的裤腰袋”,而结合图的第一个节点表示店长的链,其过程其实是这样:

    此时店长proceed()了,则会将链接给下一个接点,也就是分发的店员,如下:

    接着经过漫长的等待,最终会从店员中收到钱,此时节点就会回到了如下位置:

    用更加形象一点的图来表示整个proceed()的过程,其实有点像递归:

    从中可以看到链上的每一个结点都执行proceed()之后则就行成了一个一去一回的完整链的过程。

    大概理解拦截链之后,接下来则细看一下具体的各个链,对于OkHttp的链,有两个可以自动配置的,如下:

    这个先放一下,先来看一下其它具体的拦截器

    RetryAndFollowUpInterceptor:

    第一个是它:

    重试及重定向拦截器,点进去,对于每一个拦截器主要是看它的intercept()方法,所以:

    其里面做的事情基本上如我们上面理论所说会有如下三件事件【OkHttp所有链的工作方式都遵循此原则,所以理解它非常之重要!】:

    1、最初的准备(准备汉堡并交给店员)。

    其中瞅一下StreamAllocation:

    基本上这个拦截器前置工作比较简单,重点是后置工作。

    2、等待结果(等待收钱)。

    接着执行proceed():

    此时就会等待它之后的所有节点都处理完之后,此proceed()才会返回结果。

    3、处理结果(将钱装自自己口袋)。

    也就是proceed()之后的则是处理结果,具体来瞅下此拦截器的后置处理:

    所以瞅一下recover方法:

    如果符合重试条件,那么直接再次循环:

    其它的重试处理也类似:

    最终:

    BridgeInterceptor:

    点进来瞅下,能很清楚的看到http的影子,如下:

    好,还是按之前的三步骤来分析:

    1、最初的准备(准备汉堡并交给店员)。

    基本全是加头信息:

     

    也就是OkHttp自身就已经支持gzip压缩解压缩,也就是开发者完全透明,具体解的地方在:

    这个是需要开发者自己定义的,因为CookieJar默认是空实现的,如下:

    其大致用法可以这样写:

    2、等待结果(等待收钱)。

    当然就是执行proceed()方法喽:

    3、处理结果(将钱装自自己口袋)。

    基本上做解工作,在proceed()之后:

    CacheInterceptor:

    比较简单,就直接过了。

    ConnectInterceptor【真正跟http或https进行交互了】:

    点进去瞅下细节,主要是看流程,因为这个流程能够让我们清楚的发现跟TCP和SSL的建立过程:

    好,目前TCP的连接已经看到了,那SSL的连接呢?得回到上一层,这:

    所以此拦截器的作用就是用来建立TCP+SSL连接用的,而它木有后置工作:

    CallServerInterceptor:

    因为它是最后一个拦截器了,所以木有proceed()了,做完事情直接返回就ok了,它都是做的实质工作,下面也来分析其关键代码:

    我们看http1的就成:

    也就是通过TCP来往服务器建立通信了,接着来处理response并返回:

    最后就只剩自定义的拦截器木有看了,这个比较简单,首先可以配置一个自己的拦截器,如下:

    对于我在公司的项目通常会用这个拦截器来打印一下日志,或者传一些公共头信息之类的,总之比较好用。

    那对于这两个自定义的拦截器,有啥区别呢?

    所以如果想直接http数据时则需要用networkInterceptors,一般用不到它,主要是用上面的自定义拦截器。

    到此,整个完整的链都分析完了,可以看到OkHttp是整个接管了整个Http的工作,从TCP连接的建立,TLS连接的建立,HTTP报文相关的处理,而Retrofit是利用Okhttp来实现的,只是API做了很多的容错。

  • 相关阅读:
    nikto web server 扫描工具
    crowdsec开源安全引擎
    windows strace 类似的工具
    禁用spring boot 暴露的几个产品可用的endppint 服务
    nginx proxy minio 默认页配置(三)
    nginx proxy minio 默认页配置(二)
    nginx proxy minio 默认页配置
    flamescope + chrome cpu profile 分析web 性能
    golang noCopy 的功能
    golang 死锁&&静态检查的一些工具
  • 原文地址:https://www.cnblogs.com/webor2006/p/10513950.html
Copyright © 2020-2023  润新知