OkHttp深入理解(3)BridgeInterceptor与CacheInterceptor
来源:互联网 发布:大麦网 数据 编辑:程序博客网 时间:2024/06/06 17:20
上一篇笔记主要记录了RetryAndFollowUpInterceptor的功能,主要负责根据response决定是否对请求进行重定向。这篇笔记记录链中的下两个结点BridgeInterceptor与CacheInterceptor。
BridgeInterceptor
BridgeInterceptor负责在request阶段对请求头添加一些字段,在response阶段对响应进行一些gzip解压操作。
Request阶段
在Request阶段,BridgeInterceptor主要做了添加请求首部字段的操作。具体如下:
RequestBody body = userRequest.body(); if (body != null) { MediaType contentType = body.contentType(); if (contentType != null) { requestBuilder.header("Content-Type", contentType.toString()); } 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"); } } if (userRequest.header("Host") == null) { requestBuilder.header("Host", hostHeader(userRequest.url(), false)); } if (userRequest.header("Connection") == null) { requestBuilder.header("Connection", "Keep-Alive"); } // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing // the transfer stream. boolean transparentGzip = false; if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) { transparentGzip = true; requestBuilder.header("Accept-Encoding", "gzip"); } List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url()); if (!cookies.isEmpty()) { requestBuilder.header("Cookie", cookieHeader(cookies)); } if (userRequest.header("User-Agent") == null) { requestBuilder.header("User-Agent", Version.userAgent()); }
各个字段解析如下:
字段名 含义 Content-Type 实体主体的媒体类型 Content-Length 实体主体的大小(单位:字节) Transfer-Encoding 指定报文主体的传输编码方式 Host 客户端指定想访问的http服务器的域名/IP和端口号 Connection 当client和server通信时对于长链接如何进行处理 Accept-Encoding 优先的内容编码 Range 实体的字节范围 Cookie 设置cookies User-Agent HTTP客户端程序的信息
Response阶段
客户端收到response之后,判断是否需要进行gzip解压,然后将response返回。
Gzip是一种流行的文件压缩算法,现在的应用十分广泛,尤其是在Linux平台。当应用Gzip压缩到一个纯文本文件时,效果是非常明显的,大约可以减少70%以上的文件大小。这取决于文件中的内容。
Response networkResponse = chain.proceed(requestBuilder.build()); HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers()); Response.Builder responseBuilder = networkResponse.newBuilder() .request(userRequest); if (transparentGzip && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding")) && HttpHeaders.hasBody(networkResponse)) { //进行解压 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))); } return responseBuilder.build();
要注意的是,返回的response中读取到的request是没有进行BridgeInterceptor中添加header处理的、客户端原始的request。这也是为什么需要使用Response.Builder进行重新构建response的原因
以上就是BridgeInterceptor的大概逻辑。
CacheInterceptor
CacheInterceptor负责在request阶段判断是否有缓存,是否需要重新请求。在response阶段负责把response缓存起来。
CacheInterceptor的总体流程大致是:
1. 读取候选缓存(创建Client时可配置)
2. 创建缓存策略(根据头信息,判断强制缓存、对比缓存等策略)
3. 根据策略,不需要网络请求且没有缓存时构建一个状态码为504的Response,提示错误
4. 根据策略,不需要网络请求但存在缓存时,将缓存返回
5. 前面步骤都没返回,则从网络中读取(推进到链中的下一个结点继续执行)
6. 获取到网络返回的networkResponse
,如果状态码为304(Not Modified),说明缓存可用,直接返回缓存结果
7. 如果步骤6没有返回,则说明缓存已过期,则根据网络中返回的networkResponse
构建response
8. 将网络中返回的Response缓存
9. 返回response
源码解析如下:
public Response intercept(Chain chain) throws IOException {//获取构建Client时设置的cache Response cacheCandidate = cache != null ? cache.get(chain.request()) : null; long now = System.currentTimeMillis();//创建缓存策略 CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get(); Request networkRequest = strategy.networkRequest; Response cacheResponse = strategy.cacheResponse; if (cache != null) { cache.trackResponse(strategy); } if (cacheCandidate != null && cacheResponse == null) { closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it. } // If we're forbidden from using the network and the cache is insufficient, fail. if (networkRequest == null && cacheResponse == null) { return new Response.Builder() .request(chain.request()) .protocol(Protocol.HTTP_1_1) .code(504) .message("Unsatisfiable Request (only-if-cached)") .body(Util.EMPTY_RESPONSE) .sentRequestAtMillis(-1L) .receivedResponseAtMillis(System.currentTimeMillis()) .build(); } // If we don't need the network, we're done. if (networkRequest == null) { return cacheResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .build(); } Response networkResponse = null; try { networkResponse = chain.proceed(networkRequest); } finally { // If we're crashing on I/O or otherwise, don't leak the cache body. if (networkResponse == null && cacheCandidate != null) { closeQuietly(cacheCandidate.body()); } } // If we have a cache response too, then we're doing a conditional get. if (cacheResponse != null) { if (networkResponse.code() == HTTP_NOT_MODIFIED) { Response response = cacheResponse.newBuilder() .headers(combine(cacheResponse.headers(), networkResponse.headers())) .sentRequestAtMillis(networkResponse.sentRequestAtMillis()) .receivedResponseAtMillis(networkResponse.receivedResponseAtMillis()) .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); networkResponse.body().close(); // Update the cache after combining headers but before stripping the // Content-Encoding header (as performed by initContentStream()). cache.trackConditionalCacheHit(); cache.update(cacheResponse, response); return response; } else { closeQuietly(cacheResponse.body()); } } Response response = networkResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); if (cache != null) { if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) { // Offer this request to the cache. CacheRequest cacheRequest = cache.put(response); return cacheWritingResponse(cacheRequest, response); } if (HttpMethod.invalidatesCache(networkRequest.method())) { try { cache.remove(networkRequest); } catch (IOException ignored) { // The cache cannot be written. } } } return response; }
源码中有许多注释,大致逻辑还是比较清晰的。但是缓存策略中还有许多细节没有深入了解。等以后有空时再补充。
需要注意的是,OkHttp只提供了针对GET请求的缓存,因为GET请求的参数都放在url中,因此url可以作为唯一标识;而POST请求的参数在RequestBody中,url无法作为唯一标识。如果要实现对POST请求的缓存,需要手动写一个Interceptor。针对POST的缓存,可以通过读取RequestBody中的参数,计算MD5,拼接请求的url作为唯一标识,然后利用Sqlite或者DiskLruCache以文件形式缓存到手机上。
- OkHttp深入理解(3)BridgeInterceptor与CacheInterceptor
- OkHttp之BridgeInterceptor简单分析
- Okhttp之CacheInterceptor简单分析
- OKhttp源码解析---拦截器之BridgeInterceptor
- OkHttp深入理解(1)综述
- OkHttp深入理解(2)RetryAndFollowUpInterceptor
- OkHttp深入理解(4)ConnectInterceptor
- OkHttp深入理解(5)CallServerInterceptor
- 深入理解Okhttp
- OKhttp源码解析---拦截器之CacheInterceptor
- 深入理解OkHttp源码(一)——提交请求
- 深入理解OkHttp源码(二)——获取响应
- 深入理解OkHttp源码(三)——网络操作
- 深入理解OkHttp源码(四)——缓存
- Okhttp3源码(2)---CacheInterceptor解析
- 深入理解矩阵与渲染(一)
- 深入理解计算机系统(3)
- 深入理解JVM(3)
- 常用adb口令
- Linux服务器下使用putty pscp配置java环境
- Linux定时任务-java程序
- nfs连接
- Spring Tool Suite(STS3.9.0)自动导包快捷键设置问题
- OkHttp深入理解(3)BridgeInterceptor与CacheInterceptor
- 国庆清北刷题冲刺班 Day1 上午
- mysql 查询当天、本周,本月,上一个月的数据
- 读写优先问题
- org.tuckey.web.filters.urlrewrite.UrlRewriteFilter
- 25个常用的Linux iptables规则
- Web上的用户登录功能安全
- File IO(NIO.2):什么是路径?
- 将博客搬至CSDN