抽絲剝繭 okhttp3 (二)

上篇 抽絲剝繭 okhttp3 (一) http://www.reibang.com/p/be8a204f76a3
本來這篇文打算解析okhttp中的關于http中Method 的封裝的,但是看了一遍發(fā)現(xiàn)確實太簡單值得一提淤击。所以索性直接來剝他的Request吧罩锐。根據(jù)http協(xié)議殉疼,請求的規(guī)范如下圖所示:

Request

Request盜的圖.png

起始行開頭的GET 或者POST表示請求訪問服務器的類型论颅,稱為方法(method)算芯。隨后的字符串 /index.htm 指明了請求訪問的資源對象棵譬,也叫做請求 URI(request-URI)。最后的 HTTP/1.1末购,即 HTTP 的版本號破喻,用來提示客戶端使用的 HTTP 協(xié)議功能。
綜合來看盟榴,這段請求內(nèi)容的意思是:請求訪問某臺 HTTP 服務器上的/index.htm 頁面資源曹质。
請求報文是由請求方法、請求 URI、協(xié)議版本羽德、可選的請求首部字段和內(nèi)容實體構成的几莽。

request 各個部分的具體用途大家可以自行google,本文主要來看okhttp中對整個request的封裝宅静。
首先還是看這個類的注釋:

/**
 * An HTTP request. Instances of this class are immutable if their {@link #body} is null or itself
 * immutable.
* 一個http請求章蚣,如果這個類的實例的body是空他就是不可變的,或者他自己就是不可變的 (什么玩意姨夹,好尷尬)
 */

大概意思是 正常情況下 他的對象實例是不可變的纤垂,想想每個http請求肯定也是一次性的,不存在被改變的情形匀伏,嗯洒忧,應該是這樣的我猜的蝴韭,有懂的請指教下 够颠,謝謝,榄鉴,履磨,Request沒有父類也沒有子類也沒有包裝類也沒實現(xiàn)接口,所以全局的okhttp的request實現(xiàn)就此一家庆尘。

縱觀這個類剃诅,也是采用建造者模式進行構建的,來看builder


  public static class Builder {
    HttpUrl url;
    String method;
    Headers.Builder headers;
    RequestBody body;
    Object tag;

    public Builder() {
      this.method = "GET";
      this.headers = new Headers.Builder();
    }

    Builder(Request request) {
      this.url = request.url;
      this.method = request.method;
      this.body = request.body;
      this.tag = request.tag;
      this.headers = request.headers.newBuilder();
    }
}

從成員變量和構造方法我們可以知道驶忌,builder 主要就是把http協(xié)議規(guī)定的所有元素通過傳入進來矛辕。

  • HttpUrl url; 前文解析過 先進標準的現(xiàn)代url對象
  • String method; GET POST PUT DELETE 等等 而且默認是GET
  • Headers.Builder headers; 頭部字段集合
  • RequestBody body; body 內(nèi)容實體
  • Object tag; 不屬于http協(xié)議,用于給每個請求打標記付魔,可以取消獲取請求實例
    其他方法中都是普通的類set get的方法聊品,值得注意的是在設置url的時候
public Builder url(String url) {
      if (url == null) throw new NullPointerException("url == null");

      // Silently replace web socket URLs with HTTP URLs.
      if (url.regionMatches(true, 0, "ws:", 0, 3)) {
        url = "http:" + url.substring(3);
      } else if (url.regionMatches(true, 0, "wss:", 0, 4)) {
        url = "https:" + url.substring(4);
      }

      HttpUrl parsed = HttpUrl.parse(url);
      if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
      return url(parsed);
    }

這里自動把ws 和wss 這兩種websocket協(xié)議自動轉(zhuǎn)成了http,大家可以討論下這里的原因 几苍,和用意翻屈。

Header

