Http請求過程
指標(biāo)數(shù)據(jù)
1.入隊(duì)到請求結(jié)束耗時
2.dns查詢耗時
3.socket connect耗時
4.tls連接的耗時
5.請求發(fā)送耗時
6.響應(yīng)傳輸耗時
7.首包耗時
8.響應(yīng)解析耗時
9.Http錯誤,區(qū)分業(yè)務(wù)錯誤和請求錯誤
采集到以上指標(biāo)抚岗,結(jié)合數(shù)據(jù)可視化的工具反砌,可以對Http個階段的耗時和錯誤分布有直觀的感受载荔,同時對優(yōu)化業(yè)務(wù)Http請求提供數(shù)據(jù)支持凹联。
如何獲取指標(biāo)
獲取指標(biāo)數(shù)據(jù)首先需要找到產(chǎn)生指標(biāo)數(shù)據(jù)的關(guān)鍵代碼,然后插入收集代碼即可悦冀。
如果業(yè)務(wù)中使用的框架沒有源碼或者不重新打包源碼的情況下贤徒,如何插入代碼?
這個就需要使用到能夠?qū)崿F(xiàn)AOP的工具窟绷,在前面分享的Monitor中的提供注解和配置文件的方式锯玛,在指定函數(shù)中插入相關(guān)代碼的功能。這樣的實(shí)現(xiàn)方式也可以使監(jiān)控代碼和業(yè)務(wù)代碼分離兼蜈。
OkHttp框架
OkHttp是Android上最常用的Http請求框架攘残,OkHttp的最新版本已經(jīng)升級到4.0.x,實(shí)現(xiàn)也全部由java替換到了Kotlin为狸,API的一些使用也會有些不同歼郭。由于4.x的設(shè)備不是默認(rèn)支持TLSV1.2版本,OkHttp 3.13.x以上的版本需要在Android 5.0+(API level 21+)和Java 1.8的環(huán)境開發(fā)辐棒,不過OkHttp也為了支持4.x設(shè)備單獨(dú)創(chuàng)立了3.12.x分支病曾,本文中使用的OkHttp版本為3.12.3版本。
OkHttp整體流程
先引用個別人畫流程圖(原圖來自)
請求過程分析
1.創(chuàng)建 OkHttpClient
new OkHttpClient()中會調(diào)用new OkHttpClient.Builder()方法,Builder()會設(shè)置一些的默認(rèn)值漾根。OkHttpClient()會把OkHttpClient.Builder()產(chǎn)生的對象中字段復(fù)制到對應(yīng)OkHttpClient對象的字段上泰涂,其中sslSocketFactory如果沒有在Builder中設(shè)置,OkHttp會獲取系統(tǒng)默認(rèn)的sslSocketFactory辐怕。
public OkHttpClient.Builder(){
/**
*異步請求的分發(fā)器逼蒙,其中使用 不限制線程數(shù),存活時間為60s的線程池來執(zhí)行異步請求
* 默認(rèn)限制同時執(zhí)行的異步請求不操過64個寄疏,每個host的同時執(zhí)行的異步請求不操過5個
*超過限制新的請求需要等待是牢。
* */
dispatcher = new Dispatcher();
//支持的協(xié)議類型 Http1.0/1.1/2,QUIC
protocols = DEFAULT_PROTOCOLS;
//支持TLS和ClearText
connectionSpecs = DEFAULT_CONNECTION_SPECS;
//請求事件通知器,大部分指標(biāo)數(shù)據(jù)都可以度過EventListener來獲取
eventListenerFactory = EventListener.factory(EventListener.NONE);
//系統(tǒng)級代理服務(wù)器
proxySelector = ProxySelector.getDefault();
if(proxySelector == null){
proxySelector = new NullProxySelector();
}
//默認(rèn)不使用Cookie
cookieJar = CookieJar.NO_COOKIES;
//socket工廠
socketFactory = SocketFactory.getDefault();
//用于https主機(jī)名驗(yàn)證
hostnameVerifier = OkHostnameVerifier.INSTANCE;
//用于約束哪些證書是可信的陕截,可以用來證書固定
certificatePinner = CertificatePinner.DEFAULT;
//實(shí)現(xiàn)HTTP協(xié)議的信息認(rèn)證
proxyAuthenticator = Authenticator.NONE;
//實(shí)現(xiàn)HTTP協(xié)議的信息認(rèn)證
authenticator = Authenticator.NONE;
/**
*實(shí)現(xiàn)多路復(fù)用機(jī)制連接池妖泄,最多保持5個空閑連接
*每個空閑連接最多保持五分鐘
* */
connectionPool = new ConnectionPool();
//dns解析,默認(rèn)使用系統(tǒng)InetAddress.getAllByName(hostname)
dns = Dns.SYSTEM;
//支持ssl重定向
followSslRedirects = true;
//支持重定向
followRedirects = true;
//連接失敗是否重試
retryOnConnectionFailure = true;
//請求超時時間艘策,0為不超時
callTimeout = 0;
//連接超時時間
connectTimeout = 10_000;
//socket讀超時時間
readTimeout = 10_000;
//socket寫超時時間
writeTimeout = 10_000;
//websocket 心跳間隔
pingInterval = 0;
}
//獲取默認(rèn)的sslSocketFactory
X509TrustManager trustManager = Util.platformTrustManager();
this.sslSocketFactory = newSslSocketFactory(trustManager);
this.certificateChainCleaner = CertificateChainCleaner.get(trustManager);
2.Request執(zhí)行過程
從上面的流程圖可以看出,不管同步請求還是異步請求渊季,最終都會調(diào)用到 RealCall.getResponseWithInterceptorChain(),getResponseWithInterceptorChain() 再調(diào)用RealInterceptorChain.proceed(Request request)方法發(fā)起最終請求朋蔫,下面我們來分析一下這兩個方法的具體代碼。
Response RealCall.getResponseWithInterceptorChain() throws IOException {
//組裝所有的Interceptors
List<Interceptor> interceptors = new ArrayList<>();
//業(yè)務(wù)自定的Interceptors,通過OkHttpClient.Builid.addInterceptor添加
interceptors.addAll(client.interceptors());
//其他功能interceptors
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
//如果不是forWebSocket請求却汉,添加通過OkHttpClient.Builid.addNetworkInterceptor添加的Interceptor
if (!forWebSocket) {
interceptors.addAll([client.networkInterceptors](http://client.networkinterceptors/)());
}
//真正發(fā)起網(wǎng)絡(luò)請求的Interceptor
interceptors.add(new CallServerInterceptor(forWebSocket));
//創(chuàng)建RealInterceptorChain
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
//開始執(zhí)行請求
Response response = chain.proceed(originalRequest);
//如果請求被取消
if (retryAndFollowUpInterceptor.isCanceled()) {
closeQuietly(response);
throw new IOException("Canceled");
}
return response;
}
接下來再看RealInterceptorChain.proceed(Request request)代碼
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
/*
*index代表當(dāng)前應(yīng)該執(zhí)行的Interceptor在Interceptor列表中的位置驯妄,如果超過
*Interceptor列表size,報錯
*在RealCall.getResponseWithInterceptorChain()第一次調(diào)用proceed方法時傳遞的index值為0
*/
if (index >= interceptors.size()) throw new AssertionError();
//執(zhí)行次數(shù)+1
calls++;
//httpCodec時在ConnectInterceptor創(chuàng)建的合砂,會對應(yīng)一個socket連接
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");
}
//創(chuàng)建新的RealInterceptorChain青扔,通過改變index的值來實(shí)現(xiàn)調(diào)用Interceptor列表下一個位置的Interceptor
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
//獲取當(dāng)前index位置的Interceptor
Interceptor interceptor = interceptors.get(index);
//執(zhí)行當(dāng)前位置的interceptor,同時傳遞新創(chuàng)建的RealInterceptorChain,新的RealInterceptorChain中的index值是當(dāng)前Interceptor列表中下一個位置
//interceptor.intercept中會調(diào)用新的RealInterceptorChain的proceed方法微猖,實(shí)現(xiàn)向后調(diào)用
Response response = interceptor.intercept(next);
// 如果當(dāng)前RealInterceptorChain的httpCodec不為空谈息,確保下一個位置的Interceptor只被調(diào)用一次,httpCodec是在ConnectInterceptor中被賦值
if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// response為空報錯
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
// response.body為空報錯
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}
//返回response
return response;
}
從代碼可以看出 Interceptor 是 OkHttp 最核心的功能類凛剥,Interceptor 把實(shí)際的網(wǎng)絡(luò)請求侠仇、緩存、透明壓縮等功能都統(tǒng)一了起來犁珠,每一個功能都實(shí)現(xiàn)為一個Interceptor逻炊,它們最終組成一個了Interceptor.Chain的責(zé)任鏈,其中每個 Interceptor 都可能完成 Request到 Response 轉(zhuǎn)變的任務(wù)犁享,循著責(zé)任鏈讓每個 Interceptor 自行決定能否完成任務(wù)以及怎么完成任務(wù)余素,完成網(wǎng)絡(luò)請求這件事也從 RealCall 類中剝離了出來,簡化了各自的責(zé)任和邏輯炊昆,代碼變得優(yōu)雅桨吊。
這些Interceptor最為關(guān)鍵的兩個Interceptor是ConnectInterceptor和CallServerInterceptor,ConnectInterceptor的主要功能是在連接池里找到可復(fù)用連接窑眯,如果沒有屏积,就創(chuàng)建新的socket,進(jìn)行tls握手磅甩,將socket用Okio進(jìn)行包裹炊林,創(chuàng)建HttpCodec。CallServerInterceptor使用HttpCodec進(jìn)行相關(guān)協(xié)議的傳輸和解析卷要。下面對ConnectInterceptor中findConnect過程和CallServerInterceptor請求過程做一個分析渣聚。
3.ConnectInterceptor findConnection過程分析
在ConnectInterceptor中,會為本次請求創(chuàng)建可用的RealConnection,首先會從連接池中找到能夠復(fù)用的連接僧叉,如果沒有就創(chuàng)建新的socket奕枝,然后使用RealConnection創(chuàng)建HttpCodec。創(chuàng)建RealConnection的方法調(diào)用鏈路為StreamAllocation.newStream()-> StreamAllocation.findHealthyConnection()->StreamAllocation.findConnection()瓶堕,findConnection()是創(chuàng)建連接的主要代碼隘道。
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
boolean foundPooledConnection = false;
//最終找到的connection
RealConnection result = null;
Route selectedRoute = null;
//需要釋放的connection
Connection releasedConnection;
//需要關(guān)閉的socket
Socket toClose;
synchronized (connectionPool) {
if (released) throw new IllegalStateException("released");
if (codec != null) throw new IllegalStateException("codec != null");
if (canceled) throw new IOException("Canceled");
/**
*如果遇到重定向到同一個地址的情況下,在RetryAndFollowUpInterceptor中會使用已經(jīng)分配的StreamAllocation
*進(jìn)行重定向請求郎笆,這個時候的connection不為空,但是這個connection不一定時有效的連接谭梗。
* **/
releasedConnection = this.connection;
/**
*如果已經(jīng)存在RealConnection,但是不能用來創(chuàng)建新的Stream
*就設(shè)置this.connection=null,同時返回要關(guān)閉的socket
*當(dāng)前請求第一次執(zhí)行時releaseIfNoNewStreams()不進(jìn)行任何操作
* */
toClose = releaseIfNoNewStreams();
/**
*
* 這時候this.connection不為空
* 表示原來的connection可以用來創(chuàng)建新的Stream
* 當(dāng)前請求第一次執(zhí)行時this.connection=null
*
* */
if (this.connection != null) {
// We had an already-allocated connection and it's good.
result = this.connection;
releasedConnection = null;
}
/**
* reportedAcquired會在新建socket或者從連接池
* 獲取到有效RealConnection時賦值為true
* 當(dāng)前請求第一次執(zhí)行時reportedAcquired=fasle
* */
if (!reportedAcquired) {
// If the connection was never reported acquired, don't report it as released!
releasedConnection = null;
}
/**
* 如果還沒找到目標(biāo)RealConnection
* 嘗試從連接池中獲取
* */
if (result == null) {
/**
* 對于非http/2協(xié)議宛蚓,如果已經(jīng)存在不超過過RealConnection復(fù)用的最大值且協(xié)議激捏,證書都一致
* 這個RealConnection可以用來復(fù)用
* 如果從連接池中獲取RealConnection,會調(diào)用
* streamAllocation.acquire()設(shè)置connection為新值
* */
Internal.instance.get(connectionPool, address, this, null);
/**
* connection != null表示從連接池獲取到合適的
* RealConnection凄吏,設(shè)置foundPooledConnection = true;
* */
if (connection != null) {
foundPooledConnection = true;
result = connection;
} else {
selectedRoute = route;
}
}
}
/**
* close當(dāng)前的需要關(guān)閉的socket
* */
closeQuietly(toClose);
/**
* 如果當(dāng)前的RealConnection需要釋放远舅,調(diào)用eventListener
* */
if (releasedConnection != null) {
eventListener.connectionReleased(call, releasedConnection);
}
/**
* 如果從連接池獲取到RealConnection闰蛔,調(diào)用eventListener
* */
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
}
/**
* 當(dāng)前RealConnection可以繼續(xù)使用或者從連接池中找到合適的RealConnection
* 返回這個RealConnection
* */
if (result != null) {
// If we found an already-allocated or pooled connection, we're done.
return result;
}
// If we need a route selection, make one. This is a blocking operation.
/**
* routeSelector在StreamAllocation構(gòu)造方法中被創(chuàng)建
* 連接池重新尋找
* 請求第一次執(zhí)行時routeSelector.next()會進(jìn)行域名解析工作
* */
boolean newRouteSelection = false;
if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
newRouteSelection = true;
routeSelection = routeSelector.next();
}
synchronized (connectionPool) {
if (canceled) throw new IOException("Canceled");
if (newRouteSelection) {
// Now that we have a set of IP addresses, make another attempt at getting a connection from
// the pool. This could match due to connection coalescing.
List<Route> routes = routeSelection.getAll();
for (int i = 0, size = routes.size(); i < size; i++) {
Route route = routes.get(i);
Internal.instance.get(connectionPool, address, this, route);
if (connection != null) {
foundPooledConnection = true;
result = connection;
this.route = route;
break;
}
}
}
if (!foundPooledConnection) {
if (selectedRoute == null) {
selectedRoute = routeSelection.next();
}
// Create a connection and assign it to this allocation immediately. This makes it possible
// for an asynchronous cancel() to interrupt the handshake we're about to do.
route = selectedRoute;
refusedStreamCount = 0;
result = new RealConnection(connectionPool, selectedRoute);
acquire(result, false);
}
}
// If we found a pooled connection on the 2nd time around, we're done.
/**
* 如果在連接池重新找到合適的RealConnection,返回
* */
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
return result;
}
/**
* 如果還沒有找到,就需要創(chuàng)建新的RealConnect
* 生成新的socket,建立Tls,并加入ConnectPool
* */
// Do TCP + TLS handshakes. This is a blocking operation.
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
routeDatabase().connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
reportedAcquired = true;
// Pool the connection.
Internal.instance.put(connectionPool, result);
// If another multiplexed connection to the same address was created concurrently, then
// release this connection and acquire that one.
if (result.isMultiplexed()) {
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
}
}
closeQuietly(socket);
eventListener.connectionAcquired(call, result);
return result;
}
4.CallServerInterceptor程分析
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
//Http解析器图柏,在ConncetInterceptor中創(chuàng)建
HttpCodec httpCodec = realChain.httpStream();
StreamAllocation streamAllocation = realChain.streamAllocation();
/**
*請求的使用的連接序六,在ConncetInterceptor中產(chǎn)生
*連接可能是從ConnectPool中選擇或者重新創(chuàng)建出來
**/
RealConnection connection = (RealConnection) realChain.connection();
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();
realChain.eventListener().requestHeadersStart(realChain.call());
/**
* 通過httpCodec中用Okio包裹的socket寫請求頭
**/
httpCodec.writeRequestHeaders(request);
realChain.eventListener().requestHeadersEnd(realChain.call(), request);
Response.Builder responseBuilder = null;
/**
* 如果請求有請求body,發(fā)送body
**/
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
request body.
/**
* 請求頭是Expect: 100-continue爆办,先讀響應(yīng)頭
* httpCodec.readResponseHeaders方法讀取到狀態(tài)碼100時难咕, 會返回null
**/
if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
httpCodec.flushRequest();
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(true);
}
/**
* 如果正常發(fā)送請求body部分
* 請求頭有Expect: 100-continue,但是服務(wù)器沒有返回狀態(tài)碼100距辆,且不是Http/2協(xié)議余佃,關(guān)閉當(dāng)前連接
**/
if (responseBuilder == null) {
realChain.eventListener().requestBodyStart(realChain.call());
long contentLength = request.body().contentLength();
CountingSink requestBodyOut =
new CountingSink(httpCodec.createRequestBody(request, contentLength));
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
realChain.eventListener()
.requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
} else if (!connection.isMultiplexed()) {
streamAllocation.noNewStreams();
}
}
httpCodec.finishRequest();
/**
* 正常情況下,讀響應(yīng)headers
**/
if (responseBuilder == null) {
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(false);
}
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
if (code == 100) {
responseBuilder = httpCodec.readResponseHeaders(false);
response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
code = response.code();
}
/**
* 讀響應(yīng)headers結(jié)束
**/
realChain.eventListener()
.responseHeadersEnd(realChain.call(), response);
/**
* 如果是forWebSocket跨算,且 code == 101返回空響應(yīng)
* 其他返回 RealResponseBody對象
**/
if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
/**
*將
*無響應(yīng)內(nèi)容
* chunk內(nèi)容,
* 存在contenlength內(nèi)容
* 不存在contenlength內(nèi)容
* 的響應(yīng)body包裹成 RealResponseBody對象
*
**/
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
/**
* 服務(wù)端關(guān)閉要求連接
**/
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
/**
* code是204/205爆土,contentLength()還大于0,拋出協(xié)議錯誤
**/
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
CallServerInterceptor執(zhí)行完成后返回的是一個只讀取了響應(yīng)headers诸蚕,但是還沒有讀取body的Response步势,OkHttp網(wǎng)絡(luò)請求部分的代碼到此就結(jié)束了,后續(xù)的parseReponse都在更上層的框架中背犯,比如Retrofit就是在OkHttpCall.parseReponse()方法中調(diào)用serviceMethod.toResponse(catchingBody)中調(diào)用GsonConvter或者其他Convertor來進(jìn)行處理坏瘩。
獲取指標(biāo)具體實(shí)現(xiàn)
對于Http 請求耗時,異常漠魏,數(shù)據(jù)大小,狀態(tài)碼 的獲取倔矾,直接使用前面實(shí)現(xiàn)的MAOP,攔截OkHttpClient.Builder的build方法加入統(tǒng)計(jì)Interceptor ,DNSLookUp 耗時柱锹,連接耗時哪自,ssl耗時,通過設(shè)置EventListener.Factory禁熏,可以直接收集壤巷。解析耗時需要攔截上層框架的parseReponse方法進(jìn)行收集。
首包時間需要攔截OkHttp讀請求數(shù)據(jù)的方法來實(shí)現(xiàn)瞧毙,OKHttpClient 最終調(diào)用CallServerInterceptor,關(guān)鍵代碼就是讀取readResponseHeaders的時機(jī)胧华。
MAOP 實(shí)現(xiàn)
使用前面提供的MAOP功能,在AOP配置文件中加入宙彪,攔截OkHttpClient的builder方法和Http1Codec的readHeaderLine方法和okhttp3.internal.http2.Http2Stream的takeResponseHeaders方法的配置
在攔截OkHttpClient的Builder的build()方法中加入統(tǒng)計(jì)Interceptor和EventListenerFactory
首包的時間通過:認(rèn)為第一次讀響應(yīng)頭返回時為首包時間撑柔,攔截okhttp3.internal.http1.Http1Code.readHeaderLine的方法和okhttp3.internal.http2.Http2Stream.takeResponseHeaders計(jì)算首包時間
Retrofit parse耗時收集
AOP配置文件中加入對retrofit2.OKHttp.parseResponse攔截的配置
Method回掉中處理相關(guān)的數(shù)據(jù)
綜上,這個方案基本能實(shí)現(xiàn)Http基本指標(biāo)的獲取您访,但是有些細(xì)節(jié)還需完善,可以微信關(guān)注知識星球共同交流