Okhttp解析—Okhttp概览

Okhttp解析—Okhttp概览

Okhttp做为目前Android使用最为普遍的网络框架之一,咱们有必要去深刻了解一下,本文是Okhttp解析的第一篇,主要是从宏观上认识Okhttp整个架构是如何实现的。git

1、什么是Okhttp

HTTP是当今应用程序经过网络交换数据和媒体的方式。 有效地使用 HTTP 可使应用加载得更快并节省带宽。
Okhttp是一个高效的HTTP Client,高效性体如今:github

  • Http / 2支持容许对同一主机的全部请求共享一个套接字
  • 链接池减小了请求延迟
  • 透明 GZIP 缩小了下载大小
  • 对于重复请求,响应缓存能够彻底避免网络请求

当网络出现问题时,OkHttp 不会当即结束: 它会默默地从常见的链接问题中恢复过来。 若是您的服务有多个 IP 地址,若是第一次链接失败,OkHttp 将尝试替代地址。 这对于 IPv4 + IPv6和承载于冗余数据中心的服务是必要的。 Okhttp 支持现代 TLS 特性(TLS 1.三、 ALPN、证书ping)。 它能够配置为回退到可用的链接。
而且Okhttp是易用的,其经过Builder模式设计请求 / 响应 API,支持同步阻塞调用和带回调的异步调用。web

2、Okhttp的请求机制以及相关概念

首先咱们来了解下HTTP client、request、response。
HTTP client的做用就是接受咱们的request并返回response。
request一般包含一个 URL, 一个方法 (好比GET/POST), 以及一个headers列表还可能包含一个body(特定内容类型的数据流)。
response则一般用响应代码(好比200表示成功,404表示未找到)、headers和可选的body来回答request。缓存

咱们平常使用http都是按如下步骤:
一、建立httpClient
二、建立request
三、使用httpClient请求request而后获取respone安全

使用Okhttp也是如此,咱们建立OkhttpClient而后把Reques交给它,最后拿到Respone,可是Okhttp在内部实际进行http请求时并非这样简单的拿Request去请求而后得到Resopne返回。服务器

下面就来看下Okhttp的请求机制,能够归纳为如下流程:cookie

  1. 当咱们建立OkhttpClient而后把Reques交给它以后,Okhttp为了提升正确性和效率在传输请求以前会重写请求。
  2. 而后Okhttp会尝试链接webserver,咱们知道request中是带有URL的可是Okhttp在链接webserver时不只仅使用URL它还会用Address和Route。链接webserver成功后获取respone,链接webserver失败Okhttp会进行重试操做。
  3. 在把respone返回给client以前Okhttp通常还会重写respone以及缓存respone。还有就是若是请求过程当中产生重定向Okhttp也会进行处理并返回最终的respone。
    上边就是Okhttp的请求以及返回的大体流程

Rewriting Requests

Okhttp 能够添加原始请求中缺乏的headers,包括Content-Length,Transfer-Encoding,User-Agent ,Host ,Connection , 和Content-Type。 除非Accept-Encoding头已经存在,不然它将添加一个用于透明响应压缩的 Accept-Encoding 头。 若是你有 cookies,OkHttp 会添加一个 Cookie 头。网络

有些请求会有一个缓存response。 当这个缓存过时,OkHttp 能够执行一个有条件的 GET 来下载新的response,这须要添加如 If-Modified-Since 和 If-None-Match 这样的headers。架构

Connections

Okhttp链接webserver时使用了URL、Address和Route。并发

URL

Url是 HTTP 和互联网的基础,每一个 URL 标识一个特定的路径。它是一个通用的,分散的网络命名方案,它指定了如何访问网络资源、指定调用是纯文本(http) 或加密(https)方式。它们没有指定是否应该使用特定的代理服务器或者如何经过该代理服务器的身份验证

Address