除去HttpUrl和Method 我們簡單看一下header部分。Headers類也很簡單妻坝,是個final 類 伸眶,說明也是既沒父類也無子類,獨一家刽宪。
此類中同樣采用了構建者模式厘贼。
同樣Headers類的內(nèi)容也是沒有提供修改方法,所以也是不可修改的圣拄,內(nèi)部用一個數(shù)組存放key value 奇數(shù)位存key 偶數(shù)位存value嘴秸。

RequestBody

RequestBody是這部分的重點角色,因為網(wǎng)絡編程中很多的交互都通過body來進行,比如POST 中的表單赁遗,json數(shù)據(jù)上傳署辉,上傳文件等等,都少不了body的身影岩四。注意哭尝,get請求是沒有body的

首先我們看RequestBody是個抽象類,并且有兩個子類實現(xiàn)剖煌。從字面上可以看出子類分別代表post請求的兩類body材鹦,form表單的body和multypart的多部分上傳(多部分上傳又分為幾種,比如文件上傳耕姊,json桶唐,文本等多種上傳)


image.png

目前網(wǎng)上沒有找到好的關于介紹http協(xié)議的文章,等遇到的時候貼出來吧茉兰。好的關于RequestBody的家族就這么大尤泽,下面來一一攻破。

首先來看他們的父類RequestBody规脸∨髟迹看他的結構:


RequestBody類結構圖

看來很簡單,封裝了
contentType 對應http請求頭中的contentType
contentLength 對應http請求頭中的contentLength

writeTo(BufferedSink sink) 把數(shù)據(jù)寫入okio提供的緩沖池中莫鸭。

余下的是五個重載的創(chuàng)建RequestBody對象的create方法闹丐。
五個方法 最終都是 返回一個new 出來的RequestBody對象,并實現(xiàn)了相關方法:
比如:普通的請求


  /** Returns a new request body that transmits {@code content}. */
  public static RequestBody create(
      final @Nullable MediaType contentType, final ByteString content) {
    return new RequestBody() {
      @Override public @Nullable MediaType contentType() {
        return contentType;
      }

      @Override public long contentLength() throws IOException {
        return content.size();
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        sink.write(content);
      }
    };
  }

上傳文件的請求

/** Returns a new request body that transmits the content of {@code file}. */
  public static RequestBody create(final @Nullable MediaType contentType, final File file) {
    if (file == null) throw new NullPointerException("content == null");

    return new RequestBody() {
      @Override public @Nullable MediaType contentType() {
        return contentType;
      }

      @Override public long contentLength() {
        return file.length();
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        Source source = null;
        try {
          source = Okio.source(file);
          sink.writeAll(source);
        } finally {
          Util.closeQuietly(source);
        }
      }
    };
  }

還是比較簡單的被因。
接下來是FormBody 類:
首先我們看到他的contentType是固定的卿拴,即:

private static final MediaType CONTENT_TYPE =
      MediaType.parse("application/x-www-form-urlencoded");

這就是我們常見的post 提交上傳表單的格式。所以這個類叫formbody梨与;總體來看這特么又有個builder內(nèi)部類堕花。玩建造者玩的不亦樂乎,所以以后我們封裝些什么或者做一類庫的時候builder的使用可以多多借鑒這些開源項目蛋欣。
來看這個類比父類多了什么航徙。


FormBody結構圖.png

除了builder類,主類中多了兩組List 分別存放post的表單中的key 和value 并且一一對應的陷虎,(這樣使用比Map的k-v形式性能更高)到踏。并且在biulder中提供了add方法來添加參數(shù),查看下面源碼尚猿,值得我們注意的是當我們向formbody中添加參數(shù)時窝稿,如果add的時候key多次傳入同一值時,后者并不會覆蓋前者凿掂,而是繼續(xù)追加伴榔,并傳給服務端纹蝴。

