OkHttp与HTTP协议
Posted Danny_姜
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OkHttp与HTTP协议相关的知识,希望对你有一定的参考价值。
OkHttp,从名字也能感觉到这套框架似乎与http协议有着千丝万缕的关系。事实上也确实如此,OkHttp所做的各种操作都是建立在http协议基础之上的。因此在理解并自己手写OkHttp之前,首先需要了解什么是http协议,以及http协议具体有哪些规则(或者说是格式)
为什么需要http协议
一般情况下,网络数据由客户端向服务端发送,数据进入互联网之后,是以二进制流(Stream)的形式进行传播,过程如下图所示:
客户端浏览器访问网页 “www.sample.com” 并进行登录操作,需要向服务端发送用户名与密码;然后服务端接收到客户端的请求之后,对数据进行封装、解析并返回响应结果。
但是正如图中所示,客户端发送的数据在互联网内部是以 二进制流 ,也就是010101…
这种格式进行转发的。服务端最终接收到的只是一堆 二进制流 数据,并不清楚其具体代表的是什么含义,造成的后果也就如下图所示:
为了让服务端与客户端之间能够互相理解,需要先制定一套规范。客户端按照这套规范对需要发送的数据进行格式化,同样服务端按照这套规范对数据进行解析。比如下图中描述的就是一个最简单的规范格式:
根据上述规范,一个完整的网络请求操作包含以下几步:
客户端在发送之前,需要构造一个字节数组byte[2048],并使用前1024个byte保存账户”axing",将”123456”保存在后1024个byte中。
计算机网卡会将字节数组转换为二进制数据,并在互联网中进行传播。
服务端收到二进制数据之后,首先将其重新封装到byte数组中,然后从前1024个byte中取出账户信息,以及从后1024个byte中取出密码信息。
但是,实际项目中的网络请求远不止如此简单。我们经常需要在发送网络数据的同时,附带一些其它额外信息。比如请求日期Date、请求数据格式、语言类型、数据压缩格式、数据有效期等等。这就要求有一套定义更加完善,兼容性更高的规范来帮助我们实现更复杂的网络请求操作,而这套规范就是HTTP协议。
http协议格式简介
按照http协议收发的数据叫做http消息,
主要分为 请求消息 和 响应消息
不论是请求消息还是响应消息都具有严格的格式定义,更加详细的内容将会在后续实现OkHttp的文章中,循序渐进的向读者介绍。这节我们先简单了解下http消息主要由哪几部分组成,如下图所示:
可以看出,请求消息和响应消息都是由首行、消息头、消息体这3部分组成。但是两者也有一定的区别:
请求消息的首行叫做请求行,包含客户端发起请求的方法以及请求地址;
响应消息的首行叫做状态行,包含服务端的响应码与响应短语。
至于客户端具体可以使用哪种方法发起请求,以及服务端具体有哪些响应状态,会在后续实现文章详细介绍。
注意:实际上,不论是请求消息还是响应消息,在消息头和消息体之间都会有一个空行,用来代表消息头(Header)数据结束。
OkHttp与http之间的关系
在我介绍OkHttp时,我经常会将它称为
mini版浏览器,或者是 无UI版浏览器
因为OkHttp的工作机制与浏览器非常相似
同浏览器一样,OkHttp需要将用户的各种操作转化为相对应的http请求对象。比如下图中用户点击了"登录"操作,OkHttp会将其转化为图中右方的请求格式,并发送给服务端。
针对上述操作,一种简单的伪代码实现方式如下:
public void sendRequest()
OutputStream os = socket.getOutputStream();
// 写入请求行
os.write("POST ");
os.write("/sample ");
os.write("HTTP/1.1 \\n");
// 写入请求头
os.write("Accept=*/*");
...
// 写入请求体
os.write("account=axing\\n");
os.write("pwd=123456");
Header & Body
上述代码只是一种伪代码实现,如果实际实现也是直接使用OutputStream依次将所有数据进行写入操作,未免有些麻烦,这样的代码很不优雅。高级一点的工程师立马能想到应该在OutputStream之上封装一层实体类,用来代表请求头Headers和请求体Body。
实际上,在OkHttp中也正是使用了这种方式,Heads和Body如下所示:
/**
* 请求头
*/
public final class Headers
private final String[] namesAndValues;
...
/**
* 请求体
*/
public abstract class RequestBody
/** Returns the Content-Type header for this body. */
public abstract @Nullable MediaType contentType();
...
public static RequestBody create(@Nullable MediaType contentType, String content)
Charset charset = Util.UTF_8;
if (contentType != null)
charset = contentType.charset();
if (charset == null)
charset = Util.UTF_8;
contentType = MediaType.parse(contentType + "; charset=utf-8");
byte[] bytes = content.getBytes(charset);
return create(contentType, bytes);
Request & Response
有了Header和Body之后,就可以创建出专门用来发送消息的请求类Request,如下所示:
这样框架使用者就可以很方便的使用Request类来存储一次网络请求的地址、请求方式、请求头、请求体等数据。
同样,对于响应消息也需要一个专门的封装类Response来表示,如下
请求发送和接收
不管是Request还是Response,都需要将数据提交给TCP协议层,并以流的形式在互联网中进行传播。在Java SDK中,有一个专门负责完成这一操作的类Socket,因此我们还需要手动调用Socket API实现网络通信。在OkHttp框架中,这部分操作被封装在HttpCodec类中,如下图所示
图中红框标记的finishRequest和openResponseBody,分别代表调用Socket发送请求数据,以及读取响应结果中的信息。
实际上,一次网络请求操作除了基本的通信需求之外,还需要考虑很多其它情况,比如缓存、代理、https支持等。本书在先实现网络通信的基础上,后续也会对这部分内容做详细封装介绍。
总结
本系列文章的内容是自己动手实现OkHttp框架,但实际最终目标是带领大家在实现过程中,对http协议(也有部分TCP协议)有一个完整深入的理解,这整个实现过程也可以看做是对http协议的二次封装。
如果你喜欢本文
长按二维码关注
以上是关于OkHttp与HTTP协议的主要内容,如果未能解决你的问题,请参考以下文章