BridgeInterceptor
系列
OKHttp3–详细使用及源码分析系列之初步介绍【一】
OKHttp3–流程分析 核心类介绍 同步异步请求源码分析【二】
OKHttp3–Dispatcher分发器源码解析【三】
OKHttp3–调用对象RealCall源码解析【四】
OKHttp3–拦截器链RealInterceptorChain源码解析【五】
OKHttp3–重试及重定向拦截器RetryAndFollowUpInterceptor源码解析【六】
OKHttp3–桥接拦截器BridgeInterceptor源码解析及相关http请求头字段解析【七】
OKHttp3–缓存拦截器CacheInterceptor源码解析【八】
OKHttp3-- HTTP缓存机制解析 缓存处理类Cache和缓存策略类CacheStrategy源码分析 【九】
BridgeInterceptor
本篇文章对拦截器链中的第二个拦截器即桥接拦截器进行解析
通过前面的文章我们大概知道OKHttp实现网络请求实际上就是通过一个个拦截器的调用,实现与服务器的连接与数据传输,最后再通过拦截器链将请求响应返回给用户;那处于拦截器链中的第二个拦截器在其中扮演什么角色呢,或者说有什么作用呢?(开发者为添加拦截器的情况下)
其实从名字我们大概能猜到一些奥妙,Bridge中文意思即桥梁,连接的意思,那在这里其实就是连接应用程序和服务器的桥梁,我们发出的请求将会经过它的处理才能成为一个服务器能识别的网络请求;所以它的具体作用就是在真正进行网络请求前对我们的请求头做一些设置,比如设置请求内容长度,编码,gzip压缩,cookie等,获取响应后为响应添加一些响应头信息,如图
构造方法
由于这个拦截器代码比较少,我就从头开始分析了,先看构造方法
public BridgeInterceptor(CookieJar cookieJar) {
this.cookieJar = cookieJar;
}
构造方法接收一个CookieJar,还记得在RealCall中怎么实现这个拦截器的吗
interceptors.add(new BridgeInterceptor(client.cookieJar()));
原来这个参数是从OKHttpClient中取的,其实回去看看OKHttpClient的实现就知道在Build过程中会实例化一个CookieJar
public Builder() {
......
cookieJar = CookieJar.NO_COOKIES;
......
}
这里只是提供一个默认的CookieJar的实现,到CookieJar中去看看
CookieJar
public interface CookieJar {
CookieJar NO_COOKIES = new CookieJar() {
@Override
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
}
@Override
public List<Cookie> loadForRequest(HttpUrl url) {
return Collections.emptyList();
}
};
void saveFromResponse(HttpUrl url, List<Cookie> cookies);
List<Cookie> loadForRequest(HttpUrl url);
}
可以看到BridgeInterceptor的构造方法接收的只是一个空的CookieJar 实现,默认不对Cookie进行处理,那这个接口有什么作用呢?
为HTTP Cookie提供策略和持久化操作
策略也就是选择接收哪些cookie和拒绝哪些cookie
持久化也就是提供cookie的存储,可以存储在内存中,也可以在文件里,还可以在数据库中
所以如果我们想要保存Cookie,那我们需要自定义一个类去实现CookieJar接口,然后实现其中的方法,对Cookie做一些保存操作,最后在通过Build构建OKHttpClient的时候调用 public Builder cookieJar(CookieJar cookieJar) 方法去设置就行了
有人可能好奇了,Cookie是什么呢?
Cookie
用下图来展示Cookie的作用
简单的说,Cookie就是一些数据,用于服务器识别客户端的凭证,以避免每次访问服务器都要重复登录
因为HTTP协议是无状态的,你第一次连接服务器并登录成功后,下一次再访问服务器,服务器还是不知道你是谁;而有了Cookie后,就能解决这些问题;第一次登录成功后,服务器返回一些数据(Cookie)给浏览器,浏览器保存在本地;下次再访问服务器时,把这些数据即Cookie一起发送给服务器,服务器就能根据这些数据判断你是之前登录的那位用户;cookie存储的数据量有限,不同的浏览器有不同的存储大小,但一般不超过4KB。因此使用cookie只能存储一些小量的数据
说起Cookie,你可能还会想到Session,有一篇文章讲的比较全面理解Cookie和Session机制
BridgeInterceptor.intercept
该类第二个方法就是拦截方法了
@Override
public Response intercept(Chain chain) throws IOException {
//获取请求信息
Request userRequest = chain.request();
//构建一个包含当前Request数据的Build对象
Request.Builder requestBuilder = userRequest.newBuilder();
// 获取请求体对象
RequestBody body = userRequest.body();
if (body != null) {
MediaType contentType = body.contentType();
if (contentType != null) {
// 如果用户设置了Content-Type,那就添加该头部信息
// 设置请求体的MIME类型
requestBuilder.header("Content-Type", contentType.toString());
}
long contentLength = body.contentLength();
if (contentLength != -1) {
//如果用户设置了Content-Length,那就添加该头部信息,并删除Transfer-Encoding
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");
}
}
// 添加host头部信息,即主机名
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
/**
* 我们知道以前HTTP是无状态的,即进行请求时的操作是
* 先进行TCP三次握手,再进行传输,最后释放连接;
* 当网络内容复杂时候,需要多次数据传输,那么就会造成频繁的创建Socket和释放,频繁的握手
* 延时和资源的消耗的问题就产生了
* HTTP1.1开始引入长连接或者说持久连接后,这个问题得到解决
* Keep-Alive机制可以在数据传输完成之后仍然保持TCP连接,以备后用
* 当用户需要再次获取数据时,直接使用刚刚空闲下来的连接,无需再次握手,这样就解决了延时问题,提高了HTTP的性能
*/
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive");
}
// 设置gzip压缩,告诉服务器,客户端是支持gzip的,希望服务器返回的数据是经过gzip压缩的
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true;
//Accept-Encoding 向服务器端表明客户端支持的编码格式
requestBuilder.header("Accept-Encoding", "gzip");
}
// 如果Cookie非空,则加入到请求头
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
//如果没有设置用户代理,则默认为当前OkHttp的版本“okhttp/3.7.0”
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
//调用拦截器链的下一个拦截器,获取响应
Response networkResponse = chain.proceed(requestBuilder.build());
/**
* 如果服务器给我们返回了Cookie,那就通过CookieJar的具体实现类去保存
* 当然了,如果开发者没有设置CookieJar的实现类,默认是不会保存的
*/
HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
//新建一个包含Request所有信息的Response.Builder实例
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
/**
* 第一个判断是:客户端是否设置了gzip压缩,也就是有没有告诉服务器你要给我一个gzip压缩的响应体
* 第二个判断是:服务器支不支持gzip压缩,也就是有没有对响应进行gzip压缩
* 第三个判断是:有没有响应体
* 如果三个条件都满足,说明响应体是经过gzip压缩的,那么就需要进行解压
* 最后构建用户需要的Response
* Content-Encoding 表示响应体内容的编码格式
*/
if (transparentGzip
&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& HttpHeaders.hasBody(networkResponse)) {
// 将响应体的输入流转换成GzipSource类型,也就是进行解压
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();
}
ridgeInterceptor.cookieHeader
将所有的Cookie组装成String返回,添加到请求头
private String cookieHeader(List<Cookie> 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();
}
总结
从上面的代码可以看到,桥接拦截器的执行逻辑主要就是以下几点
- 对用户构建的Request进行添加或者删除相关头部信息,以转化成能够真正进行网络请求的Request
- 将符合网络请求规范的Request交给下一个拦截器处理,并获取Response
- 如果响应体经过了GZIP压缩,那就需要解压,再构建成用户可用的Response并返回
还有一点需要注意的是桥接拦截器可能会被执行多次,或者说重试及重定向拦截器后面的拦截器都有可能会被执行多次