上篇 抽絲剝繭 okhttp3 (一) http://www.reibang.com/p/be8a204f76a3
本來這篇文打算解析okhttp中的關于http中Method 的封裝的,但是看了一遍發(fā)現(xiàn)確實太簡單值得一提淤击。所以索性直接來剝他的Request吧罩锐。根據(jù)http協(xié)議殉疼,請求的規(guī)范如下圖所示:
Request
起始行開頭的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桶唐,文本等多種上傳)
目前網(wǎng)上沒有找到好的關于介紹http協(xié)議的文章,等遇到的時候貼出來吧茉兰。好的關于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的使用可以多多借鑒這些開源項目蛋欣。
來看這個類比父類多了什么航徙。
除了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用來組裝這些分部分切黔。
每個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完結,寫的也不好驱显,不足的東西以后慢慢補上诗芜,學習和分享的過程應該如此,一邊分享一邊學習共同進步和成長秒紧。