一直搞开发,也使用各种框架,但是基本上也从来没研究过这些框架的底层是如何实现的。像我们客户端,一般重要的事情就是:网络请求、图片处理、操作数据库、展现View界面,刚好今天哥们有问我一个okhttp网络请求的问题,帮他看完了之后,就详细的走了一遍okhttp框架实现文件上传的基本流程,也算是对网络请求这块的东西学习一下。
先来看一下我按照思路整理出来的详细的流程图。说起流程图,大家还不能单独看,必须要结合源码一起,流程图时刻可以帮助我们找到当前的点,但是没有源码,光看流程图,你根本不知道这是干什么的,所以大家参考的时候,还是需要对照源码一起分析,哦,还需要说一下,我当前使用的是okhttp-3.2.0.jar的包,可能不同的版本,细节实现会有一些差别。
流程图当中几个重要的节点我都要红色标注出来了。我要在客户端测试的代码非常简单,因为代码片现在越来越多了,这里就不贴代码了,直接上一张图,因为代码很少,所以大家看着也方便。
按照上图的流程,我们把整个文件上传的过程三为三个小节来分析:1、RealCall.getResponse之前的逻辑;2、RealCall类的getResponse方法中调用HttpEngine.sendRequest发送请求;3、RealCall类的getResponse方法中调用HttpEngine.readResponse读取响应。
1、RealCall.getResponse之前的逻辑
这一步中的逻辑都比较简单,就是一些数据封装类的操作,首先获取到我们要上传的文件,以我们的目标文件为参数创建一个RequestBody对象,我们来看一下RequestBody类的create(MediaType.parse(TYPE), file)方法的实现:
public static RequestBody create(final MediaType contentType, final File file) {
if(file == null) {
throw new NullPointerException("content == null");
} else {
return new RequestBody() {
public MediaType contentType() {
return contentType;
}
public long contentLength() {
return file.length();
}
public void writeTo(BufferedSink sink) throws IOException {
Source source = null;
try {
source = Okio.source(file);
sink.writeAll(source);
} finally {
Util.closeQuietly(source);
}
}
};
}
}
public void enqueue(Callback responseCallback) {
this.enqueue(responseCallback, false);
}
void enqueue(Callback responseCallback, boolean forWebSocket) {
synchronized(this) {
if(this.executed) {
throw new IllegalStateException("Already Executed");
}
this.executed = true;
}
this.client.dispatcher().enqueue(new RealCall.AsyncCall(responseCallback, forWebSocket));
}
protected void execute() {
boolean signalledCallback = false;
try {
Response e = RealCall.this.getResponseWithInterceptorChain(this.forWebSocket);
if(RealCall.this.canceled) {
signalledCallback = true;
this.responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
this.responseCallback.onResponse(RealCall.this, e);
}
} catch (IOException var6) {
if(signalledCallback) {
Internal.logger.log(Level.INFO, "Callback failure for " + RealCall.this.toLoggableString(), var6);
} else {
this.responseCallback.onFailure(RealCall.this, var6);
}
} finally {
RealCall.this.client.dispatcher().finished(this);
}
}
看一下这个方法的代码,再对比一下我们的流程图,就可以看到,最终回调到我们应用层就是在这里的。而返回给我们的Response对象就是通过getResponseWithInterceptorChain获取到的。当我们的请求因异常而失败时,okhttp框架中都会将其取消,所以最终的结果就根据RealCall.this.canceled标志位来判断,如果取消了,那么肯定是请求失败了,否则请求就是成功的,最后请求完成后,将当前的call从分发器中移除。好,对照着流程图,接下来我们看一下RealCall类的getResponseWithInterceptorChain方法的实现,它当中就是直接调用ApplicationInterceptorChain内部类对象,然后直接调用它的proceed方法。构造它时传进来的第一个参数index为0,而我们当前的实现中,client中的拦截器数量也是0,所以就直接调用RealCall.this.getResponse进行处理。这个方法也是比较核心的方法了,从我们的流程图就可以看出来,在它当中调用了我们下面要讲的两具节点方法:sendRequest()、readResponse(),发送请求,读取响应,读取响应完成后,会将结果保存在HttpEngine类的成员变量userResponse当中,最后将它通过回调返回给应用层。我们来看一下getResponse方法的实现:
Response getResponse(Request request, boolean forWebSocket) throws IOException {
RequestBody body = request.body();
if(body != null) {
Builder followUpCount = request.newBuilder();
MediaType releaseConnection = body.contentType();
if(releaseConnection != null) {
followUpCount.header("Content-Type", releaseConnection.toString());
}
long response = body.contentLength();
if(response != -1L) {
followUpCount.header("Content-Length", Long.toString(response));
followUpCount.removeHeader("Transfer-Encoding");
} else {
followUpCount.header("Transfer-Encoding", "chunked");
followUpCount.removeHeader("Content-Length");
}
request = followUpCount.build();
}
this.engine = new HttpEngine(this.client, request, false, false, forWebSocket, (StreamAllocation)null, (RetryableSink)null, (Response)null);
int var20 = 0;
while(!this.canceled) {
boolean var21 = true;
boolean var15 = false;
StreamAllocation streamAllocation;
label173: {
label172: {
try {
HttpEngine followUp;
try {
var15 = true;
this.engine.sendRequest();
this.engine.readResponse();
var21 = false;
var15 = false;
break label173;
} catch (RequestException var16) {
throw var16.getCause();
} catch (RouteException var17) {
followUp = this.engine.recover(var17.getLastConnectException(), (Sink)null);
if(followUp == null) {
throw var17.getLastConnectException();
}
} catch (IOException var18) {
followUp = this.engine.recover(var18, (Sink)null);
if(followUp != null) {
var21 = false;
this.engine = followUp;
var15 = false;
break label172;
}
throw var18;
}
var21 = false;
this.engine = followUp;
var15 = false;
} finally {
if(var15) {
if(var21) {
StreamAllocation streamAllocation1 = this.engine.close();
streamAllocation1.release();
}
}
}
if(var21) {
streamAllocation = this.engine.close();
streamAllocation.release();
}
continue;
}
if(var21) {
streamAllocation = this.engine.close();
streamAllocation.release();
}
continue;
}
if(var21) {
StreamAllocation var22 = this.engine.close();
var22.release();
}
Response var23 = this.engine.getResponse();
Request var24 = this.engine.followUpRequest();
if(var24 == null) {
if(!forWebSocket) {
this.engine.releaseStreamAllocation();
}
return var23;
}
streamAllocation = this.engine.close();
++var20;
if(var20 > 20) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + var20);
}
if(!this.engine.sameConnection(var24.url())) {
streamAllocation.release();
streamAllocation = null;
}
this.engine = new HttpEngine(this.client, var24, false, false, forWebSocket, streamAllocation, (RetryableSink)null, var23);
}
this.engine.releaseStreamAllocation();
throw new IOException("Canceled");
}
2、RealCall类的getResponse方法中调用HttpEngine.sendRequest发送请求
再来对照流程图,从中也可以看到,这一步的任务就是发送消息头,我们来看一下这个方法的实现:
public void sendRequest() throws RequestException, RouteException, IOException {
if(this.cacheStrategy == null) {
if(this.httpStream != null) {
throw new IllegalStateException();
} else {
Request request = this.networkRequest(this.userRequest);
InternalCache responseCache = Internal.instance.internalCache(this.client);
Response cacheCandidate = responseCache != null?responseCache.get(request):null;
long now = System.currentTimeMillis();
this.cacheStrategy = (new Factory(now, request, cacheCandidate)).get();
this.networkRequest = this.cacheStrategy.networkRequest;
this.cacheResponse = this.cacheStrategy.cacheResponse;
if(responseCache != null) {
responseCache.trackResponse(this.cacheStrategy);
}
if(cacheCandidate != null && this.cacheResponse == null) {
Util.closeQuietly(cacheCandidate.body());
}
if(this.networkRequest == null && this.cacheResponse == null) {
this.userResponse = (new Builder()).request(this.userRequest).priorResponse(stripBody(this.priorResponse)).protocol(Protocol.HTTP_1_1).code(504).message("Unsatisfiable Request (only-if-cached)").body(EMPTY_BODY).build();
} else if(this.networkRequest == null) {
this.userResponse = this.cacheResponse.newBuilder().request(this.userRequest).priorResponse(stripBody(this.priorResponse)).cacheResponse(stripBody(this.cacheResponse)).build();
this.userResponse = this.unzip(this.userResponse);
} else {
boolean success = false;
try {
this.httpStream = this.connect();
this.httpStream.setHttpEngine(this);
if(this.writeRequestHeadersEagerly()) {
long contentLength = OkHeaders.contentLength(request);
if(this.bufferRequestBody) {
if(contentLength > 2147483647L) {
throw new IllegalStateException("Use setFixedLengthStreamingMode() or setChunkedStreamingMode() for requests larger than 2 GiB.");
}
if(contentLength != -1L) {
this.httpStream.writeRequestHeaders(this.networkRequest);
this.requestBodyOut = new RetryableSink((int)contentLength);
} else {
this.requestBodyOut = new RetryableSink();
}
} else {
this.httpStream.writeRequestHeaders(this.networkRequest);
this.requestBodyOut = this.httpStream.createRequestBody(this.networkRequest, contentLength);
}
}
success = true;
} finally {
if(!success && cacheCandidate != null) {
Util.closeQuietly(cacheCandidate.body());
}
}
}
}
}
}
public void writeRequestHeaders(Request request) throws IOException {
if(this.stream == null) {
this.httpEngine.writingRequestHeaders();
boolean permitsRequestBody = this.httpEngine.permitsRequestBody(request);
List requestHeaders = this.framedConnection.getProtocol() == Protocol.HTTP_2?http2HeadersList(request):spdy3HeadersList(request);
boolean hasResponseBody = true;
this.stream = this.framedConnection.newStream(requestHeaders, permitsRequestBody, hasResponseBody);
this.stream.readTimeout().timeout((long)this.httpEngine.client.readTimeoutMillis(), TimeUnit.MILLISECONDS);
this.stream.writeTimeout().timeout((long)this.httpEngine.client.writeTimeoutMillis(), TimeUnit.MILLISECONDS);
}
}
public FramedStream newStream(List requestHeaders, boolean out, boolean in) throws IOException {
return this.newStream(0, requestHeaders, out, in);
}
private FramedStream newStream(int associatedStreamId, List requestHeaders, boolean out, boolean in) throws IOException {
boolean outFinished = !out;
boolean inFinished = !in;
FrameWriter var9 = this.frameWriter;
FramedStream stream;
synchronized(this.frameWriter) {
int streamId;
synchronized(this) {
if(this.shutdown) {
throw new IOException("shutdown");
}
streamId = this.nextStreamId;
this.nextStreamId += 2;
stream = new FramedStream(streamId, this, outFinished, inFinished, requestHeaders);
if(stream.isOpen()) {
this.streams.put(Integer.valueOf(streamId), stream);
this.setIdle(false);
}
}
if(associatedStreamId == 0) {
this.frameWriter.synStream(outFinished, inFinished, streamId, associatedStreamId, requestHeaders);
} else {
if(this.client) {
throw new IllegalArgumentException("client streams shouldn\'t have associated stream IDs");
}
this.frameWriter.pushPromise(associatedStreamId, streamId, requestHeaders);
}
}
if(!out) {
this.frameWriter.flush();
}
return stream;
}
再回来,对照流程图,看一下Http2xStream类的createRequestBody方法。它当就很简单,就是调用getSink()将上一步返回来的FramedStream对象的成员变量sink取出来返回给调用者。注意,这里返回的实体是FramedStream.FramedDataSink对象,它是实现了Sink接口的。
好了,请求头发送完了,再来对照一下流程图,接下来就是读取响应了。
3、RealCall类的getResponse方法中调用HttpEngine.readResponse读取响应
我们来看一下这个方法的代码:
public void readResponse() throws IOException {
if(this.userResponse == null) {
if(this.networkRequest == null && this.cacheResponse == null) {
throw new IllegalStateException("call sendRequest() first!");
} else if(this.networkRequest != null) {
Response networkResponse;
if(this.forWebSocket) {
this.httpStream.writeRequestHeaders(this.networkRequest);
networkResponse = this.readNetworkResponse();
} else if(!this.callerWritesRequestBody) {
networkResponse = (new HttpEngine.NetworkInterceptorChain(0, this.networkRequest)).proceed(this.networkRequest);
} else {
if(this.bufferedRequestBody != null && this.bufferedRequestBody.buffer().size() > 0L) {
this.bufferedRequestBody.emit();
}
if(this.sentRequestMillis == -1L) {
if(OkHeaders.contentLength(this.networkRequest) == -1L && this.requestBodyOut instanceof RetryableSink) {
long responseCache = ((RetryableSink)this.requestBodyOut).contentLength();
this.networkRequest = this.networkRequest.newBuilder().header("Content-Length", Long.toString(responseCache)).build();
}
this.httpStream.writeRequestHeaders(this.networkRequest);
}
if(this.requestBodyOut != null) {
if(this.bufferedRequestBody != null) {
this.bufferedRequestBody.close();
} else {
this.requestBodyOut.close();
}
if(this.requestBodyOut instanceof RetryableSink) {
this.httpStream.writeRequestBody((RetryableSink)this.requestBodyOut);
}
}
networkResponse = this.readNetworkResponse();
}
this.receiveHeaders(networkResponse.headers());
if(this.cacheResponse != null) {
if(validate(this.cacheResponse, networkResponse)) {
this.userResponse = this.cacheResponse.newBuilder().request(this.userRequest).priorResponse(stripBody(this.priorResponse)).headers(combine(this.cacheResponse.headers(), networkResponse.headers())).cacheResponse(stripBody(this.cacheResponse)).networkResponse(stripBody(networkResponse)).build();
networkResponse.body().close();
this.releaseStreamAllocation();
InternalCache responseCache1 = Internal.instance.internalCache(this.client);
responseCache1.trackConditionalCacheHit();
responseCache1.update(this.cacheResponse, stripBody(this.userResponse));
this.userResponse = this.unzip(this.userResponse);
return;
}
Util.closeQuietly(this.cacheResponse.body());
}
this.userResponse = networkResponse.newBuilder().request(this.userRequest).priorResponse(stripBody(this.priorResponse)).cacheResponse(stripBody(this.cacheResponse)).networkResponse(stripBody(networkResponse)).build();
if(hasBody(this.userResponse)) {
this.maybeCache();
this.userResponse = this.unzip(this.cacheWritingResponse(this.storeRequest, this.userResponse));
}
}
}
}
public Response proceed(Request request) throws IOException {
++this.calls;
if(this.index > 0) {
Interceptor response = (Interceptor)HttpEngine.this.client.networkInterceptors().get(this.index - 1);
Address code = this.connection().route().address();
if(!request.url().host().equals(code.url().host()) || request.url().port() != code.url().port()) {
throw new IllegalStateException("network interceptor " + response + " must retain the same host and port");
}
if(this.calls > 1) {
throw new IllegalStateException("network interceptor " + response + " must call proceed() exactly once");
}
}
if(this.index < HttpEngine.this.client.networkInterceptors().size()) {
HttpEngine.NetworkInterceptorChain var7 = HttpEngine.this.new NetworkInterceptorChain(this.index + 1, request);
Interceptor var10 = (Interceptor)HttpEngine.this.client.networkInterceptors().get(this.index);
Response interceptedResponse = var10.intercept(var7);
if(var7.calls != 1) {
throw new IllegalStateException("network interceptor " + var10 + " must call proceed() exactly once");
} else if(interceptedResponse == null) {
throw new NullPointerException("network interceptor " + var10 + " returned null");
} else {
return interceptedResponse;
}
} else {
HttpEngine.this.httpStream.writeRequestHeaders(request);
HttpEngine.this.networkRequest = request;
if(HttpEngine.this.permitsRequestBody(request) && request.body() != null) {
Sink var5 = HttpEngine.this.httpStream.createRequestBody(request, request.body().contentLength());
BufferedSink var8 = Okio.buffer(var5);
request.body().writeTo(var8);
var8.close();
}
Response var6 = HttpEngine.this.readNetworkResponse();
int var9 = var6.code();
if((var9 == 204 || var9 == 205) && var6.body().contentLength() > 0L) {
throw new ProtocolException("HTTP " + var9 + " had non-zero Content-Length: " + var6.body().contentLength());
} else {
return var6;
}
}
}
我们在上一步构造HttpEngine.NetworkInterceptorChain对象时,传入的第一个参数index为0,而在这个调用过程当中,client中的拦截器的数量也是0,所以就直接执行到最后的一个else分支当中了。在这里判断我们的request.body() != null,因为我们要上传文件,所以这个条件为true,那么就调用构造好一个BufferedSink对象,然后调用request.body().writeTo(var8)把我们的目标文件写入流中。request.body()获取到的是一个RequestBody对象,但是它的writeTo方法定义的是一个虚函数,而实现就是在我们一开始调用create(final MediaType contentType, final File file)构造Request请求当中,直接new了一个new RequestBody(),大家可以回头看看我们本篇的第一个代码片,这里的writeTo就是回调到那里的,而回调时的参数BufferedSink也是在回调前构造好的。writeTo方法中先调用Okio.source(file),将我们的目标文件转换成Source类型,然后再sink.writeAll(source)把数据写进去,这里的流传输底层实现应该都是一样的,在看蓝牙模块文件传输的代码时,也可以看到这样的逻辑,我们的文件如何传过去的呢?就是把文件通过Stream转换成流,然后将流数据通过Socket发送给目标。
好了,我们再回到HttpEngine.NetworkInterceptorChain类的proceed方法当中,文件数据写完后,就调用HttpEngine.this.readNetworkResponse()读取响应,最终返回给应用层的Response也就是在这里生成的。这里完成后,再往上退一步,就到了RealCall类的getResponse方法中,请求已经处理完了,响应数据也拿回来了,最后通过this.engine.getResponse()获取到响应,然后就返回给应用层了。
在这篇文章中,我们只是大概走了一下Okhttp网络框架中的一个最简单的流程,还有很多非常重要的细节没有深入研究,比如将文件读入到流之后,最后如何通过Socket发送过去的,中间过程还有涉及到很多非常重要的对象,如文件转换后得到的Source,写文件时使用的BufferedSink等等,还有很多重量级对象,大家如果有兴趣,可以自己研究一下。虽然我们没有进一步深入分析这些细节,但是从这个过程中,我们还是了解到了网络通信的一些实现,也希望能给大家带来一些帮助。
这节课就到这里了,谢谢大家,也希望大家关注我的博客!!