OkHttp原理解析2(拦截器篇)(更新中...)

Hello小伙伴们,现在公司已经恢复了正常办公,但是疫情依旧还在继续。最近工作实在是有点小忙,导致更新有点缓慢,实在抱歉,本文是OkHttp原理解析的第二篇, 主要针对OkHttp中几个 默认拦截器 的具体实现逻辑进行分析。
因为OkHttp的很大一部分逻辑都在拦截器中,因此本文会比较长,同时采用连载更新的方式进行描述,每完成一个拦截器的逻辑分析都会进行更新。
如有对OkHttp的框架流程不太了解的可优先阅读网我上篇博客 OkHttp原理解析1(框架流程篇)

我又要开始表演了~~~
但为了方便后续描述,我还是简单对上文做了个总结。大概分为以下几步。

  1. 创建OkHttpClient(可通过OkHttpClient.Builder创建,内部设置一些基础参数)

  2. 创建 Request 对象设置 url,请求类型等参数

  3. 通过OkHttpClient.newCall()创建RealCall对象,同时创建了Transmitter对象。

  4. 通过RealCall.enqueue()或者RealCall.execute()触发 异步或同步 请求。

  5. 调用OkHttpClient.dispatcher().enqueue(new AsyncCall(responseCallback))做请求准备,循环runningAsyncCalls和readyAsyncCalls 队列找出host相同的AsyncCall进行重用,并将readyAsyncCallsAsyncCall转移到runningAsyncCalls中,如果runningAsyncCalls超过64则终止转移,如相同主机计数器>5则终止转移本AsyncCall。

  6. 循环runningAsyncCalls调用AsyncCall.executeOn(executorService())

    6.1. AsyncCall为Runnable,执行run()方法,调用AsyncCall.execute()连带调用AsyncCall.getResponseWithInterceptorChain()设置拦截器List,首先设置用户自定义的拦截器。最后通过RealInterceptorChain.proceed()启动拦截器。

而拦截器的启动与运行依赖 责任链 模式,大概分为以下3步。

  1. 首先创建RealInterceptorChain对象,通过procee()判断各种异常,并获取当前Interceptor对象

  2. 然后 通过Interceptor.intercept(RealInterceptorChain)启动当前拦截器逻辑,并且触发下一个拦截器启动

  3. 如果当前拦截器出现异常等错误,则终止责任链

我们从添加拦截的的位置开始本文介绍,其实上文已经做了描述,源码如下所示。

//RealCall.getResponseWithInterceptorChain();
  Response getResponseWithInterceptorChain() throws IOException {
    // 建立一个完整的拦截器堆栈
    List<Interceptor> interceptors = new ArrayList<>();
     //将创建okhttpclient时的拦截器添加到interceptors
    interceptors.addAll(client.interceptors());
    //重试拦截器,负责处理失败后的重试与重定向
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    //请求转化拦截器(用户请求转为服务器请求,服务器响应转为用户响应)
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //缓存拦截器。负责
    //1.根据条件,缓存配置,有效期等返回缓存响应,也可增加到缓存。
    //2.设置请求头(If-None-Match、If-Modified-Since等) 服务器可能返回304(未修改)
    //3.可配置自定义的缓存拦截器。
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //网络连接拦截器,主要负责和服务器建立连接。
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      //创建okhttpclient时设置的networkInterceptors
      interceptors.addAll(client.networkInterceptors());
    }
    //数据流拦截器,主要负责像服务器发送和读取数据,请求报文封装和解析。
    interceptors.add(new CallServerInterceptor(forWebSocket));
    //责任链模式的创建。
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
        originalRequest, this, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    boolean calledNoMoreExchanges = false;
    try {
      //启动责任链
      Response response = chain.proceed(originalRequest);
      if (transmitter.isCanceled()) {
        closeQuietly(response);
        throw new IOException("Canceled");
      }
      return response;
    } catch (IOException e) {
      calledNoMoreExchanges = true;
      throw transmitter.noMoreExchanges(e);
    } finally {
      if (!calledNoMoreExchanges) {
        transmitter.noMoreExchanges(null);
      }
    }
  }

