概要
我們平時使用最多的請求方式莫過于GET和POST請求,它們幾乎完成我們平時所需的基本任務(wù),下面我們分析一下它們的區(qū)別以及在
OkHttp
中的調(diào)用流程
GET和POST區(qū)別
GET
和POST
請求都是的HTTP協(xié)議都是基于TCP/IP的應(yīng)用層協(xié)議尸诽,它們使用的是同一個傳輸層協(xié)議,所以在傳輸上相同
GET
只接受ASCII字符究恤,而POST
沒有限制
GET
請求只能進行URL
編碼,而POST
支持多種編碼方式
GET
比POST
更不安全后德,因為參數(shù)直接暴露在URL上部宿,所以不能用來傳遞敏感信息,若想保證傳輸安全,可通過HTTPS
進行加密
GET
請求產(chǎn)生一個TCP
數(shù)據(jù)包瓢湃;POST
產(chǎn)生兩個TCP
數(shù)據(jù)包理张;對于GET
方式的請求,瀏覽器會把Header
和Data
一并發(fā)送出去绵患,服務(wù)器響應(yīng)200(返回數(shù)據(jù))雾叭;而對于POST,瀏覽器先發(fā)送Header落蝙,服務(wù)器響應(yīng)100 continue织狐,瀏覽器再發(fā)送Data
,服務(wù)器響應(yīng)200(返回數(shù)據(jù))
GET源碼解析
GET同步流程解析
[ 1 ]外部操作
public class GetExample {
//聲明OkHttpClick實例?
OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
//聲明Request實例筏勒,組織所需元素數(shù)據(jù)?
Request request = new Request.Builder()
.url(url)
.build();
//通過?newCall得到一個RealCall實例移迫,同時也聲明了一個 Transmitter 發(fā)射器的實例
//?調(diào)取execute()進行執(zhí)行
try (Response response = client.newCall(request).execute()) {
return response.body().string();
}
}
[ 2 ] 進入RelCall.kt ,執(zhí)行 @方法
execute()
override fun execute(): Response {
synchronized(this) {
check(!executed) { "Already Executed" }
executed = true
}
//設(shè)置超時?
transmitter.timeoutEnter()
//此方法是個‘鉤子’管行,若在外部沒有具體實現(xiàn)厨埋,則不執(zhí)行任何操作?
transmitter.callStart()
try {
//將當(dāng)前的realCall存入runningSyncCalls鏈表?
client.dispatcher().executed(this)
//調(diào)取攔截鏈,實現(xiàn)數(shù)據(jù)訪問捐顷,返回一個Response對象?
return getResponseWithInterceptorChain()
} finally {
client.dispatcher().finished(this)
}
}
[3] 調(diào)取攔截鏈的實現(xiàn)我們不在此處過多贅述荡陷,詳情請見我的另一篇文章okHttp框架分析--攔截鏈
GET異步請求
[1]外部操作
public final class AsynchronousGet {
//OkHttp客戶端實例?
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
//請求實例?
Request request = new Request.Builder()
.url("https://raw.github.com/square/okhttp/master/README.md") //url網(wǎng)址配置
.build();
//[ 2 ] client new一個RealCall雨效,調(diào)用enqueue?函數(shù)進入隊列,并實現(xiàn)回調(diào)操作
client.newCall(request).enqueue(new Callback() {
//失敗回調(diào)實現(xiàn)?
@Override public void onFailure(Call call, IOException e) {
e.printStackTrace(); //打印訪問服務(wù)失敗的異常信息
}
//成功回調(diào)實現(xiàn)?
@Override public void onResponse(Call call, Response response) throws IOException {
...
//todo 訪問服務(wù)器成果后的具體操作?
});
}
[ 2 ]
client
聲明一個RealCall
類型的對象废赞,調(diào)用enqueue(...)
?函數(shù)進入隊列设易,并實現(xiàn)回調(diào)操作
override fun enqueue(responseCallback: Callback) {
synchronized(this) {
check(!executed) { "Already Executed" }
executed = true
}
//發(fā)射器開始調(diào)用,此處為一個空操作,可在外部進行操作重寫?
transmitter.callStart()
//[ 3 ]client事件分發(fā)處理,獲取一個Dispatcher對象蛹头,
//繼續(xù)調(diào)用enqueue函數(shù),傳入回調(diào)Callback包裝成AsyncCall類型
client.dispatcher().enqueue(AsyncCall(responseCallback))
}
[ 3 ]
client
事件分發(fā)處理,獲取一個Dispatcher
類型對象戏溺,繼續(xù)調(diào)用enqueue(...)
函數(shù)渣蜗,傳入回調(diào)Callback
包裝成AsyncCall
類型
internal fun enqueue(call: AsyncCall) {
synchronized(this) {
//將我們的callback回調(diào)添加進準(zhǔn)備異步調(diào)用的隊列?
readyAsyncCalls.add(call)
//修改AsyncCall,使其共享現(xiàn)有運行調(diào)用的AtomicInteger 相同的主機
if (!call.get().forWebSocket) {
val existingCall = findExistingCallWithHost(call.host())
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
//[ 4 ] 將符合條件的調(diào)用從[readyAsyncCalls]提升到[runningAsyncCalls]旷祸,并在線程池執(zhí)行耕拷。
//?必須不同步調(diào)用,因為執(zhí)行調(diào)用可以調(diào)用在用戶代碼?
promoteAndExecute()
}
[ 4 ] 將符合條件的調(diào)用從
readyAsyncCalls
鏈表提升到runningAsyncCalls
鏈表托享,并在線程池執(zhí)行骚烧。執(zhí)行方法promoteAndExecute()
private fun promoteAndExecute(): Boolean {
//斷言 當(dāng)先線程是否持有Lock鎖
? assert(!Thread.holdsLock(this))
//聲明私有的一個執(zhí)行回調(diào)隊列?
val executableCalls = ArrayList<AsyncCall>()
val isRunning: Boolean
synchronized(this) {
val i = readyAsyncCalls.iterator()
while (i.hasNext()) {
val asyncCall = i.next()
if (runningAsyncCalls.size >= this.maxRequests) break // 運行異步調(diào)用最大容量
if (asyncCall.callsPerHost().get() >= this.maxRequestsPerHost) continue // 主機調(diào)用最大容量
i.remove()
//此處為原子操作,將主機的調(diào)用個數(shù)增加 1 ?
asyncCall.callsPerHost().incrementAndGet()
//將我們的Callback添加到執(zhí)行?回調(diào)隊列
executableCalls.add(asyncCall)
//將我們的Callback添加到異步運行回調(diào)隊列?
runningAsyncCalls.add(asyncCall)
}
//正在運行的異步回調(diào)和同步回調(diào)個數(shù)相加?是否大于 0
isRunning = runningCallsCount() > 0
}
//循環(huán)?私有執(zhí)行回調(diào)隊列
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
//[ 5 ]? 嘗試在“executorService”上注冊此異步調(diào)用,獲取一個線程池作為參數(shù)
asyncCall.executeOn(executorService())
}
return isRunning
}
[ 5 ]? 嘗試在
executorService
上注冊此異步調(diào)用闰围,獲取一個線程池作為參數(shù)赃绊,執(zhí)行方法asyncCall.executeOn(executorService())
fun executeOn(executorService: ExecutorService) {
//判定當(dāng)前線程是否持有Lock鎖?
assert(!Thread.holdsLock(client.dispatcher()))
var success = false
try {
//[ 6 ] 執(zhí)行線程?
executorService.execute(this)
success = true
} catch (e: RejectedExecutionException) {
val ioException = InterruptedIOException("executor rejected")
ioException.initCause(e)
transmitter.noMoreExchanges(ioException)
//執(zhí)行發(fā)生異常則回調(diào) onFailure函數(shù)?
responseCallback.onFailure(this@RealCall, ioException)
} finally {
if (!success) {
client.dispatcher().finished(this) // This call is no longer running!
}
}
}
[ 6 ]最終還是進入方法
execute()
, 執(zhí)行線程操作發(fā)起對服務(wù)器的請求操作
override fun execute() {
var signalledCallback = false
?//發(fā)射器判斷超時設(shè)置
transmitter.timeoutEnter()
try {
//?/調(diào)取攔截鏈,實現(xiàn)數(shù)據(jù)訪問羡榴,返回一個Response對象?
val response = getResponseWithInterceptorChain()?
signalledCallback = true
//訪問成功則調(diào)取回調(diào)接口 onResponse?函數(shù)
responseCallback.onResponse(this@RealCall, response)
} catch (e: IOException) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log(INFO, "Callback failure for ${toLoggableString()}", e)
} else {
//?訪問發(fā)生異常則 回調(diào)接口 onFailure函數(shù)
responseCallback.onFailure(this@RealCall, e)
}
} finally {
//最終結(jié)束本次調(diào)用?
client.dispatcher().finished(this)
}
}
}
POST源碼解析
POST同步流程解析
[ 1 ]外部操作
//請求體類型
?public static final MediaType MEDIA_TYPE_MARKDOWN
= MediaType.get("text/x-markdown; charset=utf-8");
//聲明OkHttpClient實例?
private final OkHttpClient client = new OkHttpClient();
public void run() throws Exception {
//請求體文件?
File file = new File("README.md");
//聲明一個 請求實例?
Request request = new Request.Builder()
.url("https://api.github.com/markdown/raw") //url連接
.post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file)) //post請求方法,傳入一個請求體參數(shù)
.build();
//new 一個 RealCall實例,調(diào)用 @方法?[ execute() ]函數(shù)碧查,執(zhí)行數(shù)據(jù)訪問
try (Response response = client.newCall(request).execute()) {?
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
System.out.println(response.body().string());
}
}
[ 2 ] 此處說明一下,
POST
同步請求流程與GET
同步請求流程一般無二,最終會在真正發(fā)起服務(wù)的攔截器CallServerInterceptor.kt做具體的操作或者在自定攔截器中對不同的請求方法做特殊處理;進入 CallServerInterceptor.kt 類校仑,執(zhí)行方法intercept(chain: Interceptor.Chain)
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
val exchange = realChain.exchange()
//獲取請求實例?
val request = realChain.request()
//獲取請求體?
val requestBody = request.body()
...
? //@重點 做判定忠售,若當(dāng)前請求方法不是 GET或 HEAD 并且 請求體不為 Null 則判定成立
if (HttpMethod.permitsRequestBody(request.method()) && requestBody != null) {
//如果請求上有一個“Expect: 100-continue”報頭,則等待一個“HTTP/1.1 100”報頭
//在發(fā)送請求體之前繼續(xù)“響應(yīng)”迄沫。如果我們沒有收到稻扬,返回
? //在沒有傳輸請求體的情況下,我們確實得到了(例如4xx響應(yīng))
?
//在[預(yù)用知識說明]標(biāo)簽 ==> { “Expect: 100-continue”的來龍去脈 } 中做詳細(xì)說明?
if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) {
exchange.flushRequest()
responseHeadersStarted = true
//開始調(diào)用響應(yīng)頭?
exchange.responseHeadersStart()
// 讀取響應(yīng)頭?
responseBuilder = exchange.readResponseHeaders(true)
}
?
if (responseBuilder == null) {
if (requestBody.isDuplex()) {
// 準(zhǔn)備一個雙重主體羊瘩,以便應(yīng)用程序稍后可以發(fā)送請求主體泰佳。
exchange.flushRequest()
//創(chuàng)建一個buffer?
val bufferedRequestBody = exchange.createRequestBody(request, true).buffer()
//請求體寫入buff中?
requestBody.writeTo(bufferedRequestBody)
} else {
// 如果滿足“Expect: 100-continue”期望,則編寫請求主體
val bufferedRequestBody = exchange.createRequestBody(request, false).buffer()
requestBody.writeTo(bufferedRequestBody)
bufferedRequestBody.close()
}
} else {
exchange.noRequestBody()
if (!exchange.connection().isMultiplexed) {
//如果沒有滿足“Expect: 100-continue”期望尘吗,請阻止HTTP/1連接
? //避免重復(fù)使用乐纸。否則,我們?nèi)匀挥辛x務(wù)將請求體傳輸?shù)?
//讓連接保持一致的狀態(tài)摇予。
exchange.noNewExchangesOnConnection()
}
}
} else {
//沒有請求體?
exchange.noRequestBody()
}
??
?
if (requestBody == null || !requestBody.isDuplex()) {
//將所有緩沖數(shù)據(jù)寫入底層接收器(如果存在的話)汽绢。然后這個sink是遞歸不斷刷新
//?將數(shù)據(jù)盡可能地推送到最終目的地。通常, 目標(biāo)是一個網(wǎng)絡(luò)套接字或文件侧戴。?
exchange.finishRequest()
}
if (!responseHeadersStarted) {
//僅在接收響應(yīng)頭之前調(diào)用宁昭。
//? 連接是隱式的跌宛,通常與最后一個[connectionAcquired]事件相關(guān)。
//?這可以被調(diào)用超過1次為一個[Call]积仗。?
exchange.responseHeadersStart()
}
if (responseBuilder == null) {
//從HTTP傳輸解析響應(yīng)頭的字節(jié)疆拘。?
responseBuilder = exchange.readResponseHeaders(false)!!
}
//一個HTTP響應(yīng)。
//?該類的實例不是不可變的:響應(yīng)體是一次性的
//?只使用一次寂曹,然后關(guān)閉的值哎迄。所有其他屬性都是不可變的。?
var response = responseBuilder
.request(request)
.handshake(exchange.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build()
var code = response.code() //獲取響應(yīng) Code
if (code == 100) {
// 服務(wù)器發(fā)送了100-continue隆圆,即使我們沒有請求漱挚。
? //再讀一遍實際的響應(yīng)
response = exchange.readResponseHeaders(false)!!
.request(request)
.handshake(exchange.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build()
code = response.code()
}
//在接收響應(yīng)標(biāo)頭后立即調(diào)用?
exchange.responseHeadersEnd(response)
response = if (forWebSocket && code == 101) {
//連接疊積,但我們需要確保攔截器看到非空響應(yīng)體渺氧。
response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build()
} else {
response.newBuilder()
.body(exchange.openResponseBody(response))
.build()
}
//關(guān)閉連接?
if ("close".equals(response.request().header("Connection"), ignoreCase = true) ||
"close".equals(response.header("Connection"), ignoreCase = true)) {
exchange.noNewExchangesOnConnection()
}
//拋出連接異常?
if ((code == 204 || code == 205) && response.body()?.contentLength() ?: -1 > 0) {
throw ProtocolException(
"HTTP $code had non-zero Content-Length: ${response.body()?.contentLength()}")
}
//返回響應(yīng)數(shù)據(jù)?
return response
}
POST 異步請求
與GET異步操作一般無二旨涝,同樣是在真正發(fā)起服務(wù)的攔截器CallServerInterceptor.kt做具體的操作,這里不在贅述
預(yù)用知識說明
-
1)“Expect: 100-continue”的來龍去脈:
HTTP/1.1 協(xié)議 里設(shè)計100 (Continue)
HTTP 狀態(tài)碼的的目的是侣背,在客戶端發(fā)送Request Message
之前白华,HTTP/1.1 協(xié)議
允許客戶端先判定服務(wù)器是否愿意接受客戶端發(fā)來的消息主體(基于Request Headers
)即,Client
和Server
在Post
(較大)數(shù)據(jù)之前贩耐,允許雙方“握手”弧腥,如果匹配上了,Client
才開始發(fā)送(較大)數(shù)據(jù)潮太。這么做的原因是鸟赫,如果客戶端直接發(fā)送請求數(shù)據(jù),但是服務(wù)器又將該請求拒絕的話消别,這種行為將帶來很大的資源開銷抛蚤。
協(xié)議對
HTTP/1.1 clients
的要求是:
如果client
預(yù)期等待100-continue
的應(yīng)答,那么它發(fā)的請求必須包含一個Expect: 100-continue
的頭域寻狂!
2)libcurl
發(fā)送大于1024字節(jié) 數(shù)據(jù)時啟用Expect:100-continue
特性:
這也就是 Laruence
在 2011 年撰文所寫的:
在使用 curl
做 POST
的時候岁经,當(dāng)要 POST
的數(shù)據(jù)大于 1024 字節(jié)的時候,curl
并不會直接就發(fā)起 POST
請求蛇券,而是會分為兩步:
- 發(fā)送一個請求缀壤,包含一個
Expect: 100-continue
頭域,詢問Server
是否愿意接收數(shù)據(jù)纠亚;
- 接收到
Server
返回的100-continue
應(yīng)答以后塘慕,才把數(shù)據(jù)POST
給Server
;這是 libcurl 的行為蒂胞。
This ALL! Thanks EveryBody!