上篇 抽丝剥茧 okhttp3 (一) https://www.jianshu.com/p/be8a204f76a3
本来这篇文打算解析okhttp中的关于http中Method 的封装的,但是看了一遍发现确实太简单值得一提。所以索性直接来剥他的Request吧。根据http协议,请求的规范如下图所示:
Request
起始行开头的GET 或者POST表示请求访问服务器的类型,称为方法(method)。随后的字符串 /index.htm 指明了请求访问的资源对象,也叫做请求 URI(request-URI)。最后的 HTTP/1.1,即 HTTP 的版本号,用来提示客户端使用的 HTTP 协议功能。
综合来看,这段请求内容的意思是:请求访问某台 HTTP 服务器上的/index.htm 页面资源。
请求报文是由请求方法、请求 URI、协议版本、可选的请求首部字段和内容实体构成的。
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没有父类也没有子类也没有包装类也没实现接口,所以全局的okhttp的request实现就此一家。
纵观这个类,也是采用建造者模式进行构建的,来看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协议规定的所有元素通过传入进来。
- HttpUrl url; 前文解析过 先进标准的现代url对象
- String method; GET POST PUT DELETE 等等 而且默认是GET
- Headers.Builder headers; 头部字段集合
- RequestBody body; body 内容实体
- Object tag; 不属于http协议,用于给每个请求打标记,可以取消获取请求实例
其他方法中都是普通的类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协议自动转成了http,大家可以讨论下这里的原因 ,和用意。
Header
除去HttpUrl和Method 我们简单看一下header部分。Headers类也很简单,是个final 类 ,说明也是既没父类也无子类,独一家。
此类中同样采用了构建者模式。
同样Headers类的内容也是没有提供修改方法,所以也是不可修改的,内部用一个数组存放key value 奇数位存key 偶数位存value。
RequestBody
RequestBody是这部分的重点角色,因为网络编程中很多的交互都通过body来进行,比如POST 中的表单,json数据上传,上传文件等等,都少不了body的身影。注意,get请求是没有body的
首先我们看RequestBody是个抽象类,并且有两个子类实现。从字面上可以看出子类分别代表post请求的两类body,form表单的body和multypart的多部分上传(多部分上传又分为几种,比如文件上传,json,文本等多种上传)
目前网上没有找到好的关于介绍http协议的文章,等遇到的时候贴出来吧。好的关于RequestBody的家族就这么大,下面来一一攻破。
首先来看他们的父类RequestBody。看他的结构:
看来很简单,封装了
contentType 对应http请求头中的contentType
contentLength 对应http请求头中的contentLength
writeTo(BufferedSink sink) 把数据写入okio提供的缓冲池中。
余下的是五个重载的创建RequestBody对象的create方法。
五个方法 最终都是 返回一个new 出来的RequestBody对象,并实现了相关方法:
比如:普通的请求
/** 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内部类。玩建造者玩的不亦乐乎,所以以后我们封装些什么或者做一类库的时候builder的使用可以多多借鉴这些开源项目。
来看这个类比父类多了什么。
除了builder类,主类中多了两组List 分别存放post的表单中的key 和value 并且一一对应的,(这样使用比Map的k-v形式性能更高)。并且在biulder中提供了add方法来添加参数,查看下面源码,值得我们注意的是当我们向formbody中添加参数时,如果add的时候key多次传入同一值时,后者并不会覆盖前者,而是继续追加,并传给服务端。
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;
}
拼接参数的逻辑在这里
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方法中完成最终实体数据的拼接并写入缓冲池。
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++) {
//此层循环把每个part按照http的协议拼接好
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完结,写的也不好,不足的东西以后慢慢补上,学习和分享的过程应该如此,一边分享一边学习共同进步和成长。