okhttp源碼解析(二) 攔截器分析

前言

上篇我們介紹了okhttp整體的流程執(zhí)行,本篇來具體分析每個(gè)攔截器的執(zhí)行贮乳,其中CacheInterceptor和ConnectInterceptor是里面的核心也是比較難的點(diǎn)旋炒。

  • 1.RetryAndFollowUpInterceptor,負(fù)責(zé)失敗重連以及重定向
@Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Call call = realChain.call();
    EventListener eventListener = realChain.eventListener();
    //1.StreamAllocation
    StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
        createAddress(request.url()), call, eventListener, callStackTrace);
    this.streamAllocation = streamAllocation;

    int followUpCount = 0;
    Response priorResponse = null;
    //2.開啟無限循環(huán)
    while (true) {
      if (canceled) {
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response;
      boolean releaseConnection = true;
      try {

        // 3.執(zhí)行下一個(gè)攔截器,即BridgeInterceptor會(huì)將初始化好的連接對(duì)象傳遞給下一個(gè)攔截器
        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.
    //  4.如果有異常,判斷是否要恢復(fù)
        if (!recover(e.getLastConnectException(), streamAllocation, 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, 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();
        }
      }

      //   5.priorResponse 是否為null
      if (priorResponse != null) {
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }
//   6.重定向
      Request followUp = followUpRequest(response, streamAllocation.route());

      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());
      }
//7.檢查是否有相同的鏈接皆看,是:釋放,重建創(chuàng)建
      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;
    }
  }
  • 1.注釋1處創(chuàng)建了一個(gè)StreamAllocation對(duì)象背零,這個(gè)對(duì)象很重要在建立連接網(wǎng)絡(luò)的時(shí)候使用的悬蔽,這里只是初始化用于傳入后面的攔截器使用的,后面會(huì)講到捉兴,這里根據(jù)url創(chuàng)建一個(gè)Address對(duì)象,初始化一個(gè)Socket連接對(duì)象录语,基于Okio用于構(gòu)造StreamAllocation對(duì)象倍啥。
  • 2.開啟了一個(gè)無限循環(huán),如果取消streamAllocation釋放再拋出異常
  • 3.是把request和streamAllocation傳給了下一個(gè)攔截器及BridgeInterceptor
  • 4.如果有異常澎埠,判斷是否要恢復(fù)
  • 5.如果priorResponse不為空虽缕,則說明前面已經(jīng)獲取到了響應(yīng),這里會(huì)結(jié)合當(dāng)前獲取的Response和先前的Response
  • 6.檢查是否需要重定向蒲稳,如果不需要重定向則返回當(dāng)前請(qǐng)求氮趋,這里主要是根據(jù)響應(yīng)碼(code)和響應(yīng)頭(header),查看是否需要重定向
  • 7.檢查是否有相同的鏈接江耀,是就釋放重建創(chuàng)建
  • 2.BridgeInterceptor 負(fù)責(zé)對(duì)Request和Response處理
@Override public Response intercept(Chain chain) throws IOException {
   Request userRequest = chain.request();
   Request.Builder requestBuilder = userRequest.newBuilder();
   //1剩胁。request
   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");
   }

   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", "3.10");
   }

   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();
 }
  • 這個(gè)攔截器比較簡單主要負(fù)責(zé)request請(qǐng)求頭的封裝,cookie祥国,gizp等昵观,以及response響應(yīng)的一些處理,就不再多說了舌稀。
  • 3.CacheInterceptor負(fù)責(zé)對(duì)response緩存的一些處理