Address指定一个 web 服务器(好比 github. com)和链接到该服务器所需的全部静态配置: 端口号、 HTTPS 设置和首选网络协议(好比 http / 2或 SPDY)。

具备相同Address的 url 也可能有相同的底层 TCP 套接字链接。 共享一个链接有很大的性能优点好比更低的延迟,更高的吞吐量(因为 TCP 缓慢启动)和节省电池。 Okhttp 使用一个 ConnectionPool 自动重用 http / 1.x 链接以及多路传输 http / 2和 SPDY 链接。
在 OkHttp 中,Address的一些字段来自 URL (scheme, hostname, port) ,其他字段来自 OkHttpClient。

Route

Route提供实际链接到网络服务器所需的动态信息。 它会尝试的特定 IP 地址(由 DNS 查询发现)、使用的准确代理服务器(若是使用 ProxySelector)以及协商的 TLS 版本(用于 HTTPS 链接)。
一个地址可能有多条Route。 例如,承载于多个数据中心的 web 服务器在其 DNS 响应中可能会产生多个 IP 地址。

当你使用 OkHttp 请求一个 URL 时,它是这样作的:

  1. 它使用 URL 并配置 OkHttpClient 来建立一个address。 这个address指定了咱们如何链接到网络服务器
  2. 它试图从链接池(connection pool)中检索具备该地址的链接
  3. 若是它没有在链接池中找到链接,它会选择一条route进行尝试。 这一般意味着发出 DNS 请求来获取服务器的 IP 地址。 而后,若是有须要,它会选择一个 TLS 版本和代理服务器
  4. 若是是一个新的route,则经过构建直接的套接字链接、 TLS 隧道(经过 HTTP 代理使用 HTTPS)或直接的 TLS 链接进行链接。 必要时,它会进行 TLS 握手
  5. 发送 HTTP 请求并读取respone

若是链接有问题,OkHttp 会选择另外一条route,再试一次。 这让 OkHttp 在服务器地址的一个子集没法访问时从错误中恢复。 当池链接过期或者不支持当前使用的 TLS 版本时,它也颇有用。
一旦接收到respone,链接将返回到池中,以即可以在未来的请求中重用它。 链接在一段时间的不活动会被从链接池中清除。

Rewriting Response

若是使用透明压缩,OkHttp 将删除相应的 Content-Encoding 和 Content-Length,由于它们不适用于解压缩的响应体。
若是条件 GET请求 成功,来自网络和缓存的respone将按照规范的指示进行合并。

Follow-up Requests

当你请求的 URL 被重定向,webserver 将返回一个响应代码,好比302来指示新 URL。Okhttp将会重定向检索最终的respone。
若是respone发出了一个受权验证,OkHttp 将要求 Authenticator (若是配置了一个)知足这个验证。 若是身份验证者提供了凭据,那么request会携带该凭据去重试。

Retrying Requests

有时候链接会失败(好比池链接过期并断开链接或没法链接到网络服务器自己)若是此时有一个可用的Route,OkHttp 会用该Route重试请求。

Okhttp在实现流程的时候还引入了一些概念好比Call、Interceptors、ConnectionSpec、Events等等。

Call

通过重写、重定向、重试等操做,简单请求可能会产生许多请求和响应。 Okhttp 使用 Call 来封装表示request,Call就是一个已经准备好能够执行的请求。若是 url 被重定向,或者故障转移到另外一个 IP 地址,那么代码将继续工做,直至返回最终respone。

Call有两种调用方式:
同步:线程阻塞直到响应可读为止
异步: 能够在任何线程上对请求进行排队,当响应可读时在另外一个线程上被调回
能够从任何线程取消Call调用, 这将致使调用失败。在写入请求body或读取响应body时调用cancel会抛出IOException。

dispatcher

