OkHttp源码解析(二)

本文深入剖析OkHttp的拦截器链机制,讲解getResponseWithInterceptorChain方法,详细解读RetryAndFollowUpInterceptor与BridgeInterceptor的工作原理及流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一篇讲到OkHttp的整体流程,但是里面有一个很重要的方法getResponseWithInterceptorChain还没有讲到,这个方法里面包括了整个okHttp最重要的拦截器链,所以我们今天来讲解一下。

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);
}

这里创建了一个拦截器list的集合,首先会把用户自定义的拦截器(应用程序拦截器),和okhttp内部提供的拦截器添加进去,而接下来我们着重讲解的是okhttp内部提供的这5个拦截器。然后在第13行这里,创建了一个RealInterceptorChain的一个实例,在这个实例里面把我们刚刚添加完的list集合传递进去,还有一个index值为0也传递进去(这个非常重要),还有request。最后就是调用这个chain的proceed方法,而这个拦截器链式如何运转,秘密应该就是在这个方法里面了,我们就继续往下看:

@Override public Response proceed(Request request) throws IOException {
  return proceed(request, streamAllocation, httpCodec, connection);
}
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
    RealConnection connection) throws IOException {
  if (index >= interceptors.size()) throw new AssertionError();
  calls++;
  // If we already have a stream, confirm that the incoming request will use it.
  if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
    throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
        + " must retain the same host and port");
  }
  // If we already have a stream, confirm that this is the only call to chain.proceed().
  if (this.httpCodec != null && calls > 1) {
    throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
        + " must call proceed() exactly once");
  }
  // Call the next interceptor in the chain.
  RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
      connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
      writeTimeout);
  Interceptor interceptor = interceptors.get(index);
  Response response = interceptor.intercept(next);
  // Confirm that the next interceptor made its required call to chain.proceed().
  if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
    throw new IllegalStateException("network interceptor " + interceptor
        + " must call proceed() exactly once");
  }
  // Confirm that the intercepted response isn't null.
  if (response == null) {
    throw new NullPointerException("interceptor " + interceptor + " returned null");
  }
  if (response.body() == null) {
    throw new IllegalStateException(
        "interceptor " + interceptor + " returned a response with no body");
  }
  return response;
}

看到它这里会继续调用它的proceed重载方法,然后看第19行,它这里又创建了一个新的拦截器,而这个拦截器链跟刚才创建的有什么区别呢?就是传进去的index值是index+1,这样子如果我们下次要进行访问的话,就只能从下一个拦截器进行访问,这样子就可以把我们的整个拦截器构成一个链条。下一行就是获取索引index的拦截器,然后再调用拦截器的intercept方法,并且将刚刚创建好的next拦截器链传递进去。为了分析我们整个拦截器链的流程,我们对第一个拦截器RetryAndFollowUpInterceptor的intercept方法进行粗略分析。

response = realChain.proceed(request, streamAllocation, null, null);

这里这个realChain就是之前创建好的next拦截器链,这里会继续它的proceed方法。调用了proceed方法之后,又会继续创建下一个拦截器链,并且获取当前的拦截器,这样子就构成了我们整个拦截器链的工作流程。

在这里插入图片描述

而在拦截器里面,主要是三个步骤:
1、请求前对request进行处理
2、继续调用下一个拦截器而获取response
3、对response进行处理,返回给上一个拦截器

上面讲完了整个拦截器链的工作流程,我们接下来分析各个拦截器的工作流程。

  • RetryAndFollowUpInterceptor

从名字上可以看得出,它是重定向拦截器,失败重连的。我们来看看它的intercept方法:

@Override public Response intercept(Chain chain) throws IOException {
  Request request = chain.request();
  RealInterceptorChain realChain = (RealInterceptorChain) chain;
  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.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();
      }
    }
    // 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, 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());
    }
    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;
  }
}

我们看到第6行这里创建了一个StreamAllocation,这个是用于获取连接服务器的connection和数据传输的输入输出流。虽然这个实例是在这里创建,但是它并不是在这个拦截器里面执行,而是一步一步往下传递,到后面的拦截器才会被执行。
在第19行这里就会调用realChain.proceed,会继续调用下一个拦截器去获取response。既然是失败重连,那这里又是怎样实现失败重连的呢?我们看一下这里:

Request followUp = followUpRequest(response, streamAllocation.route());
if (followUp == null) {
  if (!forWebSocket) {
    streamAllocation.release();
  }
  return response;
}

if (++followUpCount > MAX_FOLLOW_UPS) {
  streamAllocation.release();
  throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}

如果获取的response是符合条件的话,就会直接把这个response返回。如果不符合条件的话,就会继续往下走,这里有一个MAX_FOLLOW_UPS变量,这个变量是在这个拦截器里面定义的。

private static final int MAX_FOLLOW_UPS = 20;

因为我们重连也不可能无休止地进行重连,所以okhttp这里就规定了重连的次数最多只能是20次。

  • 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");
  }
  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);
  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();
}

这个拦截器主要的作用就是设置我们的内容长度,编码方式,压缩,添加头部信息等。
当获取到response之后,看到最后有一个if的判断,transparentGzip是在我们的设置“Accept-Encoding”时赋值的,这个标志位其实就是告诉服务器我们客户端是支持gzip压缩的,那么这样子服务器才会返回一个支持gzip压缩的response回来。第二个判断其实就是判断服务器返回来的Response是否经过了gzip压缩,根据“Content-Encoding”进行判断。第三个条件就是判断Http头部是否有body。当三个条件都符合的情况下,我们就会将返回的response的body体的输入流转化为GzipSource,这样子的目的就是我们用户可以直接以解压的方式读取这个数据流。

这样子就讲完了前两个拦截器,后面会继续讲解剩下的三个拦截器OkHttp源码解析(三),谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值