美文网首页Android开发Android开发经验谈Android开发
Android之OkHttp源码解析(2)- 拦截器

Android之OkHttp源码解析(2)- 拦截器

作者: 居居居居居居x | 来源:发表于2019-11-22 16:29 被阅读0次

    前言

    经过Android之OkHttp源码解析(1)- 请求过程的请求网络流程解析源码之后,我们本篇将会讲解OkHttp拦截器,这里也是OkHttp设计最核心的一个部分。版本为3.7。

    拦截器概括

    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);
        return chain.proceed(originalRequest);
    }
    

    getResponseWithInterceptorChain()方法里会进行拦截器链的创建,总共有5个拦截器,里面分别有:

    • 重定向拦截器(RetryAndFollowUpInterceptor)
    • 内容桥拦截器(BridgeInterceptor)
    • 缓存拦截器(CacheInterceptor)
    • 链接拦截器(ConnectInterceptor)
    • 网络请求拦截器(CallServerInterceptor)
    拦截器流程图,侵删

    重定向拦截器(RetryAndFollowUpInterceptor)

      @Override public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        //创建一个StreamAllocation
        streamAllocation = new StreamAllocation(
            client.connectionPool(), createAddress(request.url()), callStackTrace);
        
        //这里开始统计重定向次数
        int followUpCount = 0;
        Response priorResponse = null;
        while (true) {
          if (canceled) {
            streamAllocation.release();
            throw new IOException("Canceled");
          }
    
          Response response = null;
          boolean releaseConnection = true;
          try {
            //这里调用下一个的interceptor来获得响应内容
            response = ((RealInterceptorChain) chain).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(), false, request)) {
              throw e.getLastConnectException();
            }
            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, 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 = followUpRequest(response);
    
          if (followUp == null) {
            if (!forWebSocket) {
              streamAllocation.release();
            }
            return response;
          }
    
          closeQuietly(response.body());
          //这里判断大于重定向次数,次数为20
          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()), callStackTrace);
          } 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;
        }
      }
    

    拦截器链开始执行的时候,会调用里面每个拦截器的intercept(),我们从第一个RetryAndFollowUpInterceptor开始看,它主要功能其实就是负责失败自动重连和必要的重定向负责失败自动重连和必要的重定向,其实就是在网络请求失败后进行重试,当服务器返回当前请求需要进行重定向时直接发起新的请求,并在条件允许情况下复用当前连接,里面最重要的是里面会调用((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null)去调用下一个拦截器,这里就是责任链模式的体现了。

    内容桥拦截器(BridgeInterceptor)

      @Override public Response intercept(Chain chain) throws IOException {
        Request userRequest = chain.request();
        Request.Builder requestBuilder = userRequest.newBuilder();
        
        //这里开始添加一些请求头信息
        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) {
            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");
        }
        
        //建立Cookie内容
        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 networkResponse = chain.proceed(requestBuilder.build());
    
        HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
    
        Response.Builder responseBuilder = networkResponse.newBuilder()
            .request(userRequest);
        //gzip压缩内容
        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);
          responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
        }
    
        return responseBuilder.build();
      }
    

    BridgeInterceptor中,添加了必要的请求头信息,设置了gzip压缩,并在接收到内容后进行解压。省去了应用层处理数据解压的麻烦,还有添加了cookie,还有设置了其他报头,如User-Agent,Host,Keep-alive等。其中Keep-Alive是实现多路复用的必要步骤,整个步骤其实就是将用户的Request转换成网络实际请求的Request。然后调用下一个拦截器获取response,转换成用户的response。

    缓存拦截器(CacheInterceptor)

      @Override public Response intercept(Chain chain) throws IOException {
        //cache不为空的话获取cacheCandidate
        Response cacheCandidate = cache != null
            ? cache.get(chain.request())
            : null;
    
        long now = System.currentTimeMillis();
        //获取缓存策略
        CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
        //获取网络请求
        Request networkRequest = strategy.networkRequest;
        //获取响应策略
        Response cacheResponse = strategy.cacheResponse;
    
        if (cache != null) {
          //同步方法
          cache.trackResponse(strategy);
        }
    
        if (cacheCandidate != null && cacheResponse == null) {
          closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
        }
    
        // If we're forbidden from using the network and the cache is insufficient, fail.
        if (networkRequest == null && cacheResponse == null) {
          //没网没缓存返回504
          return new Response.Builder()
              .request(chain.request())
              .protocol(Protocol.HTTP_1_1)
              .code(504)
              .message("Unsatisfiable Request (only-if-cached)")
              .body(Util.EMPTY_RESPONSE)
              .sentRequestAtMillis(-1L)
              .receivedResponseAtMillis(System.currentTimeMillis())
              .build();
        }
    
        // If we don't need the network, we're done.
        if (networkRequest == null) {
          return cacheResponse.newBuilder()
              .cacheResponse(stripBody(cacheResponse))
              .build();
        }
    
        Response networkResponse = null;
        try {
            //调用下一个拦截器
          networkResponse = chain.proceed(networkRequest);
        } finally {
          // If we're crashing on I/O or otherwise, don't leak the cache body.
          if (networkResponse == null && cacheCandidate != null) {
            closeQuietly(cacheCandidate.body());
          }
        }
    
        // If we have a cache response too, then we're doing a conditional get.
        //返回缓存的内容
        if (cacheResponse != null) {
          //有响应缓存并且响应码为304,直接使用缓存,通过cacheResponse创建response并返回
          if (networkResponse.code() == HTTP_NOT_MODIFIED) {
            Response response = cacheResponse.newBuilder()
                .headers(combine(cacheResponse.headers(), networkResponse.headers()))
                .sentRequestAtMillis(networkResponse.sentRequestAtMillis())
                .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
                .cacheResponse(stripBody(cacheResponse))
                .networkResponse(stripBody(networkResponse))
                .build();
            networkResponse.body().close();
    
            // 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;
          } else {
            closeQuietly(cacheResponse.body());
          }
        }
    
        Response response = networkResponse.newBuilder()
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
    
        if (cache != null) {
          if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
            // Offer this request to the cache.
            //cache不为空,有请求有和缓存策略,通过put方法进行缓存
            CacheRequest cacheRequest = cache.put(response);
            return cacheWritingResponse(cacheRequest, response);
          }
    
          if (HttpMethod.invalidatesCache(networkRequest.method())) {
            try {
              cache.remove(networkRequest);
            } catch (IOException ignored) {
              // The cache cannot be written.
            }
          }
        }
    
        return response;
      }
    

    CacheInterceptor中,首先判断cache不为空的话,获取到cacheCandidate,然后接着依次获取缓存策略,网络请求,响应策略这些,主要是通过CacheStrategy.Factory()这个实例来获取,接着执行cache的trackResponse()的同步方法,来保证一致性。然后就是如果判断没有网络也没有缓存的话,就会直接构建一个response并返回504。然后接下来的内容会交给下一个拦截器。如果有响应缓存的话,并且响应码为304的话。直接使用缓存,通过cacheResponse创建response并返回。最后如果cache不为空,而且有请求有和缓存策略,通过put()方法进行缓存。

    链接拦截器(ConnectInterceptor)

      @Override public Response intercept(Chain chain) throws IOException {
        RealInterceptorChain realChain = (RealInterceptorChain) chain;
        Request request = realChain.request();
        //获取StreamAllocation对象
        StreamAllocation streamAllocation = realChain.streamAllocation();
    
        // We need the network to satisfy this request. Possibly for validating a conditional GET.
        boolean doExtensiveHealthChecks = !request.method().equals("GET");
        HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
        RealConnection connection = streamAllocation.connection();
        
        //交给下一个拦截器。
        return realChain.proceed(request, streamAllocation, httpCodec, connection);
      }
    

    ConnectInterceptor就相对少了很多。主要是通过realChain.streamAllocation()获取到StreamAllocation对象,注意一下,这里的ConnectInterceptor初始化是在RealInterceptorChain这个类完成的。而且设置了链接池、url路径等。其实主要供就是配置RealInterceptorChain。

    网络请求拦截器(CallServerInterceptor)

      @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();
        //写入请求头信息
        httpCodec.writeRequestHeaders(request);
    
        Response.Builder responseBuilder = null;
        //写入body信息
        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"))) {
            httpCodec.flushRequest();
            responseBuilder = httpCodec.readResponseHeaders(true);
          }
    
          if (responseBuilder == null) {
            // Write the request body if the "Expect: 100-continue" expectation was met.
            Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
            BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
            request.body().writeTo(bufferedRequestBody);
            bufferedRequestBody.close();
          } else if (!connection.isMultiplexed()) {
            // 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) {
          responseBuilder = httpCodec.readResponseHeaders(false);
        }
    
        Response response = responseBuilder
            .request(request)
            .handshake(streamAllocation.connection().handshake())
            .sentRequestAtMillis(sentRequestMillis)
            .receivedResponseAtMillis(System.currentTimeMillis())
            .build();
    
        int code = response.code();
        if (forWebSocket && code == 101) {
          // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
          response = response.newBuilder()
              .body(Util.EMPTY_RESPONSE)
              .build();
        } else {
          response = response.newBuilder()
              .body(httpCodec.openResponseBody(response))
              .build();
        }
    
        if ("close".equalsIgnoreCase(response.request().header("Connection"))
            || "close".equalsIgnoreCase(response.header("Connection"))) {
          streamAllocation.noNewStreams();
        }
        //判断响应码是204还是205,然后抛出异常。
        if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
          throw new ProtocolException(
              "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
        }
    
        return response;
      }
    

    这个拦截器就是真正发起请求和处理返回响应的拦截器,其实主要就是做了两个工作,发起请求,然后处理返回的响应,中间还会有一些封装请求信息等操作。CallServerInterceptor一开始就是先获取之前各个拦截器的参数,然后写入头信息,body信息,然后读取响应信息,会先读取响应头信息,响应如果读取或者创建完成后会 通过streamAllocation.noNewStreams();这个方法进行关闭流,还会判断如果响应码是204或者205的话抛出一个异常。最后返回一个response。到这里5个拦截器就全部解析完成了。

    参考

    相关文章

      网友评论

        本文标题:Android之OkHttp源码解析(2)- 拦截器

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