@Override public Response intercept(Chain chain) throws IOException {
    //1.獲取緩存的response
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();
    //2.緩存策略
    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.
    }

    //  3.返回code=504
    if (networkRequest == null && cacheResponse == null) {
      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.
    //  4.從緩存里拿response
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }

    Response networkResponse = null;
    try {
      //  5.調(diào)用下一個(gè)攔截器去請(qǐng)求response
      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());
      }
    }

    //  6.根據(jù)條件判斷是用cacheResponse還是networkResponse
    if (cacheResponse != null) {
      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();

        cache.trackConditionalCacheHit();
        cache.update(cacheResponse, response);
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }
    //  7.使用networkResponse
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();
    //  8.存入緩存
    if (cache != null) {
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        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;
  }
  • 1.這里獲取緩存的response啊犬,這里面緩存用到的是DiskLruCache來進(jìn)行存儲(chǔ)
  • 2.根據(jù)緩存策略獲取networkRequest和cacheResponse

 /**
     * Returns a strategy to satisfy {@code request} using the a cached response {@code response}.
     */
    public CacheStrategy get() {
      CacheStrategy candidate = getCandidate();
      if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) {
        return new CacheStrategy(null, null);
      }
      return candidate;
    }
  • 這里調(diào)用get方法來獲取緩存策略,而這個(gè)方法主要通過getCandidate()來獲取壁查,我們看下這個(gè)方法