主要看我注释的部分,OkHttp主要涉及了以下几个默认拦截器

  1. client.interceptors();//用户自定义拦截器

  2. RetryAndFollowUpInterceptor(client)//失败重试拦截器

  3. BridgeInterceptor(client.cookieJar())//请求转化拦截器

  4. CacheInterceptor(client.internalCache())//缓存拦截器

  5. ConnectInterceptor(client)//网络连接拦截器

  6. CallServerInterceptor(forWebSocket)//数据流拦截器

OkHttp会把用户自定义的拦截器默认放到拦截器列表的头部,以方面优先执行,然后通过创建RealInterceptorChain对象,并调用RealInterceptorChain.proceed()启动第一个拦截器,然后调用拦截器的interceptor.intercept(next)执行第一个拦截器的逻辑并将下一个拦截器RealInterceptorChain对象传入依此类推。接下来我们就一个个进行分析。

1. RetryAndFollowUpInterceptor(client) 连接失败重试拦截器

首先开始的是RetryAndFollowUpInterceptor失败重试拦截器,这个拦截器是可以在OkHttpClient.Builder对象中通过retryOnConnectionFailure(boolean retryOnConnectionFailure)设置是否开启,默认构建的OkHttpClient对象是开启的。
该拦截器主要的职责官方注释解释了这么几点。

  1. 无法访问ip地址,如果URL的主机有多个IP地址,则无法访问任何单个IP地址不会使整个请求失败。这可以提高多宿服务的可用性。

  2. 过时的池连接,通过ConnectionPool重用套接字以减少请求延迟,但这些连接偶尔会超时。

  3. 无法访问的代理服务器,可以使用ProxySelector,最终返回到直接连接。

这描述略显抽象,咱们还是通过源码看下真像吧。

//我们应该尝试多少重定向和验证挑战?Chrome遵循21个重定向;Firefox、curl和wget遵循20;Safari遵循16;HTTP/1.0建议5。
private static final int MAX_FOLLOW_UPS = 20;

@Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Transmitter transmitter = realChain.transmitter();

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      //通过transmitter创建ExchangeFinder,Address,RouteSelector三个对象
      transmitter.prepareToConnect(request);
      //判断如果当前请求结束了则抛出异常,可通过transmitter终止请求。
      if (transmitter.isCanceled()) {
        throw new IOException("Canceled");
      }

      Response response;
      boolean success = false;
      try {
        //启动下一个拦截器
        response = realChain.proceed(request, transmitter, null);
        //执行顺利则通过该字段退出循环。
        success = true;
      } catch (RouteException e) {
        // 如果是路由异常。请求还没发送。
        if (!recover(e.getLastConnectException(), transmitter, false, request)) {
          throw e.getFirstConnectException();
        }
        continue;
      } catch (IOException e) {
        // 尝试与服务器通信失败。请求可能已发送。
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        if (!recover(e, transmitter, requestSendStarted, request)) throw e;
        continue;
      } finally {
        //网络调用引发异常。释放所有资源。
        if (!success) {
          transmitter.exchangeDoneDueToException();
        }
      }

      // 如果body不为空
      if (priorResponse != null) {
        //获得新的response
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }

      Exchange exchange = Internal.instance.exchange(response);
      Route route = exchange != null ? exchange.connection().route() : null;
      // 调用followUpRequest()查看响应是否需要重定向,不需要就返回当前请求,如果需要返回新的请求
      Request followUp = followUpRequest(response, route);
      // 不需要重定向或无法重定向
      if (followUp == null) {
        if (exchange != null && exchange.isDuplex()) {
          transmitter.timeoutEarlyExit();
        }
        return response;
      }

      RequestBody followUpBody = followUp.body();
      if (followUpBody != null && followUpBody.isOneShot()) {
        return response;
      }

      closeQuietly(response.body());
      if (transmitter.hasExchange()) {
        exchange.detachWithViolence();
      }
      //如果重定向次数超过最大次数抛出异常
      if (++followUpCount > MAX_FOLLOW_UPS) {
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }

      request = followUp;
      priorResponse = response;
    }
  }

  //RetryAndFollowUpInterceptor.recover()
  //报告并尝试从与服务器通信失败中恢复。如果{@code e}可恢复,则返回true;
  //如果失败是永久性的,则返回false。只有在缓冲了正文或在发送请求之前发生故障时,才可以恢复具有正文的请求。
 private boolean recover(IOException e, Transmitter transmitter,
      boolean requestSendStarted, Request userRequest) {
    // 用户设置的禁止重试
    if (!client.retryOnConnectionFailure()) return false;

    // 不能再发送请求体
    if (requestSendStarted && requestIsOneShot(e, userRequest)) return false;

    // 异常是致命的
    if (!isRecoverable(e, requestSendStarted)) return false;

    // 没有更多的路线可以尝试。
    if (!transmitter.canRetry()) return false;

    //对于故障恢复,请对新连接使用同一路由选择器
    return true;
  }

  /**
   * 查看响应是否需要重定向,不需要就返回当前请求,如果需要返回新的请求。
   * 找出接收{@code userResponse}时要发出的HTTP请求。这将添加身份验证头、遵循重定向或处理客户端请求超时。
   * 如果后续操作不必要或不适用,则返回null。
   */
  private Request followUpRequest(Response userResponse, @Nullable Route route) throws IOException {
    if (userResponse == null) throw new IllegalStateException();
    //获取响应码
    int responseCode = userResponse.code();
    //请求方法
    final String method = userResponse.request().method();
    //响应码分类处理
    switch (responseCode) {
      //407 代理需要身份认证
      case HTTP_PROXY_AUTH:
        Proxy selectedProxy = route != null
            ? route.proxy()
            : client.proxy();
        if (selectedProxy.type() != Proxy.Type.HTTP) {
          throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
        }
        return client.proxyAuthenticator().authenticate(route, userResponse);
      //401 需要身份认证
      case HTTP_UNAUTHORIZED:
        return client.authenticator().authenticate(route, userResponse);

      case HTTP_PERM_REDIRECT:
      case HTTP_TEMP_REDIRECT:
        // "If the 307 or 308 status code is received in response to a request other than GET
        // or HEAD, the user agent MUST NOT automatically redirect the request"
        if (!method.equals("GET") && !method.equals("HEAD")) {
          return null;
        }
        // fall-through
       // 300多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择
      case HTTP_MULT_CHOICE:
      // 301永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。
      case HTTP_MOVED_PERM:
      // 302临时移动。与301类似。但资源只是临时被移动。
      case HTTP_MOVED_TEMP:
      // 303查看其它地址。与301类似。使用GET和POST请求查看
      case HTTP_SEE_OTHER:
        // Does the client allow redirects?
        if (!client.followRedirects()) return null;

        String location = userResponse.header("Location");
        if (location == null) return null;
        HttpUrl url = userResponse.request().url().resolve(location);

        // 不要遵循重定向到不支持的协议。
        if (url == null) return null;

        //如果已配置,请不要遵循SSL和非SSL之间的重定向。
        boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
        if (!sameScheme && !client.followSslRedirects()) return null;

        // 大多数重定向不包括请求正文。
        Request.Builder requestBuilder = userResponse.request().newBuilder();
        if (HttpMethod.permitsRequestBody(method)) {
          final boolean maintainBody = HttpMethod.redirectsWithBody(method);
          if (HttpMethod.redirectsToGet(method)) {
            requestBuilder.method("GET", null);
          } else {
            RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
            requestBuilder.method(method, requestBody);
          }
          if (!maintainBody) {
            requestBuilder.removeHeader("Transfer-Encoding");
            requestBuilder.removeHeader("Content-Length");
            requestBuilder.removeHeader("Content-Type");
          }
        }

        // 跨主机重定向时,删除所有身份验证头。这对应用程序层来说可能很烦人,因为它们无法保留它们。
        if (!sameConnection(userResponse.request().url(), url)) {
          requestBuilder.removeHeader("Authorization");
        }

        return requestBuilder.url(url).build();
      //408 超时
      case HTTP_CLIENT_TIMEOUT:
        // 408's are rare in practice, but some servers like HAProxy use this response code. The
        // spec says that we may repeat the request without modifications. Modern browsers also
        // repeat the request (even non-idempotent ones.)
        if (!client.retryOnConnectionFailure()) {
          // 应用层指示我们不要重试请求
          return null;
        }

        RequestBody requestBody = userResponse.request().body();
        if (requestBody != null && requestBody.isOneShot()) {
          return null;
        }

        if (userResponse.priorResponse() != null
            && userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {
          // We attempted to retry and got another timeout. Give up.
          return null;
        }

        if (retryAfter(userResponse, 0) > 0) {
          return null;
        }

        return userResponse.request();
      // 503 由于超载或系统维护,服务器暂时的无法处理客户端的请求。延时的长度可包含在服务器的Retry-After头信息中
      case HTTP_UNAVAILABLE:
        if (userResponse.priorResponse() != null
            && userResponse.priorResponse().code() == HTTP_UNAVAILABLE) {
          // We attempted to retry and got another timeout. Give up.
          return null;
        }

        if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
          // specifically received an instruction to retry without delay
          return userResponse.request();
        }

        return null;

      default:
        return null;
    }
  }


