抽絲剝繭 okhttp3 (二)

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

Request

Request盜的圖.png

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

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

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

大概意思是 正常情況下 他的對象實例是不可變的,想想每個http請求肯定也是一次性的,不存在被改變的情形,嗯,應(yīng)該是這樣的我猜的,有懂的請指教下 ,謝謝,,,Request沒有父類也沒有子類也沒有包裝類也沒實現(xiàn)接口,所以全局的okhttp的request實現(xiàn)就此一家。

縱觀這個類,也是采用建造者模式進行構(gòu)建的,來看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();
    }
}

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

  • HttpUrl url; 前文解析過 先進標(biāo)準(zhǔn)的現(xiàn)代url對象
  • String method; GET POST PUT DELETE 等等 而且默認(rèn)是GET
  • Headers.Builder headers; 頭部字段集合
  • RequestBody body; body 內(nèi)容實體
  • Object tag; 不屬于http協(xié)議,用于給每個請求打標(biāo)記,可以取消獲取請求實例
    其他方法中都是普通的類set get的方法,值得注意的是在設(shè)置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 類 ,說明也是既沒父類也無子類,獨一家。
此類中同樣采用了構(gòu)建者模式。
同樣Headers類的內(nèi)容也是沒有提供修改方法,所以也是不可修改的,內(nèi)部用一個數(shù)組存放key value 奇數(shù)位存key 偶數(shù)位存value。

RequestBody

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

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


image.png

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

首先來看他們的父類RequestBody??此慕Y(jié)構(gòu):


RequestBody類結(jié)構(gòu)圖

看來很簡單,封裝了
contentType 對應(yīng)http請求頭中的contentType
contentLength 對應(yīng)http請求頭中的contentLength

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

余下的是五個重載的創(chuàng)建RequestBody對象的create方法。
五個方法 最終都是 返回一個new 出來的RequestBody對象,并實現(xiàn)了相關(guā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結(jié)構(gòu)圖.png

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

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 到這里就結(jié)束了 ,實際上也很簡單。MultipartBody 相對來說比較復(fù)雜,但不算難。對于multipart的請求體 我們用的最多的是multipart/form-data類型的,多用于上傳文件。其他類型用的很少我懂的也是皮毛所以一展開。關(guān)于multipart/form-data 請求可以讀下此文章:https://blog.csdn.net/five3/article/details/7181521

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

MultipartBody結(jié)構(gòu).png
Builder類結(jié)構(gòu)

每個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的構(gòu)建。


  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完結(jié),寫的也不好,不足的東西以后慢慢補上,學(xué)習(xí)和分享的過程應(yīng)該如此,一邊分享一邊學(xué)習(xí)共同進步和成長。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容