对HTTP协议的理解

远子 â€¢  2021å¹´03月23日

互联网最初的理念是:借助多文档之间相互关联形成的超文本,连接成可相互参阅的WWW(World Wide Web,万维网)。

现在互联网有三种构建技术:

  1. 作为页面的文本标记语言的HTML;
  2. 作为文档传递协议的HTTP;
  3. 指定文档地址的URL;

互联网协议

注:本章大部分内容来自《互联网协议入门(一)》,附带了一些我的理解。

互联网的实现分为好几层,每层都有自己的功能。

从最底层自上依次分为:实体层、链接层、网络层、传输层、应用层。

img

互联网分层是有好处的,如果互联网只有一个层统筹,当某个地方需要改变设计时,就必须把所有部分整体换掉。而分层之后只需把变动的层替换掉即可。把各层之间的接口部分规划好之后,每个层次内部的设计就能够自由改动了。

层次化之后,设计也变得相对简单了。处于应用层上的应用可以只考虑分派给自己的任务,而不需要弄清对方在地球上哪个地方、对方的传输路线是怎样的、能否确保传输送达等问题。

实体层

实体层或者叫物理层,是把电脑连接起来的物理手段,主要规定了网络的一些电气特性,作用是负责传送 0 和 1 的电信号。

img

链接层

单纯的 0 和 1 没有任何意义,链接层的作用是确定 0 和 1 的分组方式。

早期的时候每家公司都有自己的电信号分组方式,逐渐的以太网协议占据了主导地位。

关于以太网有三个知识点:

  1. 把 0 和 1 电信号包装成数据包;
  2. 通过网卡的 MAC 地址识别发送者和接受者;
  3. 通过广播的方式,发送数据包;

以太网协议规定,一组电型号构成一个数据包,每个数据包分成两个部分:Head 和 Data。

img

Head 中包含了:发送者、接受者、数据类型等;Data 则是数据包的具体内容。

怎么识别发送者和接受者呢?以太网协议规定,连入网络的所有设备都必须具有网卡接口,每块网卡出厂的时候都有一个全球唯一的 MAC 地址。

有了 MAC 地址还不够,想一下,李雷怎么知道韩梅梅的 MAC 地址呢?

答案是广播,以太网向本网络内所有的计算机发送数据,让每台计算机自己判断是否接收。

img

上图中,1 号计算机要向 2 号计算机发送一个数据包,2号、3号、4号、5号计算机都会收到这个包。他们读取这个包的 Head,找到接收方的 MAC 地址,然后与自身的 MAC 的地址比较,如果两者相同则接收,否则丢弃。

网络层

以太网协议使用广播方式发送数据包有一个重大的缺点,所有成员人手一“包”,不仅效率低,而且局限在发送者所在的子网络。也就是说两台计算机如果不在同一个子网络,是无法收到数据的。

这就导致了网络层的诞生,网络层的作用是引进一套新的地址,使得我们能够区分不同的计算机是否属于同一个子网络。

这套新的地址就是 IP 地址,于是每台计算机有了两种地址,一种是 MAC 地址,MAC 地址可以理解成物理地址,出厂后即绑定在网卡上;另一种是 IP 地址,IP 地址是动态的,同一台电脑在不同的 Wifi 下的 IP 地址不同。

MAC 地址的格式:00:e0:4c:7a:2b:01,Mac 电脑可以在 关于本机 > 系统信息 > 以太网卡 中查看。

IP 地址的格式:172.16.219.53,Mac 电脑可以在 系统偏好设置 > 网络 中查看。

IP 地址配合子网掩码才可以判断两台计算机是否处于同一个网络。

IP 协议的数据包也分为 Head 和 Data 两部分:

img

IP 数据包放进以太网数据包的 Data 后,以太网数据包就变成了下边这样:

img

IP 地址很难记忆,就像手机号码一样,我们一般会给联系人加备注。

在互联网中,域名就是 IP 的备注,我们输入 www.baidu.com 访问百度时,本质上是在向 220.181.38.148 请求数据。这个域名和 IP 之间的映射,就是 DNS 协议。

注:

