美文网首页andnroidokhttp
okHttp拦截器分析(一)

okHttp拦截器分析(一)

作者: 放码过来吧 | 来源:发表于2018-09-04 18:08 被阅读118次

    之前分析了okHttp的同步跟异步流程分析,都是经过层层封装,然后卡在拦截器这里,关键封装请求头,建立连接,获取数据的部分全在拦截器这部分,由于拦截器较多,分析同步,异步的时候并没有详细解析,在这里,一一解析。


    image.png

    回顾下getResponseWithInterceptorChain()方法:

      final RetryAndFollowUpInterceptor retryAndFollowUpInterceptor;
      ......
     Response getResponseWithInterceptorChain() throws IOException {
        // Build a full stack of interceptors.
        List<Interceptor> interceptors = new ArrayList<>();
        interceptors.addAll(client.interceptors());
        interceptors.add(retryAndFollowUpInterceptor);
        interceptors.add(new BridgeInterceptor(client.cookieJar()));
        interceptors.add(new CacheInterceptor(client.internalCache()));
        interceptors.add(new ConnectInterceptor(client));
        if (!forWebSocket) {
          interceptors.addAll(client.networkInterceptors());
        }
        interceptors.add(new CallServerInterceptor(forWebSocket));
    
        Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
            originalRequest, this, eventListener, client.connectTimeoutMillis(),
            client.readTimeoutMillis(), client.writeTimeoutMillis());
    
        return chain.proceed(originalRequest);
      }
    

    interceptors是list集合,首先就把自定义的拦截器add进去了,在这里,假装没有写自定义拦截器,来看看retryAndFollowUpInterceptor。看看注释:This interceptor recovers from failures and follows redirects as necessary。很明显,这个拦截器是用来负责失败重试以及重定向的,我们来看看关键代码:

    public final class RetryAndFollowUpInterceptor implements Interceptor {
      ......
      ......
     @Override public Response intercept(Chain chain) throws IOException {
        //获取请求体request
        Request request = chain.request();
       //获取拦截器链对象,这个很重要,是用来运行完拦截器集合的关键(内部的proceed()方法)
        RealInterceptorChain realChain = (RealInterceptorChain) chain;
        //这个call之前介绍过,在这里获取这个call对象
        Call call = realChain.call();
        //状态监听
        EventListener eventListener = realChain.eventListener();
        //连接池,获取一个正确的连接
        StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
            createAddress(request.url()), call, eventListener, callStackTrace);
        this.streamAllocation = streamAllocation;
    
        int followUpCount = 0;
        Response priorResponse = null;
        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;
          } 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.
          //前一个重试获取的response存在
          if (priorResponse != null) {
            response = response.newBuilder()
                .priorResponse(priorResponse.newBuilder()
                        .body(null)
                        .build())
                .build();
          }
    
          Request followUp;
          try {
          //封装新的重试Request,为它添加一些内容,比如验证头
            followUp = followUpRequest(response, streamAllocation.route());
          } catch (IOException e) {
            streamAllocation.release();
            throw e;
          }
          
          //请求体为空,返回response。莫非是请求成功,请求体是空的?反正我是这么想的。
          if (followUp == null) {
            if (!forWebSocket) {
              streamAllocation.release();
            }
            return response;
          }
    
          closeQuietly(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())) {
            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;
        }
      }
    }
    

    这个拦截器,从上往下看下来,可以这么理解,当一个请求,由于各种原因,获取数据失败了,就尝试着去重连恢复。否则就重新封装request(followUp),通过拦截器链继续执行这个请求。源码中有个
    if(followUp == null) 就返回response,当followUp == null的时候,这个重试流程就结束了。

    很好,接下来,我们来看BridgeInterceptor,先来看下一张图(网络上找的):


    image.png

    图中很清晰的说明这个拦截器的作用:给request添加请求头,给resonpse添加响应头。

    放码过来吧(关键代码):

    public final class BridgeInterceptor implements Interceptor {
      ......
    @Override public Response intercept(Chain chain) throws IOException {
        Request userRequest = chain.request();
        Request.Builder requestBuilder = userRequest.newBuilder();
        
        //给request添加contentType
        RequestBody body = userRequest.body();
        if (body != null) {
          MediaType contentType = body.contentType();
          if (contentType != null) {
            requestBuilder.header("Content-Type", contentType.toString());
          }
    
          long contentLength = body.contentLength();
          if (contentLength != -1) { //如果body有值,请求头设置body的内容长度,并且移除传输编码
            requestBuilder.header("Content-Length", Long.toString(contentLength));
            requestBuilder.removeHeader("Transfer-Encoding");
          } else {  //否则,设置传输编码(这里是分块传输编码),移除内容长度值
            requestBuilder.header("Transfer-Encoding", "chunked");
            requestBuilder.removeHeader("Content-Length");
          }
        }
    
        if (userRequest.header("Host") == null) {
          requestBuilder.header("Host", hostHeader(userRequest.url(), false));
        }
    
        if (userRequest.header("Connection") == null) {
          requestBuilder.header("Connection", "Keep-Alive");
        }
    
        // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
        // the transfer stream.
        boolean transparentGzip = false;
        if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
          transparentGzip = true;
          requestBuilder.header("Accept-Encoding", "gzip");
        }
    
        List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
        if (!cookies.isEmpty()) {
          requestBuilder.header("Cookie", cookieHeader(cookies));
        }
    
        if (userRequest.header("User-Agent") == null) {
          requestBuilder.header("User-Agent", Version.userAgent());
        }
        
        //这里开始,是给response添加header
        Response networkResponse = chain.proceed(requestBuilder.build());
    
        HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
    
        Response.Builder responseBuilder = networkResponse.newBuilder()
            .request(userRequest);
    
        if (transparentGzip
            && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
            && HttpHeaders.hasBody(networkResponse)) {
          GzipSource responseBody = new GzipSource(networkResponse.body().source());
          Headers strippedHeaders = networkResponse.headers().newBuilder()
              .removeAll("Content-Encoding")
              .removeAll("Content-Length")
              .build();
          responseBuilder.headers(strippedHeaders);
          String contentType = networkResponse.header("Content-Type");
          responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
        }
    
        return responseBuilder.build();
      }
    }
    

    有点意思,专门搞个拦截器,给请求前的request,返回后的response添加header内容。

    篇幅过长,下篇继续分析后面的拦截器,预知后事如何,且看下集分解。

    相关文章

      网友评论

        本文标题:okHttp拦截器分析(一)

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