OkHttp3.0(四)-Interceptor拦截器(4)-BridgeInterceptor

1.概述

本章节我们来分析一下OkHttp系列之拦截器中的BridgeInterceptor(桥接拦截器),通过前面的讲解,我们已经知道的OkHttp做网络请求实质上就是通过拦截器的调用,实现与服务器端的连接已经数据传输,最后通过拦截器链将数据返回给用户。拦截器链最早调用的是Application 拦截器,然后是RetryAndFollowUpInterceptor重定向拦截器,接下来就是我们今天要讲的BridgeInterceptor。

2.BridgeInterceptor桥接拦截器

因为BridgeInterceptor在RetryAndFollowUpInterceptor执行之后才会执行,所以当RetryAndFollowUpInterceptor重新向服务器端发送请求的时候,BridgeInterceptor就会被再次调用,所以说BridgeInterceptor可能被多次调用,也可以说RetryAndFollowUpInterceptor之后的拦截器都有可能被多次调用。BridgeInterceptor做的事情比较简单,所以本章我们不会占用太长时间来说,直接上代码

public final class BridgeInterceptor implements Interceptor {
  private final CookieJar cookieJar;
  //构造函数给CookieJar 赋值,这里的cookieJar来自于OkHttpClient初始化时创建的空CookieJar 
  public BridgeInterceptor(CookieJar cookieJar) {
    this.cookieJar = cookieJar;
  }
  //这里做BridgeInterceptor 的工作
  @Override public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();//获取请求Request
    //获取一个含有Request所有内容的Request.Builder对象
    Request.Builder requestBuilder = userRequest.newBuilder();
    RequestBody body = userRequest.body();//获取请求体
    if (body != null) {
      MediaType contentType = body.contentType();
      if (contentType != null) {//如果用户设置了Content-Type,则将Content-Type添加到Headers
        requestBuilder.header("Content-Type", contentType.toString());
      }
      long contentLength = body.contentLength();
      if (contentLength != -1) {//如果用户设置了Content-Length,添加到Headers。
        requestBuilder.header("Content-Length", Long.toString(contentLength));
        requestBuilder.removeHeader("Transfer-Encoding");
      } else {//如果没有设置Content-Length则使用分块编码Transfer-Encoding
        requestBuilder.header("Transfer-Encoding", "chunked");
        requestBuilder.removeHeader("Content-Length");
      }
    }
    if (userRequest.header("Host") == null) {//设置HOST主机
      requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }
    //如果没有设置Connection,则默认为持久连接
    if (userRequest.header("Connection") == null) {//如果没有设置
      requestBuilder.header("Connection", "Keep-Alive");
    }
    boolean transparentGzip = false;
    //如果未设置客户端支持的编码类型,则默认为gzip
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true;
      requestBuilder.header("Accept-Encoding", "gzip");
    }
    
    List cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {//如果Cookie非空,则加入到Headers
      requestBuilder.header("Cookie", cookieHeader(cookies));
    }
    //如果没有设置用户代理,则默认为当前OkHttp的版本“okhttp/3.11.0”
    if (userRequest.header("User-Agent") == null) {
      requestBuilder.header("User-Agent", Version.userAgent());
    }
    //调用拦截器链,获取服务器端的响应
    Response networkResponse = chain.proceed(requestBuilder.build());
    //如果服务器端返回的Response的Headers中有Cookie,则将用户将其保存起来
    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
    //新建一个包含Request所有信息的Response.Builder实例
    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);
    //如果是gzip编码并且有响应体,则进行解码,封装构建用户需要的Response
    if (transparentGzip
        && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
        && HttpHeaders.hasBody(networkResponse)) {
      GzipSource responseBody = new GzipSource(networkResponse.body().source());//解压
      构建Response的Headers
      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)));
    }
    //将最终的Response返回给上一个拦截器RetryAndFollowUpInterceptor
    return responseBuilder.build();
  }
  /**
    *将Cookie集合列表组装成String返回
    */
  private String cookieHeader(List cookies) {
    StringBuilder cookieHeader = new StringBuilder();
    for (int i = 0, size = cookies.size(); i < size; i++) {
      if (i > 0) {
        cookieHeader.append("; ");
      }
      Cookie cookie = cookies.get(i);
      cookieHeader.append(cookie.name()).append('=').append(cookie.value());
    }
    return cookieHeader.toString();
  }
}

我们解释下上面代码中的几个点:

2.1.Transfer-Encoding分块编码

我们在上面代码中使用到了Transfer-Encoding

if (contentLength != -1) {
  requestBuilder.header("Content-Length", Long.toString(contentLength));
  requestBuilder.removeHeader("Transfer-Encoding");
} else {
  requestBuilder.header("Transfer-Encoding", "chunked");
  requestBuilder.removeHeader("Content-Length");
}

Transfer-Encoding:我们在进行HTTP请求的时候,如果无法判断内容长度(请求体或者响应体),无法计算内容长度,Content-Length就无法使用或者长度不准确,这种情况下面对大部分都是长连接的HTTP请求,接收方不知道接收到的内容的长度,就无法判断是否接受完成或者只接收了部分内容,造成请求停滞或者内容不全。为了解决这种问题,我们Transfer-Encoding头,将其加入Headers并且设置它的值为“chunked”,这样就实现了分块编码,报文中的实体会改为一系列的分块来传输,每一个分块都包含内容数据以及长度,当分块内容为空长度为0时,表示结束。这样就解决了问题。关于Transfer-Encoding,请看我的另一篇博客[连接]。

2.2.Cookie机制

