本章节我们来分析一下OkHttp系列之拦截器中的BridgeInterceptor(桥接拦截器),通过前面的讲解,我们已经知道的OkHttp做网络请求实质上就是通过拦截器的调用,实现与服务器端的连接已经数据传输,最后通过拦截器链将数据返回给用户。拦截器链最早调用的是Application 拦截器,然后是RetryAndFollowUpInterceptor重定向拦截器,接下来就是我们今天要讲的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();
}
}
我们解释下上面代码中的几个点:
我们在上面代码中使用到了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,请看我的另一篇博客[连接]。
我们看到在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; Listcookies = Cookie.parseAll(url, headers); if (cookies.isEmpty()) return; cookieJar.saveFromResponse(url, cookies); }
看到cookieJar.saveFromResponse(url, cookies);这句代码,应该一切已经明白了,其本质就是调用了saveFromResponse方法,将服务器端返回来的Cookie存储到本地。
关于Accept-Encoding和Content-Encoding,请大家结合我的另一篇文章【连接】去理解一下BridgeInterceptor是如何进行各种请求头的添加,以及如何进行解码服务器端发送来的数据。
关于BridgeInterceptor的讲解,比较粗糙,我们已经草草完事儿。稍微做一下总结:
个人写的不是很好,欢迎大家批评指正!下章节继续拦截器