目前采用的 IP 协议是 IP 协议的第四版,简称 IPv4,IPv4 使用 32 位(4字节)地址,因此地址空间中只有 4,294,967,296 个地址,范围是从 0.0.0.0 一直到 255.255.255.255。

2019 年 11 月 26 日,全球所有 43 亿个 IPv4 地址已分配完毕,这意味着没有更多的 IPv4 地址可以分配给 ISP 和其他大型网络基础设施提供商。

于是有了 IPv6,其地址数量号称可以为全世界的每一粒沙子编上一个地址。IPv6 的地址长度为 128 位,是 IPv4 的 4 倍,IPv6 用十六进制表示,例如:2001:0db8:86a3:08d3:1319:8a2e:0370:7344。

传输层

有了 MAC 地址和 IP 地址后,我们已经可以在互联网上任意两台主机上建立通信。

接下来的问题是,同一台计算机上有许多程序需要使用网络。比如,我们一边浏览网页、一边听歌、一边聊天,当一个数据包从互联网上发来的时候,怎么区分这个数据包是网页的内容,还是歌词还是聊天内容呢?

答案是端口,端口是 0 到 65535 之间的一个整数,0 到 1023 的端口被系统占用,用户只能用 1024 到 65535 之间的端口。

应用程序会监听某个端口,然后与服务器的相应端口建立联系。

传输层的作用是建立“客户端端口”到“服务器端口”之间的通信。相比之下,网络层的功能是建立“主机到主机”之间的通信。

这种数据+端口的协议就是 UDP 协议,UDP 数据包也是由 Head 和 Data 两部分组成:

img

UDP 的 Head 里存储了端口号。UDP 数据包放入 IP 数据包的 Data 部分,IP 数据包再放在以太网数据包的 Data 部分,以太网数据包就变成了下边这样:

img

传输层有两个最常用的协议:TCP 是可靠的,UDP 是不可靠的。

UDP 协议的有点是简单容易实现,缺点是可靠性差,一旦数据包发出,无法知道对方是否收到。为了解决这个问题,诞生了 TCP 协议。

TCP 协议非常复杂,可以简单理解成有确认机制的 UDP 协议。“三次握手建立连接、四次挥手断开连接”。

应用层

应用程序收到来自传输层的数据后,接下来就要进行解读。由于互联网是开放架构,数据来源五花八门,必须事先规定好格式,否则根本无法解读。

应用层的作用就是规定应用程序的数据格式。

应用层是面向用户的一层,也就是我们前端代码在整个互联网架构中所处的位置。

应用层的数据放在 TCP数据包的 Data 部分,现在的以太网数据包就变成了下边这样:

image-20210323114252506

HTTP

从客户端到服务器的通信流程是这样的:

image-20210323114442869

HTTP 协议是应用层协议,主要规定了应用层数据的格式。

HTTP 发展过程

HTTP 协议的发展很坎坷,目前主流的版本是 1.1。

HTTP/0.9

最早版本是 1991 年发布的 0.9 版。该版本极其简单,只有一个命令 GET:

GET /index.html

协议规定,服务器只能返回 HTML 格式的字符串:

<html>
  <body>Hello World</body>
</html>

服务器发送完毕后,关闭 TCP 连接。

HTTP/1.0

1996 年 5 月发布的 HTTP/1.0 版本,内容大大增加。

HTTP/1.0 协议的特点:

  • 不限于 html,支持任何格式(图像、视频、二进制文件);
  • 除了 GET 命令,新增了 POST 和 HEAD 命令;
  • 请求和响应的格式变了,除了数据部分,新增了头信息(HTTP Header);
  • 其他新增工能还包括:状态码(Status Code)、多字符集支持、多部分发送(Multi-part Type)、权限(Authorization)、缓存(Cache)、内容编码(Content Encoding)等;

缺点:每个 TCP 连接只能发送一个请求,发送数据完毕后连接就关闭。如果还要请求其他资源,就必须再新建一个连接。

TCP 连接的新建成本很高,因为需要客户端和服务器三次握手,并且开始时发送速率较慢。所以,HTTP/1.o 版本的性能比较差。

HTTP/1.1

1997 年 1 月发布的 HTTP/1.1 版本,进一步完善了 HTTP 协议,一直用到了 24 年后的今天。