我们看到在BridgeInterceptor的intercept方法中,使用到了Cookie,稍微提一嘴。

  private final CookieJar cookieJar;
   //构造函数给CookieJar 赋值,这里的cookieJar来自于OkHttpClient初始化时创建的空CookieJar 
  public BridgeInterceptor(CookieJar cookieJar) {
    this.cookieJar = cookieJar;
  }
  @Override public Response intercept(Chain chain) throws IOException {
    ......
   //如果Cookie非空,则加入到Headers
    List cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
      requestBuilder.header("Cookie", cookieHeader(cookies));
    }
     //如果服务器端返回的Response的Headers中有Cookie,则将用户将其保存起来
    Response networkResponse = chain.proceed(requestBuilder.build());
    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
    ......
    return responseBuilder.build();
  }
 /**
    *将Cookie集合列表组装成String返回
    */
  private String cookieHeader(List cookies) {
    StringBuilder cookieHeader = new StringBuilder();
    for (int i = 0, size = cookies.size(); i < size; i++) {
      if (i > 0) {
        cookieHeader.append("; ");
      }
      Cookie cookie = cookies.get(i);
      cookieHeader.append(cookie.name()).append('=').append(cookie.value());
    }
    return cookieHeader.toString();
  }

Cookie是一种常用的会话跟踪技术,Cookie一般保存在客户端,是由服务器端发送来的。HTTP本身是一种无状态协议。服务器端无法单单从网络连接上知道客户身份,而Cookie可以解决这个问题,相当于服务器端给客户端颁发了一个通行证,这个通行证标明了客户端的身份,下次向服务器端发送请求,只要亮出这个通行证,服务器端就可以识别本次连接的客户端的身份,也就是说,Cookie是由服务器端创建并返回,由客户端存储相应的信息,用于下次发送请求时候,将Cookie发送给服务器端从而确定身份,实现会话追踪。比如网站登录出现的“请记住我”的选项、永久登录等功能会使用到Cookie。

Cookie在我们创建OkHttpClient的时候,会默认初始话,创建一个空的CookieJar,OkHttpClient提供了获取当前Request的CookieJar以及设置CookieJar 的方法,我们看下OkHttpClient.Builder的代码

 public static final class Builder {
    ...
     CookieJar cookieJar;
    ...
     public Builder() {
    ...
     cookieJar = CookieJar.NO_COOKIES;
    ...
     }
    ...
     public Builder cookieJar(CookieJar cookieJar) {
       if (cookieJar == null) throw new NullPointerException("cookieJar == null");
       this.cookieJar = cookieJar;
       return this;
     }
    ...
  }

接下来我们在看一下CookieJar的代码

public interface CookieJar {
    //对外提供一个空的CookieJar,表示不接受服务器端的任何Cookie
    CookieJar NO_COOKIES = new CookieJar() {
        public void saveFromResponse(HttpUrl url, List cookies) {
        }

        public List loadForRequest(HttpUrl url) {
            return Collections.emptyList();
        }
    };
    //保存服务器端返回的Cookie,此方法由用户端实现
    void saveFromResponse(HttpUrl var1, List var2);
    //获取Cookie列表,用户实现
    List loadForRequest(HttpUrl var1);
}

我们可以看到CookieJar接口提供了两个方法:saveFromResponse和loadForRequest。saveFromResponse是将服务器端返回来的Cookie信息从存储在客户端,具体如何存储,由用户来实现。loadForRequest是从本地获取到存储的Cookie信息,具体如何获取,由用户来实现。这样当我们调用OkHttpClient.Builder的cookieJar(CookieJar cookieJar)设置Cookie时候,就需要实现这两个方法,用来存取Cookie。如果我们没有设置Cookie,OkHttp框架默认给它赋值为CookieJar.NO_COOKIES,对两个方法做了空实现,既不存储也不获取,意味着不使用Cookie。我们通过BridgeInterceptor的代码阅读得知,在这个桥接拦截器中,我们对Cookie进行了存储和获取,可能有的同学会说,只看到了在发送请求的时候,调用了loadForRequest方法来获取Cookie,并未看到调用saveFromResponse存储服务器端返回来的Cookie,我们看下面代码,这是BridgeInterceptor获取到服务器返回的Cookie所做的操作:

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

我们在进去看一下HttpHeaders的receiveHeaders方法就 明白了:

public static void receiveHeaders(CookieJar cookieJar, HttpUrl url, Headers headers) {
  if (cookieJar == CookieJar.NO_COOKIES) return;
  List cookies = Cookie.parseAll(url, headers);
  if (cookies.isEmpty()) return;
  cookieJar.saveFromResponse(url, cookies);
}

看到cookieJar.saveFromResponse(url, cookies);这句代码,应该一切已经明白了,其本质就是调用了saveFromResponse方法,将服务器端返回来的Cookie存储到本地。

关于Accept-Encoding和Content-Encoding,请大家结合我的另一篇文章【连接】去理解一下BridgeInterceptor是如何进行各种请求头的添加,以及如何进行解码服务器端发送来的数据。

3.总结

关于BridgeInterceptor的讲解,比较粗糙,我们已经草草完事儿。稍微做一下总结:

  1. BridgeInterceptor对Request的Headers做了进一步封装,包括Content-Type、Content-Length、Connection、Accept-Encoding等请求头,分别针对客户端没有的设置,分别添加了默认值;
  2. BridgeInterceptor对Cookie做了处理,存储了服务器端发送来的Cookie,在请求的时候添加了客户传入的Cookie;
  3. BridgeInterceptor针服务器端的响应消息,做了解码、重新封装的操作,返回客户需要的Response。

个人写的不是很好,欢迎大家批评指正!下章节继续拦截器

 

 

你可能感兴趣的:(OkHttp系列,Cookie,拦截器,OkHttp)