private CacheStrategy getCandidate() {
      // No cached response.
      //1.沒有緩存
      if (cacheResponse == null) {
        return new CacheStrategy(request, null);
      }
    //2.三次握手失效
      if (request.isHttps() && cacheResponse.handshake() == null) {
        return new CacheStrategy(request, null);
      }
    //3 響應(yīng)不能被緩存
      if (!isCacheable(cacheResponse, request)) {
        return new CacheStrategy(request, null);
      }

      CacheControl requestCaching = request.cacheControl();
      if (requestCaching.noCache() || hasConditions(request)) {
        return new CacheStrategy(request, null);
      }

      CacheControl responseCaching = cacheResponse.cacheControl();
      if (responseCaching.immutable()) {
        return new CacheStrategy(null, cacheResponse);
      }

      long ageMillis = cacheResponseAge();
      long freshMillis = computeFreshnessLifetime();

      if (requestCaching.maxAgeSeconds() != -1) {
        freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds()));
      }

      long minFreshMillis = 0;
      if (requestCaching.minFreshSeconds() != -1) {
        minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds());
      }

      long maxStaleMillis = 0;
      if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) {
        maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds());
      }

      if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
        Response.Builder builder = cacheResponse.newBuilder();
        if (ageMillis + minFreshMillis >= freshMillis) {
          builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\"");
        }
        long oneDayMillis = 24 * 60 * 60 * 1000L;
        if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
          builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
        }
        return new CacheStrategy(null, builder.build());
      }
      // Find a condition to add to the request. If the condition is satisfied, the response body
      // will not be transmitted.
      String conditionName;
      String conditionValue;
      if (etag != null) {
        conditionName = "If-None-Match";
        conditionValue = etag;
      } else if (lastModified != null) {
        conditionName = "If-Modified-Since";
        conditionValue = lastModifiedString;
      } else if (servedDate != null) {
        conditionName = "If-Modified-Since";
        conditionValue = servedDateString;
      } else {
        return new CacheStrategy(request, null); // No condition! Make a regular request.
      }

      Headers.Builder conditionalRequestHeaders = request.headers().newBuilder();
      Internal.instance.addLenient(conditionalRequestHeaders, conditionName, conditionValue);

      Request conditionalRequest = request.newBuilder()
          .headers(conditionalRequestHeaders.build())
          .build();
      return new CacheStrategy(conditionalRequest, cacheResponse);
    }
  • 沒有緩存就換返回一個(gè)response為null的CacheStrategy觉至,代表沒有緩存,返回之后就會(huì)執(zhí)行下一個(gè)攔截器去請(qǐng)求網(wǎng)絡(luò)獲取response睡腿。
  • 如果是https并且三次握手失效返回和1一樣语御。
  • 代表響應(yīng)不能被緩存
  • 之后就是根據(jù)CacheControl來控制返回的結(jié)果峻贮,用大量的if/else判斷緩存是否失效

  • 3.回到我們CacheInterceptor的intercept方法的注釋3,緩存失效且onlyIfCached為ture就執(zhí)行這返回504.
  • 4.根據(jù)我們配置的CacheControl如果設(shè)置了ForceCache或者M(jìn)ax-age代表緩存未失效沃暗,則就會(huì)執(zhí)行這月洛,不去請(qǐng)求網(wǎng)絡(luò)直接使用緩存。
  • 5.緩存失效孽锥,調(diào)用后面的攔截器獲取Response嚼黔。
  • 6.網(wǎng)絡(luò)獲取的networkResponse與cacheResponse做對(duì)比來判斷使用哪個(gè)response,code==304代表緩存有效惜辑,則使用緩存并通過cache.update方法更新緩存唬涧。
  • 7.緩存過期了則使用networkResponse并存入緩存

  • 這里再說下DiskLruCache,用來做緩存的類
    //用于緩存的集合
    final LinkedHashMap<String, Entry> lruEntries = new LinkedHashMap<>(0, 0.75f, true);
    //用于清理緩存的線程池
    private final Executor executor;
    //用于清理緩存的任務(wù)
    private final Runnable cleanupRunnable = new Runnable() {
        public void run() {
            synchronized (DiskLruCache.this) {
                if (!initialized | closed) {
                    return; // Nothing to do
                }
                try {
                    trimToSize();
                } catch (IOException ignored) {
                    mostRecentTrimFailed = true;
                }

                try {
                    if (journalRebuildRequired()) {
                        rebuildJournal();
                        redundantOpCount = 0;
                    }
                } catch (IOException e) {
                    mostRecentRebuildFailed = true;
                    journalWriter = Okio.buffer(Okio.blackhole());
                }
            }
        }
    };
  • 用lruEntries 也就是lru算法執(zhí)行刪除操作盛撑,通過BufferedSource以及BufferedSink進(jìn)行讀寫操作大大提升了效率碎节。

  • 運(yùn)用線程池去執(zhí)行緩存的清理的任務(wù)

  • 每一個(gè)url請(qǐng)求cache有四個(gè)文件,兩個(gè)狀態(tài)(DIRY抵卫,CLEAN)狮荔,每個(gè)狀態(tài)對(duì)應(yīng)兩個(gè)文件:一個(gè)0文件對(duì)應(yīng)存儲(chǔ)meta數(shù)據(jù),一個(gè)文件存儲(chǔ)body數(shù)據(jù)介粘。

  • 這里面的邏輯很復(fù)雜有興趣的可以去具體去了解下殖氏。

  • CacheInterceptor核心還是http協(xié)議緩存知識(shí)來配置我們的CacheControl來控制緩存的策略。

  • 4.ConnectInterceptor姻采,向服務(wù)器發(fā)起連接
  @Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    //1.得到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");
  //2.得到httpCodec 
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
  //3.得到connection 
    RealConnection connection = streamAllocation.connection();
    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }
  • 1.得到streamAllocation對(duì)象 也就是RetryAndFollowUpInterceptor中初始化的在這里使用到了
  • 2.通過streamAllocation.newStream得到了httpCodec

  private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
      int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
      boolean doExtensiveHealthChecks) throws IOException {
    while (true) {
      RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
          pingIntervalMillis, connectionRetryEnabled);

      // If this is a brand new connection, we can skip the extensive health checks.
      synchronized (connectionPool) {
        if (candidate.successCount == 0) {
          return candidate;
        }
      }
      if (!candidate.isHealthy(doExtensiveHealthChecks)) {
        noNewStreams();
        continue;
      }
      return candidate;
    }
  }


 private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
      int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
  //.........

      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) {
            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);
        acquire(result, false);
      }
    }

    // If we found a pooled connection on the 2nd time around, we're done.
    if (foundPooledConnection) {
      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);

  //.........
    return result;
  }
  • StreamAllocation的newStream()內(nèi)部其實(shí)是通過findHealthyConnection()方法獲取一個(gè)RealConnection雅采,而在findHealthyConnection()里面通過一個(gè)while(true)死循環(huán)不斷去調(diào)用findConnection()方法去再ConnectionPool中找RealConnection,找不到則直接new一個(gè)RealConnection慨亲。然后開始握手婚瓜,握手結(jié)束后,把連接加入連接池刑棵,如果在連接池有重復(fù)連接巴刻,和合并連接。