HTTP/1.1 协议的特点:

  • 引入了持久连接,即 TCP 连接默认不关闭,可以被多个请求复用;
  • 引入了管道机制,即在一个 TCP 连接里,客户端可以同时发送多个请求;
  • 支持分块传输;
  • 新增请求方法:PUT、PATCH、HEAD、OPTIONS、DELETE;

缺点:虽然 HTTP/1.1 版本允许复用 TCP 连接,但是同一个 TCP 连接里,所有数据通信是按次序进行的。服务器只有处理完一个回应,才会进行下一个回应。要是前面的回应特别慢,后边就会有许多请求排队,这称为“队头阻塞”。

为了避免这个问题,有两种做法:

  1. 减少请求数,如合并脚本、样式表、图标等;
  2. 同时多开持久连接,目前对于同一个域名,大多数浏览器允许同时建立 6 个持久连接。

HTTP/2

2015 年 5 月发布的 HTTP/2 版本,保留了 HTTP/1.1 的大部分语法。HTTP/2 相比 HTTP/1.1 的修改并不会破坏现有程序的工作,但是新的程序可以借由新特性得到更好的速度。

HTTP/2 的开发基于 SPDY 协议,SPDY 协议是一个由 Google 主导的研究项目发明的 HTTP 替代协议。

HTTP/2 协议的特点:

  • 头信息改成了二进制格式;
  • 头信息引入了压缩机制,避免重复发送同样的内容;
  • 在一个 TCP 连接里,客户端可以发送多个请求,服务器可以发送多个回应,而且不用按照顺序一一对应,这样就避免了“队头阻塞”;
  • 允许服务器主动向客户端推送资源。

多数浏览器已经支持 HTTP/2

image-20210323132739024

HTTP 连接过程

当用户访问 poppython.com 时,过程如下:

  1. 打开一个 TCP 连接:TCP 连接被用来发送一条或多条请求,以及接受响应消息。客户端可能打开一条新的连接,或者复用一个已经存在的连接,或者也可能开几个新的 TCP 连接联向服务端。
  2. 发送一个 HTTP 报文:HTTP 报文是语义可读的。在 HTTP/2 中,报文是二进制的,不能被直接读取。

    Host: poppython.com
    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.0; rv:77.0) Gecko/20100101 Firefox/77.0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
    Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
    Accept-Encoding: gzip, deflate, br
    Connection: keep-alive
    Upgrade-Insecure-Requests: 1
    Cache-Control: max-age=0
  3. 读取服务端返回的报文信息:

    HTTP/2 200 OK
    server: nginx
    date: Tue, 23 Mar 2021 05:01:41 GMT
    content-type: text/html; charset=UTF-8
    vary: Accept-Encoding
    strict-transport-security: max-age=31536000
    content-encoding: gzip
    X-Firefox-Spdy: h2
  4. 关闭连接或者为后续请求重用连接。

HTTP 特点

  • HTTP 是简单的,虽然 HTTP/2 协议将 HTTP 消息封装到了二进制,HTTP 大体上还是被设计的简单易读。HTTP 报文能够被人读懂,还允许简单测试,降低了门槛,对新人很友好;
  • HTTP 是可扩展的,在 HTTP Headers 可以加入自定义属性,只要服务端和客户端达成一致,新功能可以被轻松加入进来。比如可以添加 X-Authorization 用来存放用户认证参数;
  • HTTP 是无状态、有会话的,无状态的意思是,对于服务器来说李雷的请求和韩梅梅的请求之间是没有关系的,有会话的意思是通过 Cookie 识别用户是李雷还是韩梅梅;
  • HTTP 和连接

HTTP 报文

用于 HTTP 协议交互的信息被称为 HTTP 报文。

请求端(客户端)的 HTTP 报文叫做请求报文,响应端(服务器)的叫做响应报文。

HTTP 报文本身是有多行数据构成的字符串文本。

image-20210323141136371

请求报文包含了以下元素:

  • Method(请求方法)
  • Path(资源路径)
  • HTTP 协议版本号
  • Headers(请求头)
  • 像 POST、PUT 这样的方法可以携带 Body

image-20210323141145319

响应报文包含了以下元素:

  • HTTP 协议版本号
  • Status Code(HTTP 协议版本号)
  • Status Message(状态信息)
  • Headers(响应头)
  • Body(响应内容)