//Transmitter.prepareToConnect()
public void prepareToConnect(Request request) {
    if (this.request != null) {
      //判断是不是相同的连接,如果是则用之前的。
      if (sameConnection(this.request.url(), request.url()) && exchangeFinder.hasRouteToTry()) {
        return; // Already ready.
      }
      if (exchange != null) throw new IllegalStateException();
      //exchangeFinder
      if (exchangeFinder != null) {
        maybeReleaseConnection(null, true);
        exchangeFinder = null;
      }
    }

    this.request = request;
    this.exchangeFinder = new ExchangeFinder(this, connectionPool, createAddress(request.url()),
        call, eventListener);
  }

//Transmitter.createAddress()
 private Address createAddress(HttpUrl url) {
    SSLSocketFactory sslSocketFactory = null;
    HostnameVerifier hostnameVerifier = null;
    CertificatePinner certificatePinner = null;
    if (url.isHttps()) {
      //https的设置
      sslSocketFactory = client.sslSocketFactory();
      hostnameVerifier = client.hostnameVerifier();
      certificatePinner = client.certificatePinner();
    }

    return new Address(url.host(), url.port(), client.dns(), client.socketFactory(),
        sslSocketFactory, hostnameVerifier, certificatePinner, client.proxyAuthenticator(),
        client.proxy(), client.protocols(), client.connectionSpecs(), client.proxySelector());
  }

//ExchangeFinder.ExchangeFinder()
  ExchangeFinder(Transmitter transmitter, RealConnectionPool connectionPool,
      Address address, Call call, EventListener eventListener) {
    this.transmitter = transmitter;
    this.connectionPool = connectionPool;
    this.address = address;
    this.call = call;
    this.eventListener = eventListener;
    //创建路由选择器
    this.routeSelector = new RouteSelector(
        address, connectionPool.routeDatabase, call, eventListener);
  }
//RouteSelector.RouteSelector()
  RouteSelector(Address address, RouteDatabase routeDatabase, Call call,
      EventListener eventListener) {
    this.address = address;
    this.routeDatabase = routeDatabase;
    this.call = call;
    this.eventListener = eventListener;

    resetNextProxy(address.url(), address.proxy());
  }

该拦截器的逻辑很多,但其实主要做两件事,一个是重试,一个是重定向,逻辑流程大概是这样的。

  1. 通过realChain.transmitter获取了transmitter对象,并启用一个while死循环,

  2. 然后通过transmitter.prepareToConnect(request)transmitter创建ExchangeFinder,Address,RouteSelector三个对象,并判断了是否是相同的连接,是否需要maybeReleaseConnection(),重置ExchangeFinder。

    2.1 . ExchangeFinder此拦截器只是创建ExchangeFinder,但ExchangeFinder中有个find()方法主要通过内部的 findHealthyConnection() 从 connectionPool 中找到一个可用的连接,这个连接可能是复用的,并 connect(),从而得到 输入/输出 流 (source/sink) ,返回一个 Exchange 给 CallServerIntercepter , 通过这个 Exchange 就可以添加请求头和请求体,并读取响应头和响应体,来交给上面的 Intercepter,层层向上传递。

    2.2 . Address为请求参数的封装类,包含url,端口,DNS,SSL,Proxy,ProxySelector,SocketFactory,主机名验证,证书校验等逻辑。

    2.3 . RouteSelector主要来选择路由,主要做三件事。1.收集所有的可用路由。2.选择可用路由。3.借助RouteDatabase内的Set对象来维护连接失败的路由信息,防止去连接失败路由浪费时间。

  3. 启动拦截器列表的下一个拦截器。

  4. 判断全局变量priorResponse是否为null,如果不为空则代表请求成功了。

  5. 执行 followUpRequest()查看响应是否需要重定向,如果不需要重定向则返回当前请求

  6. 判断重定向次数,如果超过最大值则退出。

  7. 重置request,并把当前的Response保存到priorResponse,进入下一次的while循环。