public void connect(int connectTimeout, int readTimeout, int writeTimeout,
      int pingIntervalMillis, boolean connectionRetryEnabled, Call call,
      EventListener eventListener) {
 //........

    while (true) {
      try {
        if (route.requiresTunnel()) {
          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);
        }
        establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
        eventListener.connectEnd(call, route.socketAddress(), route.proxy(), protocol);
        break;
      } catch (IOException e) {
    //...

  private void connectTunnel(int connectTimeout, int readTimeout, int writeTimeout, Call call,
      EventListener eventListener) throws IOException {
    Request tunnelRequest = createTunnelRequest();
    HttpUrl url = tunnelRequest.url();
    for (int i = 0; i < MAX_TUNNEL_ATTEMPTS; i++) {
      connectSocket(connectTimeout, readTimeout, call, eventListener);
      tunnelRequest = createTunnel(readTimeout, writeTimeout, tunnelRequest, url);

      if (tunnelRequest == null) break; // Tunnel successfully created.

      // The proxy decided to close the connection after an auth challenge. We need to create a new
      // connection, but this time with the auth credentials.
      closeQuietly(rawSocket);
      rawSocket = null;
      sink = null;
      source = null;
      eventListener.connectEnd(call, route.socketAddress(), route.proxy(), null);
    }
  }

  private void connectSocket(int connectTimeout, int readTimeout, Call call,
      EventListener eventListener) throws IOException {
    Proxy proxy = route.proxy();
    Address address = route.address();

    rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
        ? address.socketFactory().createSocket()
        : new Socket(proxy);

    eventListener.connectStart(call, route.socketAddress(), proxy);
    rawSocket.setSoTimeout(readTimeout);
    try {
      Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
    } catch (ConnectException e) {
      ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
      ce.initCause(e);
      throw ce;
    }
    try {
      source = Okio.buffer(Okio.source(rawSocket));
      sink = Okio.buffer(Okio.sink(rawSocket));
    } catch (NullPointerException npe) {
      if (NPE_THROW_WITH_NULL.equals(npe.getMessage())) {
        throw new IOException(npe);
      }
    }
  }

  private void establishProtocol(ConnectionSpecSelector connectionSpecSelector,
      int pingIntervalMillis, Call call, EventListener eventListener) throws IOException {
    if (route.address().sslSocketFactory() == null) {
      protocol = Protocol.HTTP_1_1;
      socket = rawSocket;
      return;
    }

    eventListener.secureConnectStart(call);
    connectTls(connectionSpecSelector);
    eventListener.secureConnectEnd(call, handshake);

    if (protocol == Protocol.HTTP_2) {
      socket.setSoTimeout(0); // HTTP/2 connection timeouts are set per-stream.
      http2Connection = new Http2Connection.Builder(true)
          .socket(socket, route.address().url().host(), source, sink)
          .listener(this)
          .pingIntervalMillis(pingIntervalMillis)
          .build();
      http2Connection.start();
    }
  }
  • RealConnection的connect()這個(gè)方法很重要蛉签。RealConnection的connect()是StreamAllocation調(diào)用的冈涧。在RealConnection的connect()的方法里面也是一個(gè)while(true)的循環(huán),里面判斷是隧道連接還是普通連接正蛙,如果是隧道連接就走connectTunnel()督弓,如果是普通連接則走connectSocket(),最后建立協(xié)議乒验。connectSocket()里面就是通過okio獲取source與sink愚隧。establishProtocol()方法建立連接,里面判斷是是HTTP/1.1還是HTTP/2.0。如果是HTTP/2.0則通過Builder來創(chuàng)建一個(gè)Http2Connection對(duì)象狂塘,并且調(diào)用Http2Connection對(duì)象的start()方法录煤。所以判斷一個(gè)RealConnection是否是HTTP/2.0其實(shí)很簡單,判斷RealConnection對(duì)象的http2Connection屬性是否為null即可荞胡,因?yàn)橹挥蠬TTP/2的時(shí)候http2Connection才會(huì)被賦值妈踊。
  • connectSocket()具體實(shí)現(xiàn)是AndroidPlatform.java里面的connectSocket(),通過socket建立連接

  • 3.通過streamAllocation得到connection泪漂,最后再傳入到下一個(gè)攔截器

  • 5.CallServerInterceptor廊营,負(fù)責(zé)向服務(wù)器發(fā)送請(qǐng)求以及接受請(qǐng)求
 @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());
 //1.通過httpCodec寫入請(qǐng)求頭
   httpCodec.writeRequestHeaders(request);
   realChain.eventListener().requestHeadersEnd(realChain.call(), request);

   Response.Builder responseBuilder = null;
   if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
  
     if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
       httpCodec.flushRequest();
       realChain.eventListener().responseHeadersStart(realChain.call());
       responseBuilder = httpCodec.readResponseHeaders(true);
     }

     if (responseBuilder == null) {
       // Write the request body if the "Expect: 100-continue" expectation was met.
    //2.寫入請(qǐng)求體
       realChain.eventListener().requestBodyStart(realChain.call());
       long contentLength = request.body().contentLength();
       CountingSink requestBodyOut =
           new CountingSink(httpCodec.createRequestBody(request, contentLength));
       BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);

       request.body().writeTo(bufferedRequestBody);
       bufferedRequestBody.close();
       realChain.eventListener()
           .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
     } else if (!connection.isMultiplexed()) {
       streamAllocation.noNewStreams();
     }
   }

   httpCodec.finishRequest();
