OKHttp源码解析(1)----整体流程

1,497 阅读10分钟

系列文章

OKio源码解析

OKHttp源码解析(1)----整体流程

OKHttp源码解析(2)----拦截器RetryAndFollowUpInterceptor

OKHttp源码解析(3)----拦截器BridgeInterceptor

OKHttp源码解析(4)----拦截器CacheInterceptor

OKHttp源码解析(5)----拦截器ConnectInterceptor

OKHttp源码解析(6)----拦截器CallServerInterceptor

1.简介

Android系统提供了两种HTTP通信类,HttpURLConnection和HttpClient,HttpURLConnection相对来说比HttpClient难用,google自从2.3版本之后一直推荐使用HttpURLConnection,并且在6.0版本的sdk中直接删掉了HttpClient类。Android4.4的源码中可以看到OkHttp替换了HttpURLConnection

相比以上两种通信类,OKHttp有巨大的优势:

  • 支持HTTP/2,HTTP/2通过使用多路复用技术,在一个单独的TCP链接上支持并发,通过在一个连接上一次性发送多个请求来发送或接收数据
  • 如果HTTP/2不可用,链接池减少请求延迟
  • 支持GZIP,额可以压缩下载体积
  • 响应缓存可以避免重复请求网络
  • 会从很多常用的连接问题中自动恢复,如果您的服务器配置了多个IP地址,当第一个IP连接失败的时候,OKHttp会尝试下一个IP地址
  • OKHttp还处理了代理服务器问题和SSL握手失败问题

流程图:

(图1)

2.使用

2.1 构建OkHttpClient

  OkHttpClient(Builder builder) {
    this.dispatcher = builder.dispatcher;   //调度器,主要用于异步网络请求的控制
    this.proxy = builder.proxy;  //代理
    this.protocols = builder.protocols;  //协议
    this.connectionSpecs = builder.connectionSpecs;  //指定配置(tls版本、密码组等)
    this.interceptors = Util.immutableList(builder.interceptors);  //用户自定义拦截器
    this.networkInterceptors = Util.immutableList(builder.networkInterceptors); //用户自定义网络拦截器
    this.eventListenerFactory = builder.eventListenerFactory;
    this.proxySelector = builder.proxySelector;
    this.cookieJar = builder.cookieJar;  //
    this.cache = builder.cache;  //缓存
    this.internalCache = builder.internalCache;  //内部缓存
    this.socketFactory = builder.socketFactory;

    boolean isTLS = false;
    for (ConnectionSpec spec : connectionSpecs) {
      isTLS = isTLS || spec.isTls();
    }

    if (builder.sslSocketFactory != null || !isTLS) {
      this.sslSocketFactory = builder.sslSocketFactory;
      this.certificateChainCleaner = builder.certificateChainCleaner;
    } else {
      X509TrustManager trustManager = systemDefaultTrustManager();
      this.sslSocketFactory = systemDefaultSslSocketFactory(trustManager);
      this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);
    }

    this.hostnameVerifier = builder.hostnameVerifier;
    this.certificatePinner = builder.certificatePinner.withCertificateChainCleaner(
        certificateChainCleaner);
    this.proxyAuthenticator = builder.proxyAuthenticator;
    this.authenticator = builder.authenticator;
    this.connectionPool = builder.connectionPool;  //链接池
    this.dns = builder.dns;  //dns
    this.followSslRedirects = builder.followSslRedirects;  //是否支持http与https之间互相重定向
    this.followRedirects = builder.followRedirects;  //否是支持重定向
    this.retryOnConnectionFailure = builder.retryOnConnectionFailure;  //连接失败重试
    this.connectTimeout = builder.connectTimeout;  //连接超时时间
    this.readTimeout = builder.readTimeout;  //读取超时时间
    this.writeTimeout = builder.writeTimeout;  //写入超时时间
    this.pingInterval = builder.pingInterval;
  }

2.2 get请求:

OkHttpClient client = new OkHttpClient();

    public String get(String url) throws IOException {
        Request request = new Request.Builder()
                .url(url)
                .build();

        Response response = client.newCall(request).execute();
        return response.body().string();
    }

2.3 post请求:

   OkHttpClient client = new OkHttpClient();
   
    public String post(String url,String json) throws IOException {
        RequestBody body = RequestBody.create(JSONObject,json);
        Request request = new Request.Builder()
                .post(body)
                .url(url)
                .build();

        Response response = client.newCall(request).execute();
        return response.body().string();
    }

3.流程

以下以同步的get请求来分析整体的请求过程

        Response response = client.newCall(request).execute();

OkHttpClient 首先创建一个Call(RealCall):

  @Override public Call newCall(Request request) {
    return new RealCall(this, request, false /* for web socket */);
  }

查看RealCall的execute()方法:

  @Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    try {
      client.dispatcher().executed(this);   //记录此请求
      Response result = getResponseWithInterceptorChain();  //请求链及拦截器
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  }

在RealCall的execute()方法中,有两步比较重要: client.dispatcher().executed(this);OKHttp的分发器在请求的开始会记录此次请求,请求结束后会移除。分发器在OKHttp的异步请求中比较中要,下面再讲。