HTTP 头

以 poppython.com 的首页为例:

image-20210323115225510

请求头可以分为三部分:

  • General Headers:同时适用于请求和响应消息;
  • Request Headers:关于更多有关要获取的资源或者客户端本身消息的消息头;
  • Response Headers:包含关于响应的补充信息;
  • Entity Headers:包含有关实体的更多信息;

具体的请求头清单可以参考 MDN。

HTTP 请求方法

  • GET:常用于获取数据;
  • HEAD:同 GET 类似,但没有响应体。一个使用场景是:在下载大文件之前先获取一下资源大小再决定是否要下载,以此可以节约带宽资源;
  • POST:常用于创建数据;
  • PUT:常用于修改数据;
  • DELETE:常用于删除数据;
  • CONNECT:在 HTTP 协议中,CONNECT 方法可以开启一个客户端与所请求资源之间的双向沟通的通道。它可以用来创建隧道(tunnel)。
  • OPTIONS:场景一:用来检测服务器所支持的请求方法。场景二:在 CORS 中用来检测实际请求是否可以被服务器所接受,客户端带着 Access-Control-Request-Method 询问服务器,服务器返回 Access-Control-Allow-Method。
  • TRACE:HTTP TRACE 方法 实现沿通向目标资源的路径的消息环回(loop-back)测试 ,提供了一种实用的 debug 机制。
  • PATCH:常用于修改数据,与 PUT 的区别是,PATCH 用于对资源进行部分修改;

HTTP 状态码

HTTP 状态码是一个 100 - 599 之间的数字,分为五类:

  • 信息响应(100 - 199)
  • 成功响应(200 - 299)
  • 重定向(300 - 399)
  • 客户端错误(400 - 499)
  • 服务器错误(500 - 599)

具体的状态码清单可以参考MDN。

CSP 指令

HTTP 响应头Content-Security-Policy允许站点管理者控制用户代理能够为指定的页面加载哪些资源。除了少数例外情况,设置的政策主要涉及指定服务器的源和脚本结束点。这将帮助防止跨站脚本攻击(Cross-Site Script)(XSS)。

SCP 的值可以参考MDN。

常见场景

压缩

请求报文的 Accept-Encoding 字段声明了客户端支持的压缩类型,响应报文中的 Content-Encoding 字段声明了响应内容的压缩类型。压缩内容有助于提升传输速率,但是压缩/解压的操作需要计算机处理,因此会消耗更多的 CPU 资源。

常见的压缩方式:

  • gzip(GNU zip)
  • compress(UNIX 系统的标准压缩)
  • deflate(zlib)
  • identity(原样展示,不压缩)

控制会话

HTTP 是无状态、有会话的,其中有会话体现在 HTTP Cookies。

服务端可以通过设置响应报文的 Set-Cookie 向客户端发送 Cookie。

Set-Cookie 的语法为:

Set-Cookie: <cookie-name>=<cookie-value>; Expires=<date>

除了 Cookie 的名称和值外,还支持以下可选参数:

  • Expires=<date>:设置 Cookie 在指定日期前后效;
  • Max-Age=<秒数>:设置 Cookie 在指定秒数内有效,如果 `Expires 和 Max-Age 同时存在,那么 Max-Age 优先级更高,Expires 将被忽略;如果 Expires 和 Max-Age 均不设置,将作为会话期 Cookie,在浏览器关闭时被移除。

浏览器通常支持会话恢复功能。

  • Domain=<主机名>:或不设置,默认值为当前文档的主机名;
  • Path=<路径>:仅在某路径下才会被发送到服务器;
  • Secure:只有 SSL 和 HTTPS 协议的时候才会被发送到服务器;
  • HttpOnly:设置了 HttpOnly 属性的 Cookie 不能被 JS 访问;

注:名称中包含 __Secure- 或者 __Host- 前缀的 Cookie,只可以在 HTTPS 的域中,需要同时设置 Secure。

另外,假如 Cookie 以 __Host- 为前缀,那么 Path 属性的值必须为 /,且不能含有 Domain 属性。

控制缓存

可以参考我之前的文字HTTP的缓存机制。

(完)