public Builder add(String name, String value) {
      if (name == null) throw new NullPointerException("name == null");
      if (value == null) throw new NullPointerException("value == null");

      names.add(HttpUrl.canonicalize(name, FORM_ENCODE_SET, false, false, true, true, charset));
      values.add(HttpUrl.canonicalize(value, FORM_ENCODE_SET, false, false, true, true, charset));
      return this;
    }

    public Builder addEncoded(String name, String value) {
      if (name == null) throw new NullPointerException("name == null");
      if (value == null) throw new NullPointerException("value == null");

      names.add(HttpUrl.canonicalize(name, FORM_ENCODE_SET, true, false, true, true, charset));
      values.add(HttpUrl.canonicalize(value, FORM_ENCODE_SET, true, false, true, true, charset));
      return this;
    }

拼接參數(shù)的邏輯在這里

 private long writeOrCountBytes(@Nullable BufferedSink sink, boolean countBytes) {
    long byteCount = 0L;

    Buffer buffer;
    if (countBytes) {
      buffer = new Buffer();
    } else {
      buffer = sink.buffer();
    }

    for (int i = 0, size = encodedNames.size(); i < size; i++) {
      if (i > 0) buffer.writeByte('&');
      buffer.writeUtf8(encodedNames.get(i));
      buffer.writeByte('=');
      buffer.writeUtf8(encodedValues.get(i));
    }

    if (countBytes) {
      byteCount = buffer.size();
      buffer.clear();
    }

    return byteCount;
  }

FormBody 到這里就結束了 ,實際上也很簡單踪少。MultipartBody 相對來說比較復雜塘安,但不算難。對于multipart的請求體 我們用的最多的是multipart/form-data類型的援奢,多用于上傳文件兼犯。其他類型用的很少我懂的也是皮毛所以一展開。關于multipart/form-data 請求可以讀下此文章:https://blog.csdn.net/five3/article/details/7181521

基于http的要求
MultipartBody封裝了除了用爛了的builder外還有代表每個部分的Part類集漾,builder用來組裝這些分部分切黔。

MultipartBody結構.png
Builder類結構

每個Part都有自己的一套header 和body

 final @Nullable Headers headers;
    final RequestBody body;

    private Part(@Nullable Headers headers, RequestBody body) {
      this.headers = headers;
      this.body = body;
    }

    public @Nullable Headers headers() {
      return headers;
    }

    public RequestBody body() {
      return body;
    }

MultipartBody中各個成員都來自于builder的構建。


  private final ByteString boundary;
  private final MediaType originalType;
  private final MediaType contentType;
  private final List<Part> parts;
  private long contentLength = -1L;

  MultipartBody(ByteString boundary, MediaType type, List<Part> parts) {
    this.boundary = boundary;
    this.originalType = type;
    this.contentType = MediaType.parse(type + "; boundary=" + boundary.utf8());
    this.parts = Util.immutableList(parts);
  }

同樣在writeOrCountBytes方法中完成最終實體數(shù)據(jù)的拼接并寫入緩沖池具篇。

private long writeOrCountBytes(
      @Nullable BufferedSink sink, boolean countBytes) throws IOException {
    long byteCount = 0L;

    Buffer byteCountBuffer = null;
    if (countBytes) {
      sink = byteCountBuffer = new Buffer();
    }

    for (int p = 0, partCount = parts.size(); p < partCount; p++) {
//此層循環(huán)把每個part按照http的協(xié)議拼接好
      Part part = parts.get(p);
      Headers headers = part.headers;
      RequestBody body = part.body;

      sink.write(DASHDASH);
      sink.write(boundary);
      sink.write(CRLF);

      if (headers != null) {
        for (int h = 0, headerCount = headers.size(); h < headerCount; h++) {
          sink.writeUtf8(headers.name(h))
              .write(COLONSPACE)
              .writeUtf8(headers.value(h))
              .write(CRLF);
        }
      }

      MediaType contentType = body.contentType();
      if (contentType != null) {
        sink.writeUtf8("Content-Type: ")
            .writeUtf8(contentType.toString())
            .write(CRLF);
      }

      long contentLength = body.contentLength();
      if (contentLength != -1) {
        sink.writeUtf8("Content-Length: ")
            .writeDecimalLong(contentLength)
            .write(CRLF);
      } else if (countBytes) {
        // We can't measure the body's size without the sizes of its components.
        byteCountBuffer.clear();
        return -1L;
      }

      sink.write(CRLF);

      if (countBytes) {
        byteCount += contentLength;
      } else {
        body.writeTo(sink);
      }

      sink.write(CRLF);
    }

    sink.write(DASHDASH);
    sink.write(boundary);
    sink.write(DASHDASH);
    sink.write(CRLF);

    if (countBytes) {
      byteCount += byteCountBuffer.size();
      byteCountBuffer.clear();
    }

    return byteCount;
  }