Dispatcher调度器,它实际就是负责Okhttp请求策略。
对于同步调用,调用请求的线程本身负责管理同时发出的请求数量。可是要注意的是太多的并发请求会浪费资源; 太少也很差。
对于异步调用,Dispatcher 实现最大并发请求的策略。 它设置了每一个 web 服务器的最大值并发请求数为5,总并发数为64。固然咱们也能够自行设置并发数。

Interceptors

拦截器能够说是Okhttp的精髓之一,它是一种强大的机制,它能够监测、重写和重试Call调用。系统提供了5种已经定义好的拦截器,上面说的request/respone 重写,失败重试等都是在拦截器中完成的。
对 chain.proceed (request)的调用是每一个拦截器实现的关键部分。 这个简单的外观方法是全部拦截器完成其功能的地方,而且还生成知足请求的响应。 注意若是 chain.proceed (request)被调用屡次,则必须关闭之前的响应body。
实际使用过程是经过拦截器链把拦截器链起来而后按顺序调用拦截器。
interceptor

Okhttp拦截器分为2类:Application Interceptors和Network Interceptors。

这两类拦截器本质上没有区别只是它们做用的时机不一样。由上图咱们能够看出Application Interceptors做用于Okhttp Core以前而Network Interceptors则做用于Okhttp Core以后。个人理解就是Application Interceptors调用是在请求发出以前,Network Interceptors则是在请求发出后与webserver链接的过程。
每种拦截器链都有其优势:
Application interceptors

  • 没必要担忧重定向和重试之类的中间响应
  • 老是调用一次,即便 HTTP 响应是从缓存中提供的
  • 关注应用程序的原始意图,而不关注OkHttp注入的headers
  • 容许短路和不调用Chain.proceed().
  • 容许重试并屡次调用Chain.proceed().
    Network Interceptors
  • 可以操做中间respone,如重定向和重试
  • 不会为短路网络的缓存响应调用
  • 仅当数据将要经过网络传输时才会关注
  • 容许带有Connection请求
    最后补充两个示例:
    使用Interceptor重写请求
/** This interceptor compresses the HTTP request body. Many webservers can't handle this! */
final class GzipRequestInterceptor implements Interceptor {
  @Override public Response intercept(Interceptor.Chain chain) throws IOException {
    Request originalRequest = chain.request();
    if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) {
      return chain.proceed(originalRequest);
    }

    Request compressedRequest = originalRequest.newBuilder()
        .header("Content-Encoding", "gzip")
        .method(originalRequest.method(), gzip(originalRequest.body()))
        .build();
    return chain.proceed(compressedRequest);
  }

  private RequestBody gzip(final RequestBody body) {
    return new RequestBody() {
      @Override public MediaType contentType() {
        return body.contentType();
      }

      @Override public long contentLength() {
        return -1; // We don't know the compressed length in advance!
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
        body.writeTo(gzipSink);
        gzipSink.close();
      }
    };
  }
}

使用Interceptor重写响应

/** Dangerous interceptor that rewrites the server's cache-control header. */
private static final Interceptor REWRITE_CACHE_CONTROL_INTERCEPTOR = new Interceptor() {
  @Override public Response intercept(Interceptor.Chain chain) throws IOException {
    Response originalResponse = chain.proceed(chain.request());
    return originalResponse.newBuilder()
        .header("Cache-Control", "max-age=60")
        .build();
  }
};

ConnectionSpec

ConnectionSpec的引入是为了HTTPS,在协商与 HTTPS 服务器的链接时,OkHttp 须要知道要提供哪些 TLS 版本和密码套件。Okhttp为了与尽量多的主机链接的同时保证链接的安全性引入ConnectionSpec,它实现了特定的安全性和链接性决策,Okhttp 包括四个内置的链接规范RESTRICTED_TLS、MODERN_TLS、COMPATIBLE_TLS、CLEARTEXT。
默认状况下,OkHttp 将尝试创建一个 MODERN_TLS 链接。 可是,经过配置client的 connectionSpecs,若是 MODERN_TLS失败,回退到 COMPATIBLE_TLS 链接。

