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

起始行開頭的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,文本等多種上傳)

目前網(wǎng)上沒有找到好的關(guān)于介紹http協(xié)議的文章,等遇到的時候貼出來吧。好的關(guān)于RequestBody的家族就這么大,下面來一一攻破。
首先來看他們的父類RequestBody??此慕Y(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的使用可以多多借鑒這些開源項目。
來看這個類比父類多了什么。

除了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用來組裝這些分部分。


每個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í)共同進步和成長。