至此纬霞,body完結,寫的也不好驱显,不足的東西以后慢慢補上诗芜,學習和分享的過程應該如此,一邊分享一邊學習共同進步和成長秒紧。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绢陌,一起剝皮案震驚了整個濱河市挨下,隨后出現(xiàn)的幾起案子熔恢,更是在濱河造成了極大的恐慌,老刑警劉巖臭笆,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叙淌,死亡現(xiàn)場離奇詭異,居然都是意外死亡愁铺,警方通過查閱死者的電腦和手機鹰霍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來茵乱,“玉大人茂洒,你說我怎么就攤上這事∑拷撸” “怎么了督勺?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長斤贰。 經(jīng)常有香客問我智哀,道長,這世上最難降的妖魔是什么荧恍? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任瓷叫,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘摹菠。我一直安慰自己盒卸,他們只是感情好,可當我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布次氨。 她就那樣靜靜地躺著世落,像睡著了一般。 火紅的嫁衣襯著肌膚如雪糟需。 梳的紋絲不亂的頭發(fā)上屉佳,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天,我揣著相機與錄音洲押,去河邊找鬼武花。 笑死,一個胖子當著我的面吹牛杈帐,可吹牛的內(nèi)容都是我干的体箕。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼挑童,長吁一口氣:“原來是場噩夢啊……” “哼累铅!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起站叼,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤娃兽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后尽楔,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體投储,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年阔馋,在試婚紗的時候發(fā)現(xiàn)自己被綠了玛荞。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡呕寝,死狀恐怖勋眯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情下梢,我是刑警寧澤客蹋,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站怔球,受9級特大地震影響嚼酝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜竟坛,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一闽巩、第九天 我趴在偏房一處隱蔽的房頂上張望钧舌。 院中可真熱鬧,春花似錦涎跨、人聲如沸洼冻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽撞牢。三九已至,卻和暖如春叔营,著一層夾襖步出監(jiān)牢的瞬間屋彪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工绒尊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留畜挥,地道東北人。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓婴谱,卻偏偏與公主長得像蟹但,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子谭羔,可洞房花燭夜當晚...
    茶點故事閱讀 43,724評論 2 351

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

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理伯顶,服務發(fā)現(xiàn)崇呵,斷路器祷膳,智...
    卡卡羅2017閱讀 134,638評論 18 139
  • 最近難得有時間丘喻,可以看看平時經(jīng)常用的牛逼的三方框架是怎么實現(xiàn)的,學習學習景描。比如okhttp 十办,眼下安卓開發(fā) 網(wǎng)絡框...
    張哲1111閱讀 1,233評論 0 1
  • 6.1 公鑰密鑰加密原理 6.1.1 基礎知識 密鑰:一般就是一個字符串或數(shù)字,在加密或者解密時傳遞給加密/解密算...
    AndroidMaster閱讀 4,006評論 1 8
  • 一超棺、簡介 Retrofit是Square公司開發(fā)的一款針對Android網(wǎng)絡請求的框架,Retrofit2底層基于...
    Devil不加V閱讀 544評論 0 0
  • 最近我也睡懶覺 不是我愛 是你愛 我怕一覺醒來 陷入思念的自己
    林百錯閱讀 209評論 0 3