Response result = getResponseWithInterceptorChain()OKHttp完整的网络请求就是在这步中进行的。下面我们来看下其中做了什么?

  Response getResponseWithInterceptorChain() throws IOException {
    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);
  }

OKHttp的完整请求就在这里,请求和返回数据的处理就是由这些拦截器来完成的,各个拦截器由RealInterceptorChain进行串联。如流程图图1。具体的串联实现,可以看下源码。

4.拦截器

OKHttp的拦截器主要是下面几种:

  • client.interceptors()
  • retryAndFollowUpInterceptor
  • BridgeInterceptor
  • CacheInterceptor
  • ConnectInterceptor
  • client.networkInterceptors()
  • CallServerInterceptor

4.1 client.interceptors()

APP层面的拦截器(Application Interception 应用拦截器),是在配置OKHttpClient时设置的interceptors

  • 对请求参数进行统一的加密处理
  • 拦截不符合规则的URL
  • 对请求或者返回参数设置统一的编码方式
  • 其他

4.2 RetryAndFollowUpInterceptor

重试与重定向拦截器,负责重试及重定向。现在我们来分析源码:

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

    streamAllocation = new StreamAllocation(
        client.connectionPool(), createAddress(request.url()), callStackTrace);

    int followUpCount = 0;
    Response priorResponse = null;
    //while循环进行重试,请求成功则返回response;请求失败,则对responseCode判断,并更改相应设置,进行重新请求。
    while (true) {
      if (canceled) {
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response = null;
      boolean releaseConnection = true;
      try {
      //进行链式请求,并对请求结果进行判定是否重试
        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 are 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();
      }

      //对response进行判定,是否进行重定向重试
      Request followUp = followUpRequest(response);

      //followUp为空,表明不是需要重定向的responseCode,返回结果response(可能是请求成功,也可能是请求失败)
      if (followUp == null) {
        if (!forWebSocket) {
          streamAllocation.release();
        }
        return response;
      }

       //释放response.body()
      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());
      }
      
       // response 和 followUp 比较是否为同一个连接
       // 若为重定向就销毁旧连接,创建新连接
      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
      request = followUp;
      priorResponse = response;
    }
  }

4.3 BridgeInterceptor

桥接拦截器(内容拦截器),负责把用户构造的请求转换为发送到服务器的请求、把服务器返回的响应转换为用户友好的响应。

  @Override public Response intercept(Chain chain) throws IOException {
  
    // 将用户友好的 request 构造为发送给服务器的 request
    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 are 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());
    }

    // 构造完后,将 request 交给下一个拦截器去处理。最后又得到服务端响应 networkResponse
    Response networkResponse = chain.proceed(requestBuilder.build());
    // 保存 networkResponse 的 cookie
    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
     // 将 networkResponse 构造为对用户友好的 response
    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);
    // 如果 networkResponse 使用 gzip 并且有响应体的话,给用户友好的 response 设置响应体
    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();
  }

4.4 CacheInterceptor

缓存拦截器,负责读取缓存直接返回、更新缓存。当网络请求有符合要求的Cache时,直接返回Cache。如果当前Cache失效,则删除。CacheStrategy:缓存策略,CacheStrategy类是一个非常重要的类,用于控制请求是网络获取还是缓存获取。

  @Override public Response intercept(Chain chain) throws IOException {
     // 得到 request 对应缓存中的 response
    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) {
      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 are doing a conditional get.
     // 如果我们同时有缓存和 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();

        // 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.
        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;
  }

4.5 ConnectInterceptor

连接拦截器,负责和服务器建立连接

  @Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    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 httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
    RealConnection connection = streamAllocation.connection();

    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }

链接拦截器主要是完成了网络设施的建设,包含连接connection,根据socket生成输出流sink和输入流source。 后面的CallServerInterceptor使用这些设施完成请求的发送以及响应的读取。这些请求过程,我们将在CallServerInterceptor一章中详细解读。

4.6 client.networkInterceptors()

网络请求层面的拦截器,配置OkHttpClient时设置的networkInterceptors:

请求时:

  • 可以修改OkHttp框架自动添加的一些属性
  • 可以观察最终完整的请求参数

返回结果:

  • 能够对中间的响应进行操作,比如重定向和重试
  • 当发生网络短路时,不调用缓存的响应结果
  • 监控数据,就像数据在网络上传输一样
  • 访问承载请求的连接Connection。

4.7 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 is a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
      // Continue" response before transmitting the request body. If we do not get that, return what
      // we did get (such as a 4xx response) without ever transmitting the request body.
      // 如果有 Expect: 100-continue 在请求头中,那么要等服务器的响应
      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();
     // 如果为 web socket 且状态码是 101 ,那么 body 为空
    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();
    }
     // 如果请求头中有 close 那么断开连接
    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      streamAllocation.noNewStreams();
    }
     // 抛出协议异常
    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }

    return response;
  }

小结

本章主要讲了OkHttp的整体流程,并对源码的关键点进行了备注。详细的讲解在各个拦截器的单章讲解中,开发者可以在后面几章查看。

OkHttp使用分层的思想,每一个拦截器就是一个完成相关功能的任务层,并使用链Chain串联整个流程,使用了责任链模式。在整个链中,向用户提供了插入点,用户可以自定义拦截器,完成一些自定义的加密、数据统计等相关功能。

References

OkHttp源码解析