OKHttp源码分析3 - HttpEngine底层实现
来源:互联网 发布:淘宝能开刻章店吗 编辑:程序博客网 时间:2024/05/18 12:39
1 概述
上篇文章,我们详细分析了OKHttp中Request的创建和发送过程。其中sendRequest(), readResponse(), followUpRequest()三个关键方法在底层HttpEngine中实现。革命尚未成功,我们接下来在这篇文章中分析HttpEngine中的这三个方法。由于底层HttpEngine涉及到很多Http协议方面东西,对Http协议不熟悉的同学可以先阅读我的这篇文章 Http协议简介
2 sendRequest()源码分析
sendRequest()方法是client向server发送request的主要方法。它先对request的header添加了一些默认字段,如keep-alive。然后对cache进行处理,判断是否可以直接使用cache。如果不行,才真正发送网络request。
public void sendRequest() throws RequestException, RouteException, IOException { if (cacheStrategy != null) return; // Already sent. if (transport != null) throw new IllegalStateException(); // 对header的处理,利用app中用户构造的原始request // 主要是对header进行添加。如添加"Connection: Keep-Alive"首部。后面单独分析 Request request = networkRequest(userRequest); // 对cache的处理 InternalCache responseCache = Internal.instance.internalCache(client); // 利用request为key,从cache中取出response Response cacheCandidate = responseCache != null ? responseCache.get(request) : null; // 判断cache是否可用,利用Expires,Last-Modified,Date,Age等header字段,后面详细分析 long now = System.currentTimeMillis(); cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get(); // cache可用或网络被禁止使用则networkRequest为null networkRequest = cacheStrategy.networkRequest; // cache不可用,则cacheResponse为null。对应情况有,不允许使用cache,没有对应cache,cache过期需要重新验证 cacheResponse = cacheStrategy.cacheResponse; if (responseCache != null) { responseCache.trackResponse(cacheStrategy); } if (cacheCandidate != null && cacheResponse == null) { // cache已过期,不可用,关闭它 closeQuietly(cacheCandidate.body()); } if (networkRequest != null) { // networkRequest不为空,代表cache不可用,且网络可用 // 从这儿可以看出,cache可用时会直接使用cache,不可用才走网络数据。这也是符合Http常规做法的。 if (connection == null) { // 连接到server,直接连接或通过代理均可 connect(); } // 构造HttpTransport,与发送request到网络中去有关 transport = Internal.instance.newTransport(connection, this); // 将start line,headers,body写入到buffer中,以等待发送出去 if (callerWritesRequestBody && permitsRequestBody(networkRequest) && requestBodyOut == null) { // 从request的header中获取content-length long contentLength = OkHeaders.contentLength(request); if (bufferRequestBody) { // bufferRequestBody表示body在内存中了,这可能是多次发送重试等情况 // content-length太大 if (contentLength > Integer.MAX_VALUE) { throw new IllegalStateException("Use setFixedLengthStreamingMode() or " + "setChunkedStreamingMode() for requests larger than 2 GiB."); } if (contentLength != -1) { // content-length已知,是个准确值 // 可以将start line和header写入HttpConnection中,此处涉及到Http报文结构和发送,后面重点讲解 transport.writeRequestHeaders(networkRequest); // 构造request body的buffer,长度为content-length requestBodyOut = new RetryableSink((int) contentLength); } else { // content-length还不确定,此时不能设置content-length首部,因为它还不确定。 // 要等到整个body准备好后,才能计算出content-length requestBodyOut = new RetryableSink(); } } else { transport.writeRequestHeaders(networkRequest); requestBodyOut = transport.createRequestBody(networkRequest, contentLength); } } } else { // networkRequest为null,要么cache可用,要么网络被禁止使用 if (connection != null) { // 回收网络connection,并关闭它 Internal.instance.recycle(client.getConnectionPool(), connection); connection = null; } if (cacheResponse != null) { // cache可用。可用代表有此request的cache response,且没有过期 this.userResponse = cacheResponse.newBuilder() .request(userRequest) .priorResponse(stripBody(priorResponse)) .cacheResponse(stripBody(cacheResponse)) .build(); } else { // 网络被禁止使用,自己构造一个504的response,gateway timeout this.userResponse = new Response.Builder() .request(userRequest) .priorResponse(stripBody(priorResponse)) .protocol(Protocol.HTTP_1_1) .code(504) .message("Unsatisfiable Request (only-if-cached)") .body(EMPTY_BODY) .build(); } // 将利用cache或自己生成的504response,进行gzip压缩 // 前面提到过,request的headers中声明了支持gzip压缩,故response中最好加入gzip压缩。 userResponse = unzip(userResponse); } }
我们接下来分析sendRequest()中使用到的一些比较重要的方法。networkRequest()方法作用为,在原有的request基础上添加一些header。从这些header中我们可以看出,OKHttp默认是使用Keep-Alive,response body支持gzip压缩,支持Cookie的使用。看到了吧,分析底层代码有助于我们对Http协议的理解和对OKHttp特性的掌握。
private Request networkRequest(Request request) throws IOException { Request.Builder result = request.newBuilder(); // 利用url解析出host,然后添加host header。它指明了server地址 if (request.header("Host") == null) { result.header("Host", Util.hostHeader(request.httpUrl())); } // 添加Connection首部,Keep-Alive表示持久连接,一次request和response完成后,HTTP并不立刻关闭。 if (request.header("Connection") == null) { result.header("Connection", "Keep-Alive"); } // 添加Accept-Encoding首部,gzip表示可接收gzip格式的压缩编码 if (request.header("Accept-Encoding") == null) { transparentGzip = true; result.header("Accept-Encoding", "gzip"); } // 处理cookie header CookieHandler cookieHandler = client.getCookieHandler(); if (cookieHandler != null) { // 将用户构建的原始request中的header弄成Map结构 Map<String, List<String>> headers = OkHeaders.toMultimap(result.build().headers(), null); // 从URI中解析出cookie,并添加到Map中,其key为"Cookie" Map<String, List<String>> cookies = cookieHandler.get(request.uri(), headers); // 添加Cookie和Cookie2 header OkHeaders.addCookies(result, cookies); } // 添加User-Agent header,它表示client端是啥东西,比如浏览器 // 对于OKHttp来说,就是okhttp和它的版本号 if (request.header("User-Agent") == null) { result.header("User-Agent", Version.userAgent()); } return result.build(); }
下面分析下cache是否可用的判断逻辑,也就是下面这行代码的执行逻辑。
cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
public final class CacheStrategy { // 构造方法,nowMillis为传入的系统此刻时间 public Factory(long nowMillis, Request request, Response cacheResponse) { this.nowMillis = nowMillis; this.request = request; this.cacheResponse = cacheResponse; if (cacheResponse != null) { // 取出response中的headers Headers headers = cacheResponse.headers(); // 遍历所有headers,解析出与cache过期有关的headers,并给相应成员变量赋值 for (int i = 0, size = headers.size(); i < size; i++) { String fieldName = headers.name(i); String value = headers.value(i); if ("Date".equalsIgnoreCase(fieldName)) { // Date header处理 servedDate = HttpDate.parse(value); servedDateString = value; } else if ("Expires".equalsIgnoreCase(fieldName)) { // Expires header处理 expires = HttpDate.parse(value); } else if ("Last-Modified".equalsIgnoreCase(fieldName)) { // Last-Modified header处理 lastModified = HttpDate.parse(value); lastModifiedString = value; } else if ("ETag".equalsIgnoreCase(fieldName)) { // ETag header处理 etag = value; } else if ("Age".equalsIgnoreCase(fieldName)) { // Age header处理 ageSeconds = HeaderParser.parseSeconds(value, -1); } else if (OkHeaders.SENT_MILLIS.equalsIgnoreCase(fieldName)) { sentRequestMillis = Long.parseLong(value); } else if (OkHeaders.RECEIVED_MILLIS.equalsIgnoreCase(fieldName)) { receivedResponseMillis = Long.parseLong(value); } } } } public CacheStrategy get() { // CacheStrategy生成的主要方法 CacheStrategy candidate = getCandidate(); if (candidate.networkRequest != null && request.cacheControl().onlyIfCached()) { // 网络被用户禁止使用,并且cache不可用,此时networkRequest和cacheResponse都为null return new CacheStrategy(null, null); } return candidate; } private CacheStrategy getCandidate() { // 没有此request的cache response if (cacheResponse == null) { return new CacheStrategy(request, null); } // 对于HTTPS,必须有handshake字段,否则认为此cache不可用 if (request.isHttps() && cacheResponse.handshake() == null) { return new CacheStrategy(request, null); } // 此response不能使用cache,比如金融类数据,一般追求实时性,不适合使用cache if (!isCacheable(cacheResponse, request)) { return new CacheStrategy(request, null); } // 使用cache前需要先验证一下保存的response,或者request中有条件GET的headers // noCache()方法命名不好,有歧义。它不是表示不能使用cache或者没有cache,而是表示使用前要先验证。 CacheControl requestCaching = request.cacheControl(); if (requestCaching.noCache() || hasConditions(request)) { return new CacheStrategy(request, null); } // 计算cache是否过期 long ageMillis = cacheResponseAge(); // 目前response生成时的绝对时间 long freshMillis = computeFreshnessLifetime(); // 到什么时候为止仍然是新鲜的,绝对时间 if (requestCaching.maxAgeSeconds() != -1) { freshMillis = Math.min(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds())); } long minFreshMillis = 0; // 剩余的最低的保鲜期,相对时间 if (requestCaching.minFreshSeconds() != -1) { minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds()); } long maxStaleMillis = 0; // 最大过期时间,相对时间 CacheControl responseCaching = cacheResponse.cacheControl(); if (!responseCaching.mustRevalidate() && requestCaching.maxStaleSeconds() != -1) { maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds()); } if (!responseCaching.noCache() && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) { Response.Builder builder = cacheResponse.newBuilder(); if (ageMillis + minFreshMillis >= freshMillis) { builder.addHeader("Warning", "110 HttpURLConnection \"Response is stale\""); } long oneDayMillis = 24 * 60 * 60 * 1000L; if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) { builder.addHeader("Warning", "113 HttpURLConnection \"Heuristic expiration\""); } // cache可用时,将networkRequest赋值为null,可以看出OKHttp是优先使用cache的 return new CacheStrategy(null, builder.build()); } // 条件GET的处理。 // 条件GET一般是cache过期了,需要发送验证request给server,以判断cache response是否修改了。如果没有修改,还是可以接着使用cache的。 Request.Builder conditionalRequestBuilder = request.newBuilder(); if (etag != null) { conditionalRequestBuilder.header("If-None-Match", etag); } else if (lastModified != null) { conditionalRequestBuilder.header("If-Modified-Since", lastModifiedString); } else if (servedDate != null) { conditionalRequestBuilder.header("If-Modified-Since", servedDateString); } Request conditionalRequest = conditionalRequestBuilder.build(); return hasConditions(conditionalRequest) ? new CacheStrategy(conditionalRequest, cacheResponse) : new CacheStrategy(conditionalRequest, null); }}
writeRequestHeaders()向HttpConnection的buffer中以UTF-8的编码格式写入start line和headers,合适的时机会发送到socket中传输出去
public final class HttpTransport implements Transport { // 这个名字起的不好,这个方法不仅写入了headers,还写入了start line public void writeRequestHeaders(Request request) throws IOException { // 发送request之前必须立刻调用,它记录了发送request的系统时间 httpEngine.writingRequestHeaders(); // 生成start line,后面有详细分析 String requestLine = RequestLine.get( request, httpEngine.getConnection().getRoute().getProxy().type()); // 将start line和headers写入到buffer中,UTF-8格式,合适的时机再将buffer中数据通过socket传输出去 httpConnection.writeRequest(request.headers(), requestLine); }}public final class RequestLine { // 生成request的start line,Http协议中它的格式为 method url version static String get(Request request, Proxy.Type proxyType) { StringBuilder result = new StringBuilder(); // 写入method result.append(request.method()); result.append(' '); // 写入url if (includeAuthorityInRequestLine(request, proxyType)) { result.append(request.httpUrl()); } else { result.append(requestPath(request.httpUrl())); } // 写入version,可以看到OKHttp支持的是HTTP/1.1版本 result.append(" HTTP/1.1"); return result.toString(); }}
3 readResponse()源码分析
public void readResponse() throws IOException { if (userResponse != null) { // response已经有了,这可能是利用cache生成的response或其他情况, // 此时我们就不用去接收server端的response了,其实一般此时也没有server端的response让我们去接收,哈哈~ return; } if (networkRequest == null && cacheResponse == null) { throw new IllegalStateException("call sendRequest() first!"); } if (networkRequest == null) { return; // No network response to read. } Response networkResponse; if (forWebSocket) { // 先将start line和header写入socket中 transport.writeRequestHeaders(networkRequest); // 发送request,并读取response,后面详细分析 networkResponse = readNetworkResponse(); } else if (!callerWritesRequestBody) { // 先执行拦截器,再写入request到HttpConnection的buffer中,最后发送buffer,并读取response // 和上面情况比较像,这里就不展开分析了 networkResponse = new NetworkInterceptorChain(0, networkRequest).proceed(networkRequest); } else { // 将request body的buffer发出去,这样requestBodyOut中就有了body if (bufferedRequestBody != null && bufferedRequestBody.buffer().size() > 0) { bufferedRequestBody.emit(); } // 处理request headers,并将start line和header写入socket中 if (sentRequestMillis == -1) { if (OkHeaders.contentLength(networkRequest) == -1 && requestBodyOut instanceof RetryableSink) { // 如果之前content-length值不清楚,此时在body已经ready的情况下,可以计算出content-length,并将它添加到header中 long contentLength = ((RetryableSink) requestBodyOut).contentLength(); networkRequest = networkRequest.newBuilder() .header("Content-Length", Long.toString(contentLength)) .build(); } // 将start line和header写入socket中 transport.writeRequestHeaders(networkRequest); } // 将body写入socket中 if (requestBodyOut != null) { if (bufferedRequestBody != null) { // This also closes the wrapped requestBodyOut. bufferedRequestBody.close(); } else { requestBodyOut.close(); } if (requestBodyOut instanceof RetryableSink) { // body 写入socket中 transport.writeRequestBody((RetryableSink) requestBodyOut); } } // 发送request,并读取response,后面会详细分析 networkResponse = readNetworkResponse(); } // 开始处理获取到的response // 读取并处理response的headers receiveHeaders(networkResponse.headers()); // cache response存在的情况下,应该是cache过期了,做了一次条件GET来验证cache的内容是否有变更。 // 根据Http协议,如果cache未变,回复304,not modified。且response中不会包含body, // 如果cache改变,回复200, OK。response中包含body if (cacheResponse != null) { if (validate(cacheResponse, networkResponse)) { // 再验证通过,cache内容未变,使用cache构造response userResponse = cacheResponse.newBuilder() .request(userRequest) .priorResponse(stripBody(priorResponse)) .headers(combine(cacheResponse.headers(), networkResponse.headers())) .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); networkResponse.body().close(); releaseConnection(); // 更新cache InternalCache responseCache = Internal.instance.internalCache(client); responseCache.trackConditionalCacheHit(); responseCache.update(cacheResponse, stripBody(userResponse)); userResponse = unzip(userResponse); return; } else { // cache未命中,response中会包含我们想要的body的。关闭cache body流 closeQuietly(cacheResponse.body()); } } // cache未命中,利用server返回的response string构造client使用的Response对象 // 此时会将response缓存起来,以便下次使用 userResponse = networkResponse.newBuilder() .request(userRequest) .priorResponse(stripBody(priorResponse)) .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); if (hasBody(userResponse)) { maybeCache(); userResponse = unzip(cacheWritingResponse(storeRequest, userResponse)); } }
下面详细分析readNetworkResponse(),它会通过socket流读取response string的start line,headers和body。
private Response readNetworkResponse() throws IOException { // 将HttpTransport中的buffer flush出去 transport.finishRequest(); // 读取server的response string,并构造出Response对象 Response networkResponse = transport.readResponseHeaders() .request(networkRequest) .handshake(connection.getHandshake()) .header(OkHeaders.SENT_MILLIS, Long.toString(sentRequestMillis)) .header(OkHeaders.RECEIVED_MILLIS, Long.toString(System.currentTimeMillis())) .build(); if (!forWebSocket) { networkResponse = networkResponse.newBuilder() .body(transport.openResponseBody(networkResponse)) .build(); } return networkResponse; }public final class HttpTransport implements Transport { @Override public Response.Builder readResponseHeaders() throws IOException { return httpConnection.readResponse(); }}public final class HttpConnection { public Response.Builder readResponse() throws IOException { if (state != STATE_OPEN_REQUEST_BODY && state != STATE_READ_RESPONSE_HEADERS) { throw new IllegalStateException("state: " + state); } try { while (true) { // 解析start line,response的start line格式为 protocol,code, message StatusLine statusLine = StatusLine.parse(source.readUtf8LineStrict()); // 将解析出的protocol, code, message分别放入构造的Response对象中 Response.Builder responseBuilder = new Response.Builder() .protocol(statusLine.protocol) .code(statusLine.code) .message(statusLine.message); // 解析response string的headers Headers.Builder headersBuilder = new Headers.Builder(); // 一行行读取headers, 直到遇到空行结束 readHeaders(headersBuilder); headersBuilder.add(OkHeaders.SELECTED_PROTOCOL, statusLine.protocol.toString()); // 将headers添加到Response对象中 responseBuilder.headers(headersBuilder.build()); // 如果返回code不是100, continue,则可以直接将Response对象返回 // 对于100,continue,server还会继续返回response string,我们需要在while循环中继续接收并解析 if (statusLine.code != HTTP_CONTINUE) { state = STATE_OPEN_RESPONSE_BODY; return responseBuilder; } } } catch (EOFException e) { // Provide more context if the server ends the stream before sending a response. IOException exception = new IOException("unexpected end of stream on " + connection + " (recycle count=" + Internal.instance.recycleCount(connection) + ")"); exception.initCause(e); throw exception; } }}
4 followUpRequest()源码分析
client发送一个request之后,server可能回复一个重定向的response,并在这个response中告知client需要重新访问的server的IP。此时,client需要重新向新的server发送request,并等待新server的回复。所以我们需要单独判断重定向response,并发送多次request。有了OKHttp,这一切你都不用管,它会自动帮你完成所有这一切。OKHttp中followUpRequest()方法就是完成这个功能的。是不是瞬间感觉OKHttp强大到不要不要的啊~。这个方法流程比较简单,就不给出流程图了。亲们如果对Http协议不熟悉,可以先看我的这篇文章Http协议简介
public Request followUpRequest() throws IOException { if (userResponse == null) throw new IllegalStateException(); Proxy selectedProxy = getRoute() != null ? getRoute().getProxy() : client.getProxy(); int responseCode = userResponse.code(); // 利用responseCode来分析是否需要自动发送后续request switch (responseCode) { // 未认证用户,不能访问server或代理,故需要发送认证的request case HTTP_PROXY_AUTH: if (selectedProxy.type() != Proxy.Type.HTTP) { throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy"); } case HTTP_UNAUTHORIZED: return OkHeaders.processAuthHeader(client.getAuthenticator(), userResponse, selectedProxy); // 永久重定向,暂时重定向,永久移动了等和重定向相关的response,response code中以3打头的都是 // 它们需要重新发送request给新的server,新sever的ip在response中会给出 case HTTP_PERM_REDIRECT: case HTTP_TEMP_REDIRECT: if (!userRequest.method().equals("GET") && !userRequest.method().equals("HEAD")) { return null; } case HTTP_MULT_CHOICE: case HTTP_MOVED_PERM: case HTTP_MOVED_TEMP: case HTTP_SEE_OTHER: // Does the client allow redirects? if (!client.getFollowRedirects()) return null; // 新的server的IP地址在response的Location header中给出 String location = userResponse.header("Location"); if (location == null) return null; HttpUrl url = userRequest.httpUrl().resolve(location); // Don't follow redirects to unsupported protocols. if (url == null) return null; // If configured, don't follow redirects between SSL and non-SSL. boolean sameScheme = url.scheme().equals(userRequest.httpUrl().scheme()); if (!sameScheme && !client.getFollowSslRedirects()) return null; // Redirects don't include a request body. Request.Builder requestBuilder = userRequest.newBuilder(); if (HttpMethod.permitsRequestBody(userRequest.method())) { requestBuilder.method("GET", null); requestBuilder.removeHeader("Transfer-Encoding"); requestBuilder.removeHeader("Content-Length"); requestBuilder.removeHeader("Content-Type"); } // 删掉用户认证信息 if (!sameConnection(url)) { requestBuilder.removeHeader("Authorization"); } return requestBuilder.url(url).build(); default: return null; } }
5 总结
OKHttp底层源码还是相当复杂的,毕竟它的功能如此之强大嘛。OKHttp默认采用了Keep-Alive持久连接技术,可支持gzip编码的response。在cache的处理上,如果cache可用,则直接使用cache,否则使用网络数据。OKHttp会做cache过期的判断和过期后的再验证。有了OKHttp,这一切你都不用管,它帮你cover掉了!
当需要做用户验证和重定向时,我们一般需要发送认证request,或向新server发送request,也就是要重新再生成新request并发送出去。有了OKHttp,这一切你都不用管,它又帮你cover掉了!
OKHttp功能实在是强大到爆表,掌握好了它的实现原理和底层流程之后,你就可以在项目中游刃有余的放心使用它了!另外,小编可是花了整个周末才完成了这几篇文章,走过路过的朋友帮忙写写评论吧,谢谢!
- OKHttp源码分析3 - HttpEngine底层实现
- Android 4.4以上使用HttpURLConnection底层使用OkHttp实现的源码分析
- epoll 底层实现源码分析
- 《android源码分析系列》仿OkHttp-- 自己动手实现 okhttp
- OkHttp源码分析
- OkHttp源码分析
- OkHttp源码分析
- OKHttp源码分析
- OkHttp源码分析
- OKHttp源码分析
- okhttp源码分析
- OkHttp源码分析
- HashMap底层源码分析
- EventBus源码底层分析
- Hash底层源码分析
- OkHttp面试之--HttpEngine中的readResponse流程简介
- ConcurrentHashMap源码阅读以及底层实现的简单分析
- Java中HashMap底层实现原理(JDK1.8)源码分析
- 有n个数,两两组成二元组,差最小的有多少对呢?差最大呢?
- Linux内存管理原理
- iOS 即时通讯,简单socket网络编程一<socket 底层>
- 华为上机笔试 2016-9-4
- 欢迎大神们来挑刺(关于C51串口通信)
- OKHttp源码分析3 - HttpEngine底层实现
- Build.Gradle 文件解析
- spring整合struts2
- API文档使用方法
- 【hibernate】关联映射那些事(四)----继承映射
- PowerDesigner连接MySQL失败的解决方式
- 【u032】均衡发展
- Educational Codeforces Round 7-C. Not Equal on a Segment
- JavaEE框架——Springmvc的使用