总结来说:
通过while死循环来获取response,每次循环开始都根据条件获取下一个request,如果没有request,则返回response,退出循环。而获取的request 的条件是根据上一次请求的response 状态码确定的,在循环体中同时创建了一些后续需要的对象

感谢观看,觉得不错可以点赞关注一下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
为了满足广大Android开发爱好者与从业者的学习需求,我们精心整理并上传了一份全面而实用的Android项目资源包。这份资源包内容丰富,涵盖了从基础知识到实战应用的全方位内容,旨在为开发者们提供一个便捷、高效的学习平台。 一、文件手册 资源包的文件手册部分,详细记录了Android开发的核心知识点和常用技术。无论是初学者还是有一定经验的开发者,都能从找到所需的学习资料。手册采用了简洁明了的排版方式,使得查阅更加方便快捷。同时,手册内容深入浅出,既适合新手入门,也能为老手提供有价值的参考。 二、项目实战与练习 为了让学习者能够将理论知识与实践相结合,我们特别准备了项目实战与练习部分。这部分内容包含了多个精心设计的Android项目案例,从需求分析、设计思路到实现过程,都有详细的讲解和代码示例。学习者可以通过实际操作,深入了解Android开发的整个流程,提升自己的实战能力。 此外,我们还提供了一系列练习题,旨在巩固所学知识,检验学习成果。这些练习题既有基础题,也有难度较高的挑战题,适合不同层次的学习者进行练习。 三、Android开发工具集 在Android开发过程,选择合适的工具能够大大提高开发效率。因此,我们整理了常用的Android开发工具集,包括开发工具、测试工具、性能优化工具等。这些工具都是经过我们精心筛选和测试的,能够帮助开发者们更加高效地进行Android开发工作。 总的来说,这份Android项目资源包是一份不可多得的学习资料,无论你是初学者还是有一定经验的开发者,都能从受益匪浅。我们希望通过这份资源包,为广大Android开发爱好者与从业者提供一个更加便捷、高效的学习平台,共同推动Android开发领域的发展。
OkHttp是一个用于处理HTTP请求的开源Java库。它提供了一个拦截器机制,可以在发送请求和接收响应之前对它们进行修改和处理。以下是关于OkHttp拦截器的一些介绍和示例: 1. OkHttp拦截器是一个接口,它有一个方法intercept(Chain chain),该方法接收一个Chain对象作为参数,该对象表示当前的拦截器链。 2. 拦截器链是按照添加顺序执行的,每个拦截器都可以选择将请求传递给下一个拦截器或者直接返回响应。 3. 拦截器可以在请求和响应添加、修改或删除头信息,也可以重试请求或者记录请求和响应的日志等。 以下是一个简单的OkHttp拦截器示例,它会在请求头添加一个自定义的User-Agent信息: ```java public class UserAgentInterceptor implements Interceptor { private static final String USER_AGENT_HEADER = "User-Agent"; private final String userAgent; public UserAgentInterceptor(String userAgent) { this.userAgent = userAgent; } @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); Request newRequest = request.newBuilder() .header(USER_AGENT_HEADER, userAgent) .build(); return chain.proceed(newRequest); } } ``` 在上面的示例,我们创建了一个名为UserAgentInterceptor的拦截器,它接收一个User-Agent字符串作为参数。在intercept方法,我们首先获取当前的请求对象,然后使用Request.Builder添加一个自定义的User-Agent头信息,最后使用chain.proceed方法将请求传递给下一个拦截器或者返回响应。 以下是一个使用上面定义的拦截器的示例: ```java OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(new UserAgentInterceptor("MyApp/1.0")) .build(); ``` 在上面的示例,我们创建了一个OkHttpClient对象,并使用addInterceptor方法添加了一个UserAgentInterceptor拦截器。这样,在发送请求时,OkHttp会自动调用我们定义的拦截器,并在请求头添加一个自定义的User-Agent信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值