OkHttpClient client = new OkHttpClient.Builder()
    .connectionSpecs(Arrays.asList(ConnectionSpec.MODERN_TLS, ConnectionSpec.COMPATIBLE_TLS))
    .build();

Events

Events的引入是为了监控call请求,由于咱们有必要了解咱们应用的HTTP请求。具体来讲就是监控如下内容:
应用程序 HTTP 请求调用的频率和请求大小。
基础网络的性能监控,若是网络差你应该减小请求或者改善网络。

EventListener

Okhttp提供了EventListener,咱们能够继承它并重写咱们感兴趣的方法来进行Event的监控。
Evnets
上图是在没有重试和重定向的状况下EventListener所能监控的Events流。下面是对应的代码

class PrintingEventListener extends EventListener {
  private long callStartNanos;

  private void printEvent(String name) {
    long nowNanos = System.nanoTime();
    if (name.equals("callStart")) {
      callStartNanos = nowNanos;
    }
    long elapsedNanos = nowNanos - callStartNanos;
    System.out.printf("%.3f %s%n", elapsedNanos / 1000000000d, name);
  }

  @Override public void callStart(Call call) {
    printEvent("callStart");
  }

  @Override public void callEnd(Call call) {
    printEvent("callEnd");
  }

  @Override public void dnsStart(Call call, String domainName) {
    printEvent("dnsStart");
  }

  @Override public void dnsEnd(Call call, String domainName, List<InetAddress> inetAddressList) {
    printEvent("dnsEnd");
  }

  ...
}

Eventlistener. Factory

上面Eventlistener只适用于没有并发的状况,若是有多个请求并发执行咱们须要使用Eventlistener. Factory来给每一个请求建立一个Eventlistener。

下面是一个给每一个请求建立一个带惟一ID的Eventlistener的示例:

class PrintingEventListener extends EventListener {
  public static final Factory FACTORY = new Factory() {
    final AtomicLong nextCallId = new AtomicLong(1L);

    @Override public EventListener create(Call call) {
      long callId = nextCallId.getAndIncrement();
      System.out.printf("%04d %s%n", callId, call.request().url());
      return new PrintingEventListener(callId, System.nanoTime());
    }
  };

  final long callId;
  final long callStartNanos;

  public PrintingEventListener(long callId, long callStartNanos) {
    this.callId = callId;
    this.callStartNanos = callStartNanos;
  }

  private void printEvent(String name) {
    long elapsedNanos = System.nanoTime() - callStartNanos;
    System.out.printf("%04d %.3f %s%n", callId, elapsedNanos / 1000000000d, name);
  }

  @Override public void callStart(Call call) {
    printEvent("callStart");
  }

  @Override public void callEnd(Call call) {
    printEvent("callEnd");
  }

  ...
}

上面说的都是请求正常时event,接下来讲下非正常状况下的event流程。

Events with Failures

当请求失败时,将调用一个失败方法 connectFailed () ,用于在创建到服务器的链接时发生故障,当 HTTP 请求永久失败时调用 callFailed ()。 当发生故障时,有可能开始事件没有相应的结束事件。
event

Events with Retries and Follow-Ups

Okhttp是健壮的,能够从一些链接故障中自动恢复。 在这种状况下,connectFailed ()事件不是终结符,后面不跟 callFailed ()。 当尝试重试时,事件侦听器将收到多个相同类型的事件。
单个 HTTP 调用可能须要发出后续请求来处理身份验证、重定向和 HTTP 层超时。 在这种状况下,能够尝试多个链接、请求和响应。 重定向是单个请求可能触发同一类型多个事件的另外一个缘由。
event

以上就是本文所有内容,接下来就是源码分析了,不过建议在看源码前把Okhttp的整个运行过程以及其中涉及的概念搞懂这样看源码才会事半功倍。

相关文章
相关标签/搜索