OkHttp与HTTP协议

OkHttp,从名字也能感觉到这套框架似乎与http协议有着千丝万缕的关系。事实上也确实如此,OkHttp所做的各种操作都是建立在http协议基础之上的。因此在理解并自己手写OkHttp之前,首先需要了解什么是http协议,以及http协议具体有哪些规则(或者说是格式)

为什么需要http协议

一般情况下,网络数据由客户端向服务端发送,数据进入互联网之后,是以二进制流(Stream)的形式进行传播,过程如下图所示:

客户端浏览器访问网页 “www.sample.com” 并进行登录操作,需要向服务端发送用户名与密码;然后服务端接收到客户端的请求之后,对数据进行封装、解析并返回响应结果。

但是正如图中所示,客户端发送的数据在互联网内部是以 二进制流 ,也就是010101… 这种格式进行转发的。服务端最终接收到的只是一堆 二进制流 数据,并不清楚其具体代表的是什么含义,造成的后果也就如下图所示:

为了让服务端与客户端之间能够互相理解,需要先制定一套规范。客户端按照这套规范对需要发送的数据进行格式化,同样服务端按照这套规范对数据进行解析。比如下图中描述的就是一个最简单的规范格式:

根据上述规范,一个完整的网络请求操作包含以下几步:

  1. 客户端在发送之前,需要构造一个字节数组byte[2048],并使用前1024个byte保存账户”axing",将”123456”保存在后1024个byte中。

  2. 计算机网卡会将字节数组转换为二进制数据,并在互联网中进行传播。

  3. 服务端收到二进制数据之后,首先将其重新封装到byte数组中,然后从前1024个byte中取出账户信息,以及从后1024个byte中取出密码信息。

但是,实际项目中的网络请求远不止如此简单。我们经常需要在发送网络数据的同时,附带一些其它额外信息。比如请求日期Date请求数据格式语言类型数据压缩格式数据有效期等等。这就要求有一套定义更加完善,兼容性更高的规范来帮助我们实现更复杂的网络请求操作,而这套规范就是HTTP协议。


http协议格式简介

按照http协议收发的数据叫做http消息,

主要分为 请求消息 和 响应消息

不论是请求消息还是响应消息都具有严格的格式定义,更加详细的内容将会在后续实现OkHttp的文章中,循序渐进的向读者介绍。这节我们先简单了解下http消息主要由哪几部分组成,如下图所示:

可以看出,请求消息和响应消息都是由首行消息头消息体这3部分组成。但是两者也有一定的区别:

  1. 请求消息的首行叫做请求行,包含客户端发起请求的方法以及请求地址;

  2. 响应消息的首行叫做状态行,包含服务端的响应码与响应短语。

至于客户端具体可以使用哪种方法发起请求,以及服务端具体有哪些响应状态,会在后续实现文章详细介绍。

注意:实际上,不论是请求消息还是响应消息,在消息头和消息体之间都会有一个空行,用来代表消息头(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协议的二次封装。

如果你喜欢本文

长按二维码关注

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页