//3.讀響應(yīng)頭
   if (responseBuilder == null) {
     realChain.eventListener().responseHeadersStart(realChain.call());
     responseBuilder = httpCodec.readResponseHeaders(false);
   }

   Response response = responseBuilder
       .request(request)
       .handshake(streamAllocation.connection().handshake())
       .sentRequestAtMillis(sentRequestMillis)
       .receivedResponseAtMillis(System.currentTimeMillis())
       .build();

   int code = response.code();
   if (code == 100) {
     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);
//3.讀響應(yīng)體
   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();
   }

//.....
   return response;
 }
  • 該攔截器比較簡單,看代碼注釋就能懂萝勤,就是發(fā)送請(qǐng)求頭請(qǐng)求體露筒,然后讀取響應(yīng)頭及響應(yīng)體。


    5713484-a89f541f0f7797a6.png
  • 感謝 http://www.reibang.com/p/6166d28983a2
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末敌卓,一起剝皮案震驚了整個(gè)濱河市慎式,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌趟径,老刑警劉巖瘪吏,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蜗巧,居然都是意外死亡肪虎,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門惧蛹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人刑枝,你說我怎么就攤上這事香嗓。” “怎么了装畅?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵靠娱,是天一觀的道長。 經(jīng)常有香客問我掠兄,道長像云,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任蚂夕,我火速辦了婚禮迅诬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘婿牍。我一直安慰自己侈贷,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布等脂。 她就那樣靜靜地躺著俏蛮,像睡著了一般撑蚌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上搏屑,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天争涌,我揣著相機(jī)與錄音,去河邊找鬼辣恋。 笑死亮垫,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的抑党。 我是一名探鬼主播包警,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼底靠!你這毒婦竟也來了害晦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤暑中,失蹤者是張志新(化名)和其女友劉穎壹瘟,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鳄逾,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡所灸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年宇姚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡儒士,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瓢宦,到底是詐尸還是另有隱情造烁,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布汽摹,位于F島的核電站李丰,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏逼泣。R本人自食惡果不足惜趴泌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拉庶。 院中可真熱鬧嗜憔,春花似錦、人聲如沸氏仗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至帚稠,卻和暖如春谣旁,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背滋早。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國打工榄审, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人杆麸。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓搁进,卻偏偏與公主長得像,于是被迫代替她去往敵國和親昔头。 傳聞我的和親對(duì)象是個(gè)殘疾皇子饼问,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345