一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情。
BridgeInterceptor是第二个框架提供的拦截器,是一个桥梁的功能,从应用程序用户侧代码到网络代码底层代码的桥梁。
在请求网络前,他负责配置通用的Header,比如cookie和content相关配置,在请求成功后,还会判断并使用Gzip进行解压。配置一些样板代码和功能,这些都是使用的用户不需要关心的内容,但是请求网络是需要这些内容的。
整个拦截器的拦截代码中,分为请求前和请求后两个阶段,所有的拦截器都有这种划分方式,类似装饰者模式,请求前做点事情,请求后做点事情。本篇分阶段详细了解一下。
请求前处理
请求前的操作主要是配置header字段,逐一分析下。
-
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()供我们使用。 -
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方法传入,在这个拦截器内就会设置进去。 -
Transfer-Encoding
这个值的设置和上面的Content-Length是互斥的,从代码中就能看出。如果能拿到运输数据的长度,就会移除这个字段,如果没有拿到长度,就通过配置Transfer-Encoding: chunked表示输出的内容长度不能确定。 -
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。如果和默认的端口不一致,还要带上端口。
-
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是一个接口,有两个抽象方法saveFromResponse和loadForRequest分别表示存取Cookie。如果我么要实现Cookie的设置,需要自己实现一个,并传入OkHttpClient。 -
User-Agent
用来让网络协议的对端来识别发起请求的用户代理软件的应用类型、操作系统、软件开发商以及版本号。if (userRequest.header("User-Agent") == null) { requestBuilder.header("User-Agent", Version.userAgent()); }Version.userAgent()默认返回
"okhttp/3.12.6";,也就是当前版本。 -
Connection
当前的事务完成后,是否会关闭网络连接。如果该值是“keep-alive”,网络连接就是持久的,不会关闭,使得对同一个服务器的请求可以继续在该连接上完成。if (userRequest.header("Connection") == null) { requestBuilder.header("Connection", "Keep-Alive"); }如果没有配置Connection,那么就是Keep-Alive,表示持久连接。
-
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)) {
。。。
}
先看解压操作要满足几个条件
-
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"); } -
"gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
如果返回的数据实体编码类型时gzip,这个字段时服务器处理响应实体的编码类型,如果时gzip,表示服务器已经做压缩处理了。 -
要有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,前段的缓存。