美文网首页
全面深入OkHttp源码(下)

全面深入OkHttp源码(下)

作者: s1991721 | 来源:发表于2018-10-15 23:12 被阅读0次

    分支开始Chain,保存RealCall位置

    到了烂大街的责任链了

    interceptors先添加okHttpClient对象中的应用层拦截器,紧接着RetryAndFollowUpInterceptor,之所以没在这里初始化,是因为Call对象需要持有RetryAndFollowUpInterceptor对象进行其他操作

    接着是顺序添加的拦截器,最后添加了okHttpClient对象中的网络层拦截器

    https://www.jianshu.com/u/0790f0629fc6

    最终形态的Chain,Request经过重重拦截器的包装重整发送到Server,当Response返回时又逆向经历了重重拦截器的解析处理最终到达Client

    道理很简单,但OkHttp的实现手法很高明

    这里有两个接口Interceptor、Chain

    RealInterceptorChain

    Interceptor即拦截器接口只有一个intercept方法

    而Chain只有一个实现类RealInterceptorChain,它是实现责任链的重要类,我更喜欢称其为

    责任链开始之初,实例化了一个环,参数使用的时候再解释

    接着环开始滚动proceed,我们以index参数作为环的标记

    环不能够滚出链条,所以要判断当前环的位置

    RealInterceptorChain

    当前环滚动(调用)次数自加,判断参数条件,由于当前环各参数为空,所以不throw

    RealInterceptorChain

    紧接着环滚到了下一层,新环index=1,当前index=0的环取出第一个拦截器RetryAndFollowUpInterceptor,处理新环index=1,得到Response

    index=i的环内生成index=i+1的新环,并交由拦截器链中第i项拦截器处理,第i项拦截器又调用环的process方法生成新环交给下一个拦截器,一直重复动作,直到环滚到头时,再滚回来,和算法中的递归相似

    了解了责任链与环的协作,看看具体的拦截器做了什么

    1、 RetryAndFollowUpInterceptor

    生成了一个关键对象StreamAllocation,就执行下一个环的滚动。StreamAllocation类是协调Connections、Streams、Calls三者间关系的,使用时讲。

    2、BridgeInterceptor

    生成新的Requester.builder,在保留原参数的基础上增加新参数,至于具体参数的设置,相信有更加详细准确的资料这里就不显摆了。

    我要说下body这个变量,来自RequestBody是个抽象类,有两个实现类,在Request初始化时赋值

    RequestBody为网络请求传输的数据载体,共两部分:MediaType、Content,Content根据MediaType的不同而不同,可以是字节、字符串或文件,这三种类型已经满足了所有的请求内容。

    可以通过RequestBody中静态方法,实现相应的数据传输

    3、CacheInterceptor

    用来返回相同请求且结果未更新的Response

    分支开始Cache,保存CacheInterceptor位置

    缓存来自okHttpClient对象,但在Builder中没有默认实现

    OkHttpClient.Builder

    需要手动完成,这里有两个Cache:InternalCache、Cache

    官方已经不建议使用InternalCache,而是使用Cache替代

    OkHttpClient

    但OkHttpClient返回的却还是InternalCache,Cache只是InternalCache的一层具体实现

    有点像Cache实现了InternalCache接口的味道,但实际是使用了类聚合

    Cache

    判断是否符合缓存条件,如果是POST、PATCH、PUT、DELETE、MOVE方法则移除缓存,意味着以上方法不缓存。

    紧接着判断GET方法,只有GET方法的请求能够被缓存

    再来判断vary头信息,具体内容看这里,决定是否缓存

    所有条件都通过后,将response转成Entry存入DiskLruCache,key取请求URL的MD5

    存取的关键逻辑在Entry类中

    通过不同的构造方法,初始化变量

    这里使用了okio相关的内容

    读写顺序是相反的,按照图中的指示对照着看,如果没接触过okio,单独看会懵

    分支结束Cache,读取CacheInterceptor位置

    取到候选Response后,交由缓存策略

    初始化缓存相关的Http头信息(这里真不是专业的后端开发,未避免误人子弟,各个头信息还请自行查询)

    走到这一步,将缓存策略对象strategy交由缓存cache对象,用来统计缓存是否命中

    候选Response取到了,但经过缓存策略判断不符合,则关闭候选Response的body,为什么?

    cacheCandidate是从Cache中取的Response

    Cache

    经过了Entry的转换

    image.png

    此时snapshot被传入了

    由于cacheCandidate从Cache缓存中经okio,取出,如今发现cacheCandidate不符合条件,所以需要关闭okio,类似流的概念

    又到了缓存判断的阶段(网络缓存这方面逻辑是真复杂,其实并不是复杂,而是做移动端的应该对此都比较陌生)

    CacheInterceptor

    经过缓存策略判定根据不同的情况返回请求与response,若请求为空,缓存的response也空,直接返回HttpCode 504(Gateway timeout),为什么?

    产生原因在这:

    CacheStrategy

    请求不null,但请求的参数设置了onlyIfCached,注释:我有请求参数,但我请求的response必须使用缓存,而不是来自服务器,当缓存不存在时就报了504

    终于是最后的判断了:

    由策略产出的networkRequest为空得出结论,此请求不需要真正执行,将缓存的response脱光返回

    英语讲解:stripper 为strip的名词,双写p加er

    走到这一步还没有合适的response,没办法交给下一环处理吧!!

    4、ConnectInterceptor

    重要的网络连接操作

    ConnectInterceptor

    获取之前环中生成的streamAllocation对象,调用其newStream方法

    还记得streamAllocation是在RetryAndFollowUpInterceptor那一环中被实例化赋值的吗?

    当时只是实例化对象没做任何操作,具体的参数上面都讲过了,返回看看参数的来源,会对整个流程更加清晰!

    这里生成了RealConnection对象,接着RealConnection对象又生成了HttpCodec对象,一个个来

    它是Connection接口的唯一实现类,看见红色框是不是很熟悉,BufferedSource、BufferedSink是okio中的输入输出流,有了socket和流就可以进行传输通信了,因此RealConnection是实际的连接类。

    看看它是怎么产生的

    如方法名,找到一个健康的Connection,何谓健康后面谈,先看看Connection怎么来的

      private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
          int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
        boolean foundPooledConnection = false;//是否找到Connection的标记
        RealConnection result = null;//找到的Connection
        Route selectedRoute = null;//路由
        Connection releasedConnection;//需要释放的Connection
        Socket toClose;//需要关闭的socket
        synchronized (connectionPool) {//锁住connectionPool保证线程安全
          if (released) throw new IllegalStateException("released");
          if (codec != null) throw new IllegalStateException("codec != null");
          if (canceled) throw new IOException("Canceled");
    
          // Attempt to use an already-allocated connection. We need to be careful here because our
          // already-allocated connection may have been restricted from creating new streams.
          releasedConnection = this.connection;//将自身的Connection记为需要释放的状态
          toClose = releaseIfNoNewStreams();//Connection上可以生成多个流,noNewStreams标志表示此Connection是否能够产生新的流
          if (this.connection != null) {//复用之前的connection
            // We had an already-allocated connection and it's good.
            result = this.connection;
            releasedConnection = null;
          }
          if (!reportedAcquired) {//streamAllocation对象中标记是否已经获取到connection
            // If the connection was never reported acquired, don't report it as released!
            releasedConnection = null;
          }
    
          if (result == null) {//走到这里,表明streamAllocation对象中没有可使用的connection
            // Attempt to get a connection from the pool.
            Internal.instance.get(connectionPool, address, this, null);//从connectionPool池中按要求取一个connection,
            if (connection != null) {//取到了
              foundPooledConnection = true;
              result = connection;
            } else {//没取到
              selectedRoute = route;
            }
          }
        }
        closeQuietly(toClose);
    
        if (releasedConnection != null) {//回调
          eventListener.connectionReleased(call, releasedConnection);
        }
        if (foundPooledConnection) {//回调
          eventListener.connectionAcquired(call, result);
        }
        if (result != null) {
          // If we found an already-allocated or pooled connection, we're done.
          return result;
        }
    
        // If we need a route selection, make one. This is a blocking operation.
        boolean newRouteSelection = false;//选择一条连接的路由
        if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
          newRouteSelection = true;
          routeSelection = routeSelector.next();
        }
    
        synchronized (connectionPool) {//操作池子
          if (canceled) throw new IOException("Canceled");
    
          if (newRouteSelection) {//有路由条件
            // Now that we have a set of IP addresses, make another attempt at getting a connection from
            // the pool. This could match due to connection coalescing.
            List<Route> routes = routeSelection.getAll();
            for (int i = 0, size = routes.size(); i < size; i++) {
              Route route = routes.get(i);
              Internal.instance.get(connectionPool, address, this, route);
              if (connection != null) {//找到符合路由的connection
                foundPooledConnection = true;
                result = connection;
                this.route = route;
                break;
              }
            }
          }
    
          if (!foundPooledConnection) {//没找到
            if (selectedRoute == null) {
              selectedRoute = routeSelection.next();
            }
    
            // Create a connection and assign it to this allocation immediately. This makes it possible
            // for an asynchronous cancel() to interrupt the handshake we're about to do.
            route = selectedRoute;
            refusedStreamCount = 0;
            result = new RealConnection(connectionPool, selectedRoute);//根据路由自己创建Connection
            acquire(result, false);//通知并赋值
          }
        }
    
        // If we found a pooled connection on the 2nd time around, we're done.
        if (foundPooledConnection) {//如果在池子找到则回调,否则不回调,因为池子找到的connection已经连接好了,否则还要经过下面的连接
          eventListener.connectionAcquired(call, result);
          return result;
        }
    
        // Do TCP + TLS handshakes. This is a blocking operation.
        result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
            connectionRetryEnabled, call, eventListener);//连接
        routeDatabase().connected(result.route());//记录路由
    
        Socket socket = null;
        synchronized (connectionPool) {
          reportedAcquired = true;
    
          // Pool the connection.
          Internal.instance.put(connectionPool, result);//创建的connection放入池子管理
    
          // If another multiplexed connection to the same address was created concurrently, then
          // release this connection and acquire that one.
          if (result.isMultiplexed()) {//发现池子内部有相同路由的连接(避免不了的多线程操作)舍弃自己,用那个
            socket = Internal.instance.deduplicate(connectionPool, address, this);
            result = connection;
          }
        }
        closeQuietly(socket);
    
        eventListener.connectionAcquired(call, result);//一切完成回调
        return result;
      }
    

    经过了重重的筛选,无论是池子中获取的已经连接的connection,还是自行创建并连接放入池子的connection对象,StreamAllocation对象总算持有了connection对象,现在对connection对象进行健康检查

    如果此connection对象是新生的不是在池子取的,一定健康

    RealConnection

    具体的健康检查有:socket是否关闭、http2的话连接是否关闭、非get请求还要检验流是否关闭

    如果connection不健康,释放connection关闭socket和流,并从connectionPool中移除

    并将与此connection相关联的所有StreamAllocation引用移除

    引用是在获取到connection对象时建立的

    StreamAllocation对象获取了健康的connection对象,紧接着有用connection对象生成了解码HttpCodec对象

    根据http协议的不同生成,Http1时可以看到流在此处配置,那socket和流什么时候初始化的?

    前面只说了connection对象从池子取或自己连接,那最初的那个connection对象一定是自连接的,根据这个思路看看RealConnection类

    StreamAllocation

    connection对象持有池子connectionPool和路由route对象,连接又是大串的代码

      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) {//是否https
          if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {//不是https且不支持明文
            throw new RouteException(new UnknownServiceException(
                "CLEARTEXT communication not enabled for client"));
          }
          String host = route.address().url().host();
          if (!Platform.get().isCleartextTrafficPermitted(host)) {//这里根据okhttp使用平台的不同而判断,是否明文
            throw new RouteException(new UnknownServiceException(
                "CLEARTEXT communication to " + host + " not permitted by network security policy"));
          }
        } else {//https的话不允许使用prior_knowledge协议
          if (route.address().protocols().contains(Protocol.H2_PRIOR_KNOWLEDGE)) {
            throw new RouteException(new UnknownServiceException(
                "H2_PRIOR_KNOWLEDGE cannot be used with HTTPS"));
          }
        }
    
        while (true) {
          try {
            if (route.requiresTunnel()) {//根据路由判断是建立隧道还是socket连接
              connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);//建立隧道
              if (rawSocket == null) {
                // We were unable to connect the tunnel but properly closed down our resources.
                break;
              }
            } else {
              connectSocket(connectTimeout, readTimeout, call, eventListener);//建立socket
            }
            establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);//连接建立起后,初始化协议
            eventListener.connectEnd(call, route.socketAddress(), route.proxy(), protocol);//回调连接解释
            break;
          } catch (IOException e) {//连接异常捕获处理
            closeQuietly(socket);
            closeQuietly(rawSocket);
            socket = null;
            rawSocket = null;
            source = null;
            sink = null;
            handshake = null;
            protocol = null;
            http2Connection = null;
    
            eventListener.connectFailed(call, route.socketAddress(), route.proxy(), null, e);//连接异常回调
    
            if (routeException == null) {
              routeException = new RouteException(e);
            } else {
              routeException.addConnectException(e);
            }
    
            if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {//设置重连则不抛
              throw routeException;
            }
          }
        }
    
        if (route.requiresTunnel() && rawSocket == null) {//如果路由隧道的代理是http的而请求是https则隧道异常
          ProtocolException exception = new ProtocolException("Too many tunnel connections attempted: "
              + MAX_TUNNEL_ATTEMPTS);
          throw new RouteException(exception);
        }
    
        if (http2Connection != null) {//这里则是http2的多连接复用
          synchronized (connectionPool) {
            allocationLimit = http2Connection.maxConcurrentStreams();
          }
        }
      }
    

    先看看熟悉的socket连接

    如果代理模式为直连或Http则直接生成socket,否则是代理socket

    socketFactory从okHttpclient对象传来,默认为DefaultSocketFactory直接new Socket返回

    回调连接开始,根据平台连接socket并初始化流。

    隧道有中间代理所以要生成代理请求

    先创建了socket,后又创建隧道,createTunnel方法创建隧道,方法内先发送隧道请求,紧接着就是阻塞状态下不停的读取数据,直到返回结束状态。

    到此connection有了,其内部的socket也已经连接

    解码器的功能就是实现socket数据的编解码,操作主要以HttpCodec对象为主,其内部持有了连接好的socket对象

    5、CallServerInterceptor

    经过前面几环不同的初始化,最后一环终于可以大展拳脚了

    //CallServerInterceptor.class
      @Override public Response intercept(Chain chain) throws IOException {
        RealInterceptorChain realChain = (RealInterceptorChain) chain;
        HttpCodec httpCodec = realChain.httpStream();
        StreamAllocation streamAllocation = realChain.streamAllocation();
        RealConnection connection = (RealConnection) realChain.connection();
        Request request = realChain.request();
    //取出前面几环初始化的对象
        long sentRequestMillis = System.currentTimeMillis();//正式请求的时间
    
        realChain.eventListener().requestHeadersStart(realChain.call());//回调
        httpCodec.writeRequestHeaders(request);//使用httpCodec发送头信息
        realChain.eventListener().requestHeadersEnd(realChain.call(), request);//回调
    
        Response.Builder responseBuilder = null;
        if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {//方法支持请求体且请求体不空
          // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
          // Continue" response before transmitting the request body. If we don't get that, return
          // what we did get (such as a 4xx response) without ever transmitting the request body.
          if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {//请求头中有{"Expect":"100-continue"}表明有请求体数据上传征求服务器意见,具体看服务端有没有实现此协议,若没有client也会在等待超时后发送请求体
            httpCodec.flushRequest();//发送头信息
            realChain.eventListener().responseHeadersStart(realChain.call());//回调
            responseBuilder = httpCodec.readResponseHeaders(true);//读头信息
          }
    
          if (responseBuilder == null) {//服务端接受了100-continue
            // Write the request body if the "Expect: 100-continue" expectation was met.
            realChain.eventListener().requestBodyStart(realChain.call());//回调
            long contentLength = request.body().contentLength();//body长度
            CountingSink requestBodyOut =
                new CountingSink(httpCodec.createRequestBody(request, contentLength));
            BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);//这两个对象为okio,类似流
    
            request.body().writeTo(bufferedRequestBody);//具体的内容写入流中
            bufferedRequestBody.close();//关闭流
            realChain.eventListener()
                .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);//回调写出多少数据
          } else if (!connection.isMultiplexed()) {//服务端不接受100-continue则设置此connection对象不能生成新流
            // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
            // from being reused. Otherwise we're still obligated to transmit the request body to
            // leave the connection in a consistent state.
            streamAllocation.noNewStreams();
          }
        }
    
        httpCodec.finishRequest();//请求结束
    
        if (responseBuilder == null) {//无body时进入获取response
          realChain.eventListener().responseHeadersStart(realChain.call());
          responseBuilder = httpCodec.readResponseHeaders(false);
        }
    
        Response response = responseBuilder
            .request(request)
            .handshake(streamAllocation.connection().handshake())
            .sentRequestAtMillis(sentRequestMillis)
            .receivedResponseAtMillis(System.currentTimeMillis())
            .build();//在原基础上新建response,这也是构建者模式的优势
    
        int code = response.code();//服务端返回码
        if (code == 100) {//服务端返回100表示让client继续发送数据
          // server sent a 100-continue even though we did not request one.
          // try again to read the actual response
          responseBuilder = httpCodec.readResponseHeaders(false);//重新读一遍返回信息
    
          response = responseBuilder
                  .request(request)
                  .handshake(streamAllocation.connection().handshake())
                  .sentRequestAtMillis(sentRequestMillis)
                  .receivedResponseAtMillis(System.currentTimeMillis())
                  .build();
    
          code = response.code();
        }
    
        realChain.eventListener()
                .responseHeadersEnd(realChain.call(), response);//回调
    
        if (forWebSocket && code == 101) {//请求使用的是webSocket,且服务端已接受
          // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
          response = response.newBuilder()
              .body(Util.EMPTY_RESPONSE)//由于websocket的性质,所以给body占位
              .build();
        } else {//读取返回信息
          response = response.newBuilder()
              .body(httpCodec.openResponseBody(response))
              .build();
        }
    
        if ("close".equalsIgnoreCase(response.request().header("Connection"))
            || "close".equalsIgnoreCase(response.header("Connection"))) {//请求为一次连接or服务端返回关闭连接
          streamAllocation.noNewStreams();//使连接不能够产生新流
        }
    
        if ((code == 204 || code == 205) && response.body().contentLength() > 0) {//204表示没内容,205表示清空表单,但返回的数据却有长度,明显此次的通信异常了
          throw new ProtocolException(
              "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
        }
    
        return response;
      }
    

    HttpCodec具体的数据读写看实现类Http1Codec,使用的是okio

    拼接请求行,抓包时常见

    RequestLine

    将请求行与头信息写入流

    同socket的flush

    读取头信息,红框标准的地方也是为什么

    这里要判null的原因

    请求体的创建包了好几层,chunked表示空请求体,请求体实际也是一个流,FixedLengthSink继承自Sink

    finish同flush

    创建ResponseBody,此时的body还不是内容,只是流供上层拦截器使用。

    最终Response获取到了,环滚到头又要向回滚了

    4、ConnectInterceptor

    对response没做处理直接返回

    3、CacheInterceptor

    对返回的response做缓存处理

        Response networkResponse = null;
        try {
          networkResponse = chain.proceed(networkRequest);//上一环返回的response
        } finally {
          // If we're crashing on I/O or otherwise, don't leak the cache body.
          if (networkResponse == null && cacheCandidate != null) {//结果异常,但有缓存,关闭缓存的流
            closeQuietly(cacheCandidate.body());
          }
        }
    //为什么只有try-finally,发生异常时不捕获而是向上抛出,在抛出异常前做finally的保护工作。这种写法是为了充分的暴露异常交给外层处理
        // If we have a cache response too, then we're doing a conditional get.
        if (cacheResponse != null) {//到此处一定有networkResponse,否则早在上步异常了,当有networkResponse且有cacheResponse时根据状态码决定取哪个
          if (networkResponse.code() == HTTP_NOT_MODIFIED) {//304表示内容自上次获取后就没被修改过
            Response response = cacheResponse.newBuilder()//直接使用缓存cacheResponse
                .headers(combine(cacheResponse.headers(), networkResponse.headers()))//将networkResponse和cacheResponse的头信息合并
                .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
                .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
                .cacheResponse(stripBody(cacheResponse))//这里只保留cacheResponse除body外的信息,因为body是流
                .networkResponse(stripBody(networkResponse))//同上
                .build();
            networkResponse.body().close();//既然使用缓存,那返回的networkResponse就需要将body流关闭
    
            // Update the cache after combining headers but before stripping the
            // Content-Encoding header (as performed by initContentStream()).
            cache.trackConditionalCacheHit();//记录缓存命中
            cache.update(cacheResponse, response);//更新缓存
            return response;//直接返回response
          } else {//有缓存但是缓存已过时,不使用cacheResponse,关闭cacheResponse中body的流
            closeQuietly(cacheResponse.body());
          }
        }
    
        Response response = networkResponse.newBuilder()//在返回networkResponse的基础上使用构造者模式
            .cacheResponse(stripBody(cacheResponse))//不要body
            .networkResponse(stripBody(networkResponse))
            .build();
    
        if (cache != null) {//okhttp有缓存策略
          if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {//返回有body,且http返回信息支持缓存
            // Offer this request to the cache.
            CacheRequest cacheRequest = cache.put(response);//存入缓存,返回一个带有response.body输入流的cacheRequest
            return cacheWritingResponse(cacheRequest, response);//将cacheRequest中的response.body输入流与response的body流连接,新建Response并返回
          }
    
          if (HttpMethod.invalidatesCache(networkRequest.method())) {//根据请求方法判断是否缓存,除get外其余不许缓存
            try {
              cache.remove(networkRequest);
            } catch (IOException ignored) {
              // The cache cannot be written.
            }
          }
        }
    
        return response;
      }
    

    2、BridgeInterceptor

    保存cookie和解压

        Response networkResponse = chain.proceed(requestBuilder.build());//从上一环接棒
    
        HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());//从headers信息中取出cookie,与URL关联后存入cookieJar,CookieJar是一个接口需要手动实现,OkHttp默认没有存储
    
        Response.Builder responseBuilder = networkResponse.newBuilder()
            .request(userRequest);//从这一环传递到下一环的请求信息是经过了加工的,因此返回的时候要将原始的userRequest恢复回来,否则用户会奇怪请求多了些没设置过的参数
    
        if (transparentGzip
            && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
            && HttpHeaders.hasBody(networkResponse)) {//请求是否开启了gzip压缩,默认设置是开了,且Response也使用了gzip压缩,且Response有body
          GzipSource responseBody = new GzipSource(networkResponse.body().source());//将body流转为Gzip流
          Headers strippedHeaders = networkResponse.headers().newBuilder()
              .removeAll("Content-Encoding")
              .removeAll("Content-Length")
              .build();//从headers中移除项
          responseBuilder.headers(strippedHeaders);
          String contentType = networkResponse.header("Content-Type");
          responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));//将原body流切换成Gzip流
        }
    
        return responseBuilder.build();
    

    1、RetryAndFollowUpInterceptor

    重连

        while (true) {//因为重连所以要无限循环
          if (canceled) {
            streamAllocation.release();
            throw new IOException("Canceled");
          }
    
          Response response;
          boolean releaseConnection = true;//释放连接标志
          try {
            response = realChain.proceed(request, streamAllocation, null, null);//从上环中获取
            releaseConnection = false;//是否释放连接,当发生预料外的Exception时释放
          } catch (RouteException e) {
            // The attempt to connect via a route failed. The request will not have been sent.
            if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {//是否异常能被解决
              throw e.getFirstConnectException();
            }
            releaseConnection = false;
            continue;
          } catch (IOException e) {
            // An attempt to communicate with a server failed. The request may have been sent.
            boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
            if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
            releaseConnection = false;
            continue;
          } finally {
            // We're throwing an unchecked exception. Release any resources.
            if (releaseConnection) {//最终异常不能被解决则释放连接
              streamAllocation.streamFailed(null);
              streamAllocation.release();
            }
          }
    
          // Attach the prior response if it exists. Such responses never have a body.
          if (priorResponse != null) {//因为有重连,所以要记录上次连接的返回
            response = response.newBuilder()
                .priorResponse(priorResponse.newBuilder()
                        .body(null)
                        .build())
                .build();
          }
    
          Request followUp;//下步连接操作
          try {
            followUp = followUpRequest(response, streamAllocation.route());//根据返回状态和路由判断是否有下次连接
          } catch (IOException e) {
            streamAllocation.release();
            throw e;
          }
    
          if (followUp == null) {//无下次连接
            if (!forWebSocket) {//不是websocket协议
              streamAllocation.release();//这次请求完成,关闭连接
            }
            return response;//返回结果
          }
    
          closeQuietly(response.body());//还有下次连接则关闭,response.body的流
    
          if (++followUpCount > MAX_FOLLOW_UPS) {//连接次数超限
            streamAllocation.release();
            throw new ProtocolException("Too many follow-up requests: " + followUpCount);
          }
    
          if (followUp.body() instanceof UnrepeatableRequestBody) {//不许重连
            streamAllocation.release();
            throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
          }
    
          if (!sameConnection(response, followUp.url())) {//根据host、port、scheme三个标准判断下次连接与这次是否相同,多次请求与重定向的区别
            streamAllocation.release();//重定向的话要用新的连接
            streamAllocation = new StreamAllocation(client.connectionPool(),
                createAddress(followUp.url()), call, eventListener, callStackTrace);
            this.streamAllocation = streamAllocation;
          } else if (streamAllocation.codec() != null) {
            throw new IllegalStateException("Closing the body of " + response
                + " didn't close its backing stream. Bad interceptor?");
          }
    
          request = followUp;//请求变为新请求
          priorResponse = response;//保存此次的response为priorResponse
        }
    

    分支结束Chain,读取RealCall位置

    经过了重重拦截器的处理获得了result,来到了最初的地方

    完结


    这里仅从GET请求出发,就有如此多的内容。要体会OkHttp的深度,不仅仅要具有算法、设计模式、平台特性,这些软件工程师需千锤百炼才能有的技能外,还需要有扎实的网络基础与实际的网络经验。

    恰恰网络方面是我的软肋,这次对OkHttp源码的深入,使我对网络通信中关键的点有了模糊的认识。

    相信能看到这里的人,自己看一遍call.enqueue应该不是问题。

    相关文章

      网友评论

          本文标题:全面深入OkHttp源码(下)

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