看了两天OKhttp的请求流程及拦截器,觉得有必要写一下,巩固一下。
提问问题:
1、OKhttp如何发送请求?
2、如何去处理这些请求的?
首先我们来看一下OKhttp的简单使用?
异步请求:
private void studyOkHttp(){
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
Request request = new Request.Builder().url("https://www.baidu.com").build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()){
KLog.i(response.body().string());
}
}
});
}
代码我就不解释了,大家应该都能够看懂。
下面我们来看一下HttpUrlConnection如何使用,因为大家可能对这个会比较熟悉,结合着这个,想必更加的好理解。
private void studyHttpUrlConnection(){
try {
URL url = new URL("https://www.baidu.com");
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
//设置连接超时,2000ms
urlConnection.setConnectTimeout(2000);
//设置指定时间内服务器没有返回数据的超时,5000ms
urlConnection.setReadTimeout(5000);
//设置参数
urlConnection.setDoOutput(true); //需要输出
urlConnection.setDoInput(true); //需要输入
urlConnection.setUseCaches(false); //不允许缓存
urlConnection.setRequestMethod("POST");
//设置请求属性,给请求头添加东西
urlConnection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
urlConnection.setRequestProperty("accept", "application/json");
urlConnection.setRequestProperty("Connection", "Keep-Alive");// 维持长连接
urlConnection.setRequestProperty("Charset", "UTF-8");
int resultCode = urlConnection.getResponseCode();//获取响应码
if (HttpURLConnection.HTTP_OK == resultCode) {//表示请求成功
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
不太懂的同学,可以再看看HTTP方面的一些知识。下面我们就开始学习OKhttp的请求了。
从上面代码可以看出,请求无非就三步
-
创建一个Request
-
创建一个OKhttpClient
-
通过OKhttpClient把Request发送给服务端,然后拿到Response,Response中有我们需要的一切东西
那么这个过程就算完成了。整个过程很方便,你所需要的一般都会满足。所以要一探究竟。
当然,Request可以添加header ,添加body,请求方法等等,这些可以结合HttpUrlConnection中设置属性中对比着看,就很好理解。
我们进入OKhttpClient的newCall方法
看到如下方法
@Override public Call newCall(Request request) {
return RealCall.newRealCall(this, request, false /* for web socket */);
}
返回一个Call,RealCall应该就是Call的实现,接着进入newRealCall方法
static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
// Safely publish the Call instance to the EventListener.
RealCall call = new RealCall(client, originalRequest, forWebSocket);
call.eventListener = client.eventListenerFactory().create(call);
return call;
}
紧接着又调用了void enqueue(Callback responseCallback);方法,由于RealCall实现了Call,所以真正执行的就是RealCall中的enqueue方法
@Override public Response execute() throws IOException {
//。。。省略了部分代码
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
e = timeoutExit(e);
eventListener.callFailed(this, e);
throw e;
} finally {
client.dispatcher().finished(this);
}
}
Response result = getResponseWithInterceptorChain();
从这一行代码我们就可以拿到Response,感觉没有做什么东西,就拿到了结果,所以我们来看一下**getResponseWithInterceptorChain()**方法
从名字上可以看出 获取返回结果通过拦截器链,走了这么多,才到我们的重点,原来这就是我们需要研究的东西
进入getResponseWithInterceptorChain()方法
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
从这里我们可以看到,创建了一个拦截器数组,分别放入了
- client.interceptors() : 用户自定义的拦截器
- RetryAndFollowUpInterceptor:负责重定向。
- BridgeInterceptor:负责把用户构造的请求转换为发送给服务器的请求,把服务器返回的响应转换为对用户友好的响应。
- CacheInterceptor:负责读取缓存以及更新缓存。
- ConnectInterceptor:负责与服务器建立连接。
- CallServerInterceptor:负责从服务器读取响应的数据。
最后通过Interceptor.Chain继续执行process,把Request发送出去,然后获取到Response,我们首先要了解他们是怎么个调用过程,知道了调用过程我们再具体分析每个拦截器中都做了什么操作?(过程是重点)
我们来看
chain.proceed(originalRequest),也就是RealInterceptorChain中的proceed中的方法
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// If we already have a stream, confirm that the incoming request will use it.
if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must retain the same host and port");
}
// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
// Confirm that the next interceptor made its required call to chain.proceed().
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}
return response;
}
代码不多,我就都贴出来了,我们主要看这几行代码
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
这是一个很奇妙的设计
执行proceed时,index传进来的是0,所以interceptors获取到第一个拦截器,一般是我们自己定义的,如果没有定义,那就回获取到RetryAndFollowUpInterceptor拦截器,然后就会调用RetryAndFollowUpInterceptor的intercept方法,我们进去看看
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
Response priorResponse = null;
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response;
boolean releaseConnection = true;
try {
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
}
//省略了多行代码
}
主要看这行代码response = realChain.proceed(request, streamAllocation,null,null);
realChain目前是RealInterceptorChain,然后又重新调用了proceed方法,也就是说用重新调用了RealInterceptorChain中的proceed中的方法,所以又回来了,
由于在第一次调用的时候,index传进来是0,在这里,又重新创建了一个RealInterceptorChain ,所以在RetryAndFollowUpInterceptor获取的到RealInterceptorChain 已经变成了1,然后紧接着获取interceptors中第二个拦截器,也就是BridgeInterceptor,紧接着又执行了BridgeInterceptor中的intercept方法,我们去BridgeInterceptor中看这个方法
@Override public Response intercept(Chain chain) throws IOException {
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
Response networkResponse = chain.proceed(requestBuilder.build());
return responseBuilder.build();
}
很熟悉的影子chain.proceed(requestBuilder.build());又看到了这个方法,也就是说用重新调用了RealInterceptorChain中的proceed中的方法,所以又回到了RealInterceptorChain 中的proceed中,这时候index已经变成了2,就可以得到CacheInterceptor拦截器,以此类推,会分别轮询一遍拦截器
RetryAndFollowUpInterceptor -> BridgeInterceptor -> CacheInterceptor -> ConnectInterceptor -> CallServerInterceptor。
要想拿到RetryAndFollowUpInterceptor的Response,就需要拿到BridgeInterceptor 的Response,要想拿到BridgeInterceptor的Response,就需要拿到CacheInterceptor 的Response,所以最终先拿到CallServerInterceptor的Response返回给ConnectInterceptor,依次类推,就是这样的一个过程。
各个拦截器中都做了什么操作呢?可以参考如下两篇文章
所以我们来分析一下日志拦截器HttpLoggingInterceptor,我们看一下intercept方法
@Override public Response intercept(Chain chain) throws IOException {
Level level = this.level;
//获取一个Request
Request request = chain.request();
//如果设置了NONE,就不会打印,直接去调用下一个拦截器
if (level == Level.NONE) {
return chain.proceed(request);
}
boolean logBody = level == Level.BODY;
boolean logHeaders = logBody || level == Level.HEADERS;
//获取请求体
RequestBody requestBody = request.body();
boolean hasRequestBody = requestBody != null;
//获取Connection
Connection connection = chain.connection();
String requestStartMessage = "--> "
+ request.method()
+ ' ' + request.url()
+ (connection != null ? " " + connection.protocol() : "");
if (!logHeaders && hasRequestBody) {
requestStartMessage += " (" + requestBody.contentLength() + "-byte body)";
//打印请求方法 请求rul 请求端口 请求的字节长度
}
logger.log(requestStartMessage);
if (logHeaders) {
if (hasRequestBody) {
// Request body headers are only present when installed as a network interceptor. Force
// them to be included (when available) so there values are known.
if (requestBody.contentType() != null) {
logger.log("Content-Type: " + requestBody.contentType());
}
if (requestBody.contentLength() != -1) {
logger.log("Content-Length: " + requestBody.contentLength());
}
}
//获取Header
Headers headers = request.headers();
for (int i = 0, count = headers.size(); i < count; i++) {
String name = headers.name(i);
// Skip headers from the request body as they are explicitly logged above.
if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) {
logger.log(name + ": " + headers.value(i));
//获取header的名字和值
}
}
if (!logBody || !hasRequestBody) {
logger.log("--> END " + request.method());
} else if (bodyHasUnknownEncoding(request.headers())) {
logger.log("--> END " + request.method() + " (encoded body omitted)");
} else {
Buffer buffer = new Buffer();
requestBody.writeTo(buffer);
Charset charset = UTF8;
MediaType contentType = requestBody.contentType();
if (contentType != null) {
charset = contentType.charset(UTF8);
}
logger.log("");
if (isPlaintext(buffer)) {
logger.log(buffer.readString(charset));
logger.log("--> END " + request.method()
+ " (" + requestBody.contentLength() + "-byte body)");
} else {
logger.log("--> END " + request.method() + " (binary "
+ requestBody.contentLength() + "-byte body omitted)");
}
}
}
long startNs = System.nanoTime();
Response response;
try {
response = chain.proceed(request);
//这个地方要注意一下,每个拦截器中都会调用这个方法,如果没有这个方法,那就不会继续访问其他的拦截器了
} catch (Exception e) {
logger.log("<-- HTTP FAILED: " + e);
throw e;
}
long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs);
ResponseBody responseBody = response.body();
long contentLength = responseBody.contentLength();
String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length";
logger.log("<-- "
+ response.code()
+ (response.message().isEmpty() ? "" : ' ' + response.message())
+ ' ' + response.request().url()
+ " (" + tookMs + "ms" + (!logHeaders ? ", " + bodySize + " body" : "") + ')');
if (logHeaders) {
Headers headers = response.headers();
for (int i = 0, count = headers.size(); i < count; i++) {
logger.log(headers.name(i) + ": " + headers.value(i));
}
if (!logBody || !HttpHeaders.hasBody(response)) {
logger.log("<-- END HTTP");
} else if (bodyHasUnknownEncoding(response.headers())) {
logger.log("<-- END HTTP (encoded body omitted)");
} else {
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE); // Buffer the entire body.
Buffer buffer = source.buffer();
Long gzippedLength = null;
if ("gzip".equalsIgnoreCase(headers.get("Content-Encoding"))) {
gzippedLength = buffer.size();
GzipSource gzippedResponseBody = null;
try {
gzippedResponseBody = new GzipSource(buffer.clone());
buffer = new Buffer();
buffer.writeAll(gzippedResponseBody);
} finally {
if (gzippedResponseBody != null) {
gzippedResponseBody.close();
}
}
}
Charset charset = UTF8;
MediaType contentType = responseBody.contentType();
if (contentType != null) {
charset = contentType.charset(UTF8);
}
if (!isPlaintext(buffer)) {
logger.log("");
logger.log("<-- END HTTP (binary " + buffer.size() + "-byte body omitted)");
return response;
}
if (contentLength != 0) {
logger.log("");
logger.log(buffer.clone().readString(charset));
}
if (gzippedLength != null) {
logger.log("<-- END HTTP (" + buffer.size() + "-byte, "
+ gzippedLength + "-gzipped-byte body)");
} else {
logger.log("<-- END HTTP (" + buffer.size() + "-byte body)");
}
}
}
return response;
}
这样就会把请求的body以及返回的Response中的信息打印出来了,就是这样的一个流程。不知道大家明白没有
