OkHttp源码分析(四)BridgeInterceptor

1,012 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情

BridgeInterceptor是第二个框架提供的拦截器,是一个桥梁的功能,从应用程序用户侧代码到网络代码底层代码的桥梁。
在请求网络前,他负责配置通用的Header,比如cookie和content相关配置,在请求成功后,还会判断并使用Gzip进行解压。配置一些样板代码和功能,这些都是使用的用户不需要关心的内容,但是请求网络是需要这些内容的。
整个拦截器的拦截代码中,分为请求前和请求后两个阶段,所有的拦截器都有这种划分方式,类似装饰者模式,请求前做点事情,请求后做点事情。本篇分阶段详细了解一下。

请求前处理

请求前的操作主要是配置header字段,逐一分析下。

  1. Content-Type 指定传输媒体的类型,比如text/html等类型。

    RequestBody body = userRequest.body();
    if (body != null) {
      MediaType contentType = body.contentType();
      if (contentType != null) {
        requestBuilder.header("Content-Type", contentType.toString());
      }
    }
    

    OKHttp中的设置通过取body的contentType()方法,这个body是通过设置POST、PUT等请求方法的时候传入的,类型是RequestBody,contentType()是一个抽象方法,我们可以重写这个方法返回指定的类型,RequestBody有很多静态工厂方法create()供我们使用。

  2. Content-Length
    表示传输中实体数据的大小。

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

    是通过body.contentLength()获取的,同样也是RequestBody中的一个方法,通过create方法传入,在这个拦截器内就会设置进去。

  3. Transfer-Encoding
    这个值的设置和上面的 Content-Length是互斥的,从代码中就能看出。如果能拿到运输数据的长度,就会移除这个字段,如果没有拿到长度,就通过配置Transfer-Encoding: chunked表示输出的内容长度不能确定。

  4. Host
    当多个站点对应一个IP时,比如一台服务器上有多个虚拟主机,这几个主机共同关联一个IP地址。连接到这台服务器时,怎么区分不同的主机呢,就是通过Host来区分的。

    if (userRequest.header("Host") == null) {
      requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }
    
    public static String hostHeader(HttpUrl url, boolean includeDefaultPort) {
      String host = url.host().contains(":")
          ? "[" + url.host() + "]"
          : url.host();
      return includeDefaultPort || url.port() != HttpUrl.defaultPort(url.scheme())
          ? host + ":" + url.port()
          : host;
    }
    

    在OKHttp设置的方式比较简单,如果我们手动设置Host,这里会设置一个默认的,默认的通过hostHeader方法进行计算。url.host()前面的章节讲到过,如果 android.com host返回的就是android.com。如果和默认的端口不一致,还要带上端口。

  5. Cookie
    这个大家应该都清楚,因为Http是没有状态的,为了服务器能记住用户,需要通过Cookie标记特定的用户。

    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
      requestBuilder.header("Cookie", cookieHeader(cookies));
    }
    

    Cookie的设置主要通过cookieJar,默认是CookieJar.NO_COOKIES,也就是不配置Cookie
    CookieJar是一个接口,有两个抽象方法saveFromResponseloadForRequest分别表示存取Cookie。如果我么要实现Cookie的设置,需要自己实现一个,并传入OkHttpClient。

  6. User-Agent
    用来让网络协议的对端来识别发起请求的用户代理软件的应用类型、操作系统、软件开发商以及版本号。

    if (userRequest.header("User-Agent") == null) {
      requestBuilder.header("User-Agent", Version.userAgent());
    }
    

    Version.userAgent()默认返回"okhttp/3.12.6";,也就是当前版本。

  7. Connection
    当前的事务完成后,是否会关闭网络连接。如果该值是“keep-alive”,网络连接就是持久的,不会关闭,使得对同一个服务器的请求可以继续在该连接上完成。

    if (userRequest.header("Connection") == null) {
      requestBuilder.header("Connection", "Keep-Alive");
    }
    

    如果没有配置Connection,那么就是Keep-Alive,表示持久连接。

  8. Accept-Encoding 会将客户端能够理解的内容编码方式——通常是某种压缩算法——进行通知(给服务端)。通过内容协商的方式,服务端会选择一个客户端提议的方式,使用并在响应头 Content-Encoding 中通知客户端该选择。

    boolean transparentGzip = false;
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true;
      requestBuilder.header("Accept-Encoding", "gzip");
    }
    

    如果自己没有设置Accept-Encoding字段,并且没有设置Range字段,这个字段表示请求资源的部分内容(不包括响应头的大小),如果设置了只请求部分参数,这时不能进行压缩操作。

请求后处理

cookie处理

对与响应的cookie,我们需要保存相应的cookie,同样使用CookieJar,调用他的saveFromResponse存储这次的cookie。

HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

public static void receiveHeaders(CookieJar cookieJar, HttpUrl url, Headers headers) {
  if (cookieJar == CookieJar.NO_COOKIES) return;

  List<Cookie> cookies = Cookie.parseAll(url, headers);
  if (cookies.isEmpty()) return;
  cookieJar.saveFromResponse(url, cookies);
}

上面对cookie的处理拿到header中的cookie信息,并存储到CookieJar中。

Gzip解压操作

请求后的操作主要是对上面的第8点进行处理,也就是如果请求了压缩,那么这里就要解压。

Response.Builder responseBuilder = networkResponse.newBuilder()
    .request(userRequest);
if (transparentGzip
    && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
    && HttpHeaders.hasBody(networkResponse)) {
  。。。
}

先看解压操作要满足几个条件

  1. transparentGzip为true
    transparentGzip的赋值上面有相关的代码,只有没有设置Accept-Encoding和Range时,才会设置为true,默认为false。所以如果我们自己配置了Accept-Encoding为gzip,那么transparentGzip就为false,所以OkHttp不会自动进行解压操作。这里需要注意下。

     boolean transparentGzip = false;
     if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
          transparentGzip = true;
          equestBuilder.header("Accept-Encoding", "gzip");
    }
    
  2. "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
    如果返回的数据实体编码类型时gzip,这个字段时服务器处理响应实体的编码类型,如果时gzip,表示服务器已经做压缩处理了。

  3. 要有body结构

    public static boolean hasBody(Response response) {
      // HEAD requests never yield a body regardless of the response headers.
      if (response.request().method().equals("HEAD")) {
        return false;
      }
    
      int responseCode = response.code();
      if ((responseCode < HTTP_CONTINUE || responseCode >= 200)
          && responseCode != HTTP_NO_CONTENT
          && responseCode != HTTP_NOT_MODIFIED) {
        return true;
      }
    
      if (contentLength(response) != -1
          || "chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) {
        return true;
      }
    
      return false;
    }
    

    是通过上面的方法判断的,首先如果时HEAD请求方法,只会返回头部信息,肯定不会有body。并且状态码不能在100和200之间,并且不等于204无数据和304无变化。这些情况搜时没有body的。后面判断了如果返回了Content-Length,或者Transfer-Encoding是chunked,也表示有body,上面谈到过这两个字段。 如果满足了上述条件,就会对相应进行解压操作。

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

以上就是BridgeInterceptor的全部内容,非常的简单。

下一篇分析CacheInterceptor,前段的缓存。