okHttp框架分析--GET和POST請求

概要

我們平時使用最多的請求方式莫過于GETPOST請求,它們幾乎完成我們平時所需的基本任務(wù),下面我們分析一下它們的區(qū)別以及在OkHttp中的調(diào)用流程

GET和POST區(qū)別

    1. GETPOST請求都是的HTTP協(xié)議都是基于TCP/IP的應(yīng)用層協(xié)議尸诽,它們使用的是同一個傳輸層協(xié)議,所以在傳輸上相同
    1. GET請求有字符長度限制POST請求沒有,瀏覽器通常都會限制URL長度在2K個字節(jié)盯另,而(大多數(shù))服務(wù)器最多處理64K大小的URL性含;主要原因來自于瀏覽器服務(wù)器,由于GET請求是明文傳輸鸳惯,在URL中可在?后攜帶多個參數(shù)并以&分割胶滋,為避免URL消耗資源過多,同時也為了[性能]和[安全]考慮所以為URL長度進行了限制悲敷;而POST請求會將敏感數(shù)據(jù)封裝進Request body中,所以不會造成URL過長現(xiàn)象
    1. GET只接受ASCII字符究恤,而POST沒有限制
    1. GET請求只能進行URL編碼,而POST支持多種編碼方式
    1. GETPOST更不安全后德,因為參數(shù)直接暴露在URL上部宿,所以不能用來傳遞敏感信息,若想保證傳輸安全,可通過HTTPS進行加密
    1. GET請求產(chǎn)生一個TCP數(shù)據(jù)包瓢湃;POST產(chǎn)生兩個TCP數(shù)據(jù)包理张;對于GET方式的請求,瀏覽器會把HeaderData一并發(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同步流程解析

GET流程圖.jpg

[ 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同步流程解析

POST流程圖.jpg

[ 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)即, ClientServerPost (較大)數(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 年撰文所寫的:
在使用 curlPOST 的時候岁经,當(dāng)要 POST 的數(shù)據(jù)大于 1024 字節(jié)的時候,curl 并不會直接就發(fā)起 POST 請求蛇券,而是會分為兩步:

    1. 發(fā)送一個請求缀壤,包含一個 Expect: 100-continue 頭域,詢問 Server 是否愿意接收數(shù)據(jù)纠亚;
    1. 接收到 Server 返回的 100-continue 應(yīng)答以后塘慕,才把數(shù)據(jù) POSTServer;這是 libcurl 的行為蒂胞。
      This ALL! Thanks EveryBody!
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末图呢,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蛤织,老刑警劉巖赴叹,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異指蚜,居然都是意外死亡乞巧,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門摊鸡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绽媒,“玉大人,你說我怎么就攤上這事免猾∈窃” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵掸刊,是天一觀的道長。 經(jīng)常有香客問我赢乓,道長忧侧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任牌芋,我火速辦了婚禮蚓炬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘躺屁。我一直安慰自己肯夏,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布犀暑。 她就那樣靜靜地躺著驯击,像睡著了一般。 火紅的嫁衣襯著肌膚如雪耐亏。 梳的紋絲不亂的頭發(fā)上徊都,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機與錄音广辰,去河邊找鬼暇矫。 笑死,一個胖子當(dāng)著我的面吹牛择吊,可吹牛的內(nèi)容都是我干的李根。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼几睛,長吁一口氣:“原來是場噩夢啊……” “哼房轿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤冀续,失蹤者是張志新(化名)和其女友劉穎琼讽,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體洪唐,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡钻蹬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了凭需。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片问欠。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖粒蜈,靈堂內(nèi)的尸體忽然破棺而出顺献,到底是詐尸還是另有隱情,我是刑警寧澤枯怖,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布注整,位于F島的核電站,受9級特大地震影響度硝,放射性物質(zhì)發(fā)生泄漏肿轨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一蕊程、第九天 我趴在偏房一處隱蔽的房頂上張望椒袍。 院中可真熱鬧,春花似錦藻茂、人聲如沸驹暑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽优俘。三九已至,卻和暖如春掀序,著一層夾襖步出監(jiān)牢的瞬間兼吓,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工森枪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留视搏,地道東北人。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓县袱,卻偏偏與公主長得像浑娜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子式散,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容