1、前言
这里的整体流程指的是从执行到响应的整体过程。在看过些许源码之后,我被这个框架迷住了。
2、从普通的get请求入手
一般来说,最简单的get请求是下面这种方式。
Request request = new Request.Builder()
.url("https://www.baidu.com/")
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
ok,我们知道了我们的请求是从okhttpclient的newcall方法入手。
@Override public Call newCall(Request request) {
return new RealCall(this, request);
}
在newcall方式中,只是返回了一个realcall对象,那么,我们就看下RealCall的enqueue方法,看看如何插入请求的。
3、插入请求队列
void enqueue(Callback responseCallback, boolean forWebSocket) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
}
通过调用链调用到了client.dispatcher(),这个对象是什么呢?这是个分发器,用来管理和执行我们的众多请求的,这里调用分发器插入一个AsyncCall对象。
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
这里通过判断,判断是加入执行队列执行还是加入准备队列(等待队列)。假设我们这里是执行。
4、执行请求
因为AsyncCall是实现了Runnable接口,并且是NamedRunnable的子类,因此,我们需要看下NamedRunnable内部的调用链才能知道这个时候要执行哪个方法。
@Override public final void run() {
String oldName = Thread.currentThread().getName();
Thread.currentThread().setName(name);
try {
execute();
} finally {
Thread.currentThread().setName(oldName);
}
}
从代码中,我们可以看到,执行了execute()方法。因此,我们看AsyncCall的execute方法。在这个方法里面,有这么一行代码。
Response response = getResponseWithInterceptorChain(forWebSocket);
所以,我们得继续看下去。
private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {
Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);
return chain.proceed(originalRequest);
}
调用ApplicationInterceptorChain#proceed方法获取Response返回结果。这里有一个很巧妙的设计。代码如下.
if (index < client.interceptors().size()) {
Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
Interceptor interceptor = client.interceptors().get(index);
Response interceptedResponse = interceptor.intercept(chain);
if (interceptedResponse == null) {
throw new NullPointerException("application interceptor " + interceptor
+ " returned null");
}
return interceptedResponse;
}
通过迭代的方式,将所有拦截器串联起来。反正最后会调用getResponse方法,这个方法很长,我们挑重点。
/** 省略一堆代码 **/
engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null);
/** 省略一堆代码 **/
engine.sendRequest();
engine.readResponse();
/** 省略一堆代码 **/
这里的HttpEngine对象很重要,我们看他的sentRequest方法。其中有这么2句代码。
httpStream = connect();
httpStream.setHttpEngine(this);
我们猜测connect方法是创建链接的。
5、建立链接的过程
connect方法代码如下。
private HttpStream connect() throws RouteException, RequestException, IOException {
boolean doExtensiveHealthChecks = !networkRequest.method().equals("GET");
return streamAllocation.newStream(client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis(),
client.retryOnConnectionFailure(), doExtensiveHealthChecks);
}
调用StreamAllocation的newStream方法,返回一个httpstream对象。
在这个方法当中,有这么一行代码,从名字上来看,这是真正的链接。
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
继续跟踪之后到了findConnect方法。在这个方法的最下面有这么一段代码。
newConnection.connect(connectTimeout, readTimeout, writeTimeout, address.connectionSpecs(),
connectionRetryEnabled);
在RealConnection的connect方法中,调用buildConnection方法。
private void buildConnection(int connectTimeout, int readTimeout, int writeTimeout,
ConnectionSpecSelector connectionSpecSelector) throws IOException {
connectSocket(connectTimeout, readTimeout, writeTimeout, connectionSpecSelector);
establishProtocol(readTimeout, writeTimeout, connectionSpecSelector);
}
在这个方法中,调用了connectSocket方法,到这里我们明白了,原来,okhttp是拿socket写的。在connecsocket方法中,
rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
? address.socketFactory().createSocket()
: new Socket(proxy);
这里的Address是什么呢?是什么时候初始化的?这个其实是在HttpEngine初始化的时候初始化的。我们看下HttpEngine的构造函数。
public HttpEngine(OkHttpClient client, Request request, boolean bufferRequestBody,
boolean callerWritesRequestBody, boolean forWebSocket, StreamAllocation streamAllocation,
RetryableSink requestBodyOut, Response priorResponse) {
this.client = client;
this.userRequest = request;
this.bufferRequestBody = bufferRequestBody;
this.callerWritesRequestBody = callerWritesRequestBody;
this.forWebSocket = forWebSocket;
this.streamAllocation = streamAllocation != null
? streamAllocation
: new StreamAllocation(client.connectionPool(), createAddress(client, request));
this.requestBodyOut = requestBodyOut;
this.priorResponse = priorResponse;
}
看到,有个createAddress方法。而这个方法就初始化了一个Address对象,并且他的socketFactory对象就是okhttpclient中的socketfactory。
socketFactory = SocketFactory.getDefault();
通过一些列的调用,到这里,创建了socket对象,并成功建立了链接。
6、请求头的写入过程
在HttpEngine的sendRequest方法中,有
httpStream.writeRequestHeaders(networkRequest);
这里就是写入请求头的入口。而我们的httpstream是什么呢,httpstream是在StreamAllocation的newStream方法中的到初始的。在这里有两种类型,Http1xStream和Http2xStream。分别对应http1.1和http2,spdy3.我们分开来说。
6.1、http1.1写入过程
public void writeRequest(Headers headers, String requestLine) throws IOException {
if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
sink.writeUtf8(requestLine).writeUtf8("\r\n");
for (int i = 0, size = headers.size(); i < size; i++) {
sink.writeUtf8(headers.name(i))
.writeUtf8(": ")
.writeUtf8(headers.value(i))
.writeUtf8("\r\n");
}
sink.writeUtf8("\r\n");
state = STATE_OPEN_REQUEST_BODY;
}
这里很简单,就是将我们的请求头一个一个以utf-8的格式写入sink中,这里的sink是我们在打开socket链接的时候,用okio包装后的。相应的代码在RealConnection的connectSocket方法中。
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
6.2、http2写入过程
Http2xStream#writeRequestHeaders中,有下面代码片段。
List<Header> requestHeaders = framedConnection.getProtocol() == Protocol.HTTP_2
? http2HeadersList(request)
: spdy3HeadersList(request);
boolean hasResponseBody = true;
stream = framedConnection.newStream(requestHeaders, permitsRequestBody, hasResponseBody);
先根据协议类型生成对应的请求头list,在调用FramedStream的newStream方法。在这个方法中,有这个一个代码片段。
frameWriter.synStream(outFinished, inFinished, streamId, associatedStreamId,
requestHeaders);
而这里的framwWriter是什么呢?他的初始化过程是这样的
frameWriter = variant.newWriter(builder.sink, client);
variant对应的是Http2或者是spdy3对象,我这里用http2对象来说明。所以上面产生的frameWriter队形是Http2的内部类Writer的实例,因此会调用他的synStream方法,而这个方法内部也很简单,直到调用下面的方法。
void headers(boolean outFinished, int streamId, List<Header> headerBlock) throws IOException {
if (closed) throw new IOException("closed");
hpackWriter.writeHeaders(headerBlock);
long byteCount = hpackBuffer.size();
int length = (int) Math.min(maxFrameSize, byteCount);
byte type = TYPE_HEADERS;
byte flags = byteCount == length ? FLAG_END_HEADERS : 0;
if (outFinished) flags |= FLAG_END_STREAM;
frameHeader(streamId, length, type, flags);
sink.write(hpackBuffer, length);
if (byteCount > length) writeContinuationFrames(streamId, byteCount - length);
}
好的,我们看到了sink.write,那么,hpackBuffer就是处理过后的请求头了。好,我们现在看下这里的Writer的初始化过程。
Writer(BufferedSink sink, boolean client) {
this.sink = sink;
this.client = client;
this.hpackBuffer = new Buffer();
this.hpackWriter = new Hpack.Writer(hpackBuffer);
this.maxFrameSize = INITIAL_MAX_FRAME_SIZE;
}
跟踪Hpack.Writew进去会发现,我们将buffer传给了out。那么我们现在看hpackWriter.writeHeaders(requestHeaders)也就是Hpack的内部类Writer的writeHeaders方法。在这里我们能看到将数据输入到out中,也就是我们的hpackBuffer。关于Hpack,这是一种请求头压缩算法,感兴趣的同学自己去学习吧。。。
7、下篇预告
下一篇将记录下整体流程的下一部分,请求体的写入以及响应过程。