大前端开发人员必知必会的HTTP知识

2,675 阅读16分钟

一、TCP/IP

1.TCP/IP协议族

计算机与网络设备进行通信,双方就必须基于相同的方法。比如,如何找到通信目标,采用哪种语言通信,如何结束通信等。这些都需要规则约束,我们把这种规则称之为协议。

有人认为TCP/IP是指TCP和IP协议。其实,这是片面的。在特定场景下TCP/IP的确指这两种协议。更多情况下,它指基于IP进行通信的协议族。因此通常又称TCP/IP协议族。我们今天要介绍的HTTP就是属于它的一个子集。

2.TCP/IP分层

TCP/IP采用四层分层模型,自上而下分别为应用层、传输层、网络层、数据链路层。

模型 功能 协议族
应用层 为用户提供应用服务通信。例如:HTTP用于浏览;FTP用于文件传输;POP3用于接收邮件;SMTP用于发送邮件。 HTTP FTP POP3 SMTP SSH等
传输层 传输层为应用层提供数据传输服务。 TCP UDP
网络层 处理在网络上流动的数据包,为数据包选择路由。 IP等
数据链路层 在硬件媒体上传输数据。

3. 数据处理流程

我们以浏览器浏览网页为例来说明:

应用层浏览器发送HTTP请求,生成HTTP请求数据。

TCP负责连接、发送数据、断开连接。TCP协议保证了HTTP数据包到达接受端的可靠性。为了实现这一功能,需要将HTTP数据分割,并为各个报文添加一个TCP首部。

IP协议,将TCP层传过来的数据作为自己的数据,并在自己数据前端加上IP首部然后转发给数据链路层。值得一提的是,在IP首部指定了接受端的MAC地址。

接收端在数据链路层接收到数据,依次往上层传递,直到应用层。至此,接收端接收到了发送端的HTTP数据。

4.DNS协议

DNS是Domain Name System(域名解析服务)的缩写,主要负责将域名解析为对应的IP地址。它与HTTP协议一样位于应用层。

对于用户来说使用一串有意义的字符去访问某台计算机是更容易接受的。例如,用www.baidu.com访问百度。将用户容易理解的域名解析为网络传输需要的IP,这就是DNS存在的意义。

二、HTTP报文

HTTP之间数据交换的数据信息被称之为报文。HTTP报文有两种:请求报文响应报文请求报文指从客户端向服务端发送的报文。响应报文指从服务端响应客户端的报文。

这两种报文都是由 首行报文首部报文主体三部分组成的。

请求报文

POST /i HTTP/1.1
Host: count.typora.io
Accept: */*
Content-Type: application/x-www-form-urlencoded
Connection: keep-alive
Cookie: __cfduid=d345ac732c82bada5e1f1b3ec7f42498e1550563241
Accept-Language: zh-cn
Content-Length: 206
Accept-Encoding: br, gzip, deflate
User-Agent: Typora/1355 CFNetwork/975.0.3 Darwin/18.2.0 (x86_64)

app_key=3162bc659f38963b8f15099e19551

响应报文

HTTP/1.1 200 OK
Server: nginx
Date: Mon, 11 Mar 2019 03:28:05 GMT
Content-Type: application/json; charset=utf-8
Vary: Accept-Encoding
Access-Control-Allow-Origin: *
X-Frame-Options: deny
X-XSS-Protection: 1; mode=block
Content-Encoding: gzip
Transfer-Encoding: chunked
Connection: Keep-alive

{"result":"Success"}

1.首行

1.1 请求报文首行

POST /i HTTP/1.1 是请求报文的首行。其中 POST 代表请求方法。 /i 表示请求的资源URI。HTTP/1.1 表示HTTP版本号。

请求方法:告知服务器请求意图,期望服务器产生某种行为。

目前常见的方法有:

方法 说明 HTTP协议版本
GET 请求已被URI识别的资源 1.0、1.1
POST 传输实体的主体 1.0、1.1
PUT 传输文件 1.0、1.1
HEAD HEAD和GET方法一样,只是不返回响应报文的主体部分 1.0、1.1
DELETE 删除资源 1.0、1.1
OPTIONS 询问支持的方法 1.1

除了上述几种方法外,还有TRACE、CONNECT、LINK、UNLINK等。

通常我们只会用到GET、POST方法。GET用来请求访问已被URI识别的资源,指定的资源经服务器解析后返回响应结果。POST用来传输实体的主体。虽然GET也可以用来传输实体的主体,但是一般我们不用,而是用POST。

PUT用来传输文件,但是由于HTTP/1.1的PUT方法不带验证机制,存在安全隐患,因此很少用到。

DETETE和PUT存在一样的问题,因此也很少用到。但是如果服务器采用了REST风格的设计,PUT和DELETE将会被应用到。

HEAD和GET方法一样,只是不返回响应报文的主体部分。用来确认URI的有效性和资源更新的日期时间等。例如,客户端需要一个很大的文件(几百兆),如果每次都从网络上加载文件会造成很大的开销。为了解决这个问题,客户端可以在首次GET文件后进行缓存,以后只需要HEAD请求验证文件是否更新,只有当文件更新后才需要重新GET。

URI:资源URI

HTTP版本:目前有 HTTP/1.0、HTTP/1.1、HTTP/2.0。HTTP/1.1版本使用最为广泛。

1.2 响应报文首行

HTTP/1.1 200 OK 是响应报文的首行。其中HTTP/1.1已经介绍过了,与请求报文首行中的HTTP版本号相同。200表示返回结果的状态码。OK表示原因短语。

状态码

状态码的职责是当客户端向服务端发送请求时,描述返回的请求结果。

状态码 说明
1xx 表示接受的请求正在处理
2xx 表示成功
3xx 表示重定向
4xx 表示客户端错误
5xx 表示服务器错误

2.报文首部

Host: count.typora.io
Accept: */*
Content-Type: application/x-www-form-urlencoded
Connection: keep-alive
Cookie: __cfduid=d345ac732c82bada5e1f1b3ec7f42498e1550563241
Accept-Language: zh-cn
Content-Length: 206
Accept-Encoding: br, gzip, deflate
User-Agent: Typora/1355 CFNetwork/975.0.3 Darwin/18.2.0 (x86_64)

报文的首部字段表示请求的各种条件和属性,它能起到传递额外重要信息的作用。根据其用途的不同可以分为以下四种:通用首部字段请求首部字段响应首部字段实体首部字段

  • 通用首部字段

    请求报文和响应报文都会用到的首部。

  • 请求首部字段

    请求报文用到的首部。

  • 响应首部字段

    响应报文用到的首部。

  • 实体首部字段

    针对请求报文和响应报文的实体部分使用的首部。

HTTP/1.1定义了47种首部字段。我们当然不会全部介绍,因为比较常用的也就那么几种。现在,我会列出一个表格,将HTTP的特性和这些常用的首部字段对应上。当然,随后我们会详细的介绍这些特性和首部字段,但是现在,这仅仅是一个列表。

HTTP特性 首部 首部类型 描述
无状态 Set-Cookie 响应首部 服务器下发给客户端的Cookie信息,表示服务器已经记下的客户端的身份信息
Cookie 请求首部 客户端请求服务器时携带的Cookie信息,表明自己的身份信息
持久连接 Connection 通用首部 管理连接状态
缓存 Cache-Control 通用首部 操作缓存的工作机制
Last-Modified 实体首部 资源最终修改时间(服务器时间)
If-Modified-Since 请求首部 其值是一个时间。告知服务器若If-Modified-Since字段值早于资源更新时间,处理该请求
ETag 响应首部 当前资源的唯一标识。资源改变,ETag也会改变
If-None-Match 请求首部 与ETag结合使用,告知服务器若If-None-Match值(ETag)和资源ETag不一致,处理该请求

3.实体

这个好像没什么好说的 过掉它吧!!!

三、HTTP特性

1.无状态

无状态 Set-Cookie 响应首部 服务器下发给客户端的Cookie信息,表示服务器已经记下的客户端的身份信息
Cookie 请求首部 客户端请求服务器时携带的Cookie信息,表明自己的身份信息

HTTP 是一种无状态协议,即HTTP不会管理之前客户端与服务器的通信状态。由于不用保存通信状态,那么服务器的CPU以及内存的压力会大大降价。这是HTTP无状态的优点,但它也会给我们带来一些的问题。例如,我们去某购物网站购物,提交订单之前要对我们的身份进行验证(需要我们登录账号)。但是由于HTTP的无状态特性(没有记录登录状态),每次下单都会要求我们重新登录。为了即不破坏HTTP的无状态特性又可以解决类似的问题,HTTP引入了Cookie技术。Cookie技术通过在请求报文和响应报文中加入cookie信息来实现状态的保持。在响应报文中,服务器可以通过set-cookie首部告诉客户端Cookie信息。客户端再次请求时会通过Cookie首部携带上Cookie信息。

2.持久连接

持久连接 Connection 通用首部 管理连接状态

前面我们在TCP/IP中介绍过,HTTP依赖于TCP进行数据传输。如果你了解过TCP,你应该知道TCP是一种稳定的长连接,即如果没有一方明确的提出过断开连接,那么连接将一直持续。在早些年的HTTP协议中,每进行一次HTTP通信,都会有一次TCP的连接与断开。这与TCP的长连接特性相违背。因此,在HTTP/1.1和部分HTTP/1.0中增加了持久连接HTTP persistent connection,也称作HTTP keep-aliveHTTP connection reuse),使用同一个TCP连接来发送和接受多个HTTP通信,而不是为每一次通信都打开新的连接。HTTP就是通过Connection首部来管理持久连接的。

Connection的值 描述
Connection:keep-alive HTTP/1.1默认是keep-alive,表示该连接是持久连接
Connection:close 断开连接

3.缓存机制

缓存 Cache-Control 通用首部 操作缓存的工作机制
Last-Modified 实体首部 资源最终修改时间(服务器时间)
If-Modified-Since 请求首部 其值是一个时间。告知服务器若If-Modified-Since字段值早于资源更新时间,处理该请求
ETag 响应首部 当前资源的唯一标识。资源改变,ETag也会改变
If-None-Match 请求首部 与ETag结合使用,告知服务器若If-None-Match值(ETag)和资源ETag不一致,处理该请求

HTTP允许客户端在一次URL请求完成后将响应结果存储到本地。下次向该URL请求资源时,客户端会直接从本地存储中获取到该URL的资源。这就是HTTP的缓存机制。合理的利用缓存机制,既可以为我们节省网络资源,又可以加快请求反馈。

引入了缓存机制后,HTTP请求会变的稍微复杂一点。

  1. 当我们初次访问一个URL时,服务器返回请求资源并同时告诉客户端对资源进行缓存、以及缓存过期时间。
  2. 再次访问该URL时,客户端会根据URI找到对应的缓存,并检查缓存是否有效(当前时间小于缓存的过期时间)。
  3. 若缓存有效,客户端不会去访问服务器,而是直接从缓存中获取资源,这个过程我们称为缓存命中
  4. 若缓存无效(当前时间大于缓存的过期时间),客户端会去向服务器验证缓存是否有效。
    • 有效,服务器仅返回代表缓存有效的首部信息,客户端更新缓存过期时间,同时从缓存中取得资源。
    • 无效,服务器返回新的资源,客户端更新资源。

对HTTP的缓存流程有了一定的了解之后,我们就可以继续探讨缓存的具体实现了。

第一步中,我们知道服务器会告诉客户端缓存的过期时间,那么这个时间是怎么确定的呢?这是由服务器在响应报文中加入Cache-Control:max-age来实现的。max-age后面会跟一个相对时间,意思是缓存的有效时间。例如,Cache-Control:max-age=60表示缓存在60秒内有效。需要注意的是,Cache-Control是HTTP/1.1才定义的字段,在HTTP/1.0的版本中过期时间是通过Expire字段实现的。Exprie有一个缺陷,它返回的到期时间是服务器的绝对时间,这就导致如果客户端时间和服务器时间不一致,那么缓存的时间不正确。如果我们的头部同时设置了Cache-Control:max-age和Expire,在HTTP/1.1中会优先使用Cache-Control:max-age,而在HTTP/1.0中会直接忽略掉Cache-Control首部。

第二、三步比较简单,没什么要额外说明的。

第四步中,我们知道当缓存失效,客户端会再次向服务器验证。那么客户端是如何向服务器验证的呢?其实,第一步中,服务器除了返回Cache-Control首部信息外,还会返回一个Last-Modified字段,它的值是资源的最近更改时间(服务器时间)。当客户端向服务器验证缓存的有效性时,会在请求首部加上If-Modified-Since,它的值就是Last-Modified的值。服务器根据If-Modified-Since携带的时间就能判断出客户端缓存资源与服务器资源是否一致。如果一致服务器会返回状态码304(Not Modified),此时是不会返回任何实体信息的。如果不一致,服务器返回状态码200(success),同时也会返回资源信息。无论是返回304还是200,客户端都会重新更新缓存(304只更新过期时间,200更新过期时间和资源)。

除了Last-Modified/If-Modified-Since外,还可以用ETag/If-None-Match管理缓存。ETag为每一个资源打上一个唯一标识,资源变化都会导致ETag变化。If-None-Match验证的就是资源的ETag有没有发生变化。其余的逻辑和Last-Modified/If-Modified-Since基本相同,我就不赘述了。

缓存机制极大的提高了HTTP访问效率,但又引入了实时性问题。因此HTTP提供更严格的缓存控制方式:Cache-Control:no-cacheCache-Control:no-store

Cache-Control:no-cache表示客户端不能直接使用本地缓存,必须向服务器验证缓存的有效性。而不是字面意思上的不缓存。

Cache-Control:no-store表示客户端不能对资源进行缓存。

上面说了那么多,其实就是为了引出这张图。

四、HTTPS

在介绍HTTPS之前,我们先了解一下HTTP有哪些缺点:

  1. 通信采用明文,内容可能会被窃听。
  2. 不验证通信方身份,有可能遭遇伪装。
  3. 无法验证报文的完整性,有可能报文已遭篡改。

为了解决以上问题,HTTPS应运而生了。HTTPS并非是一种应用层的新协议。而是在HTTP和TCP之间加入了SSL\TLS。一般,HTTP直接和TCP通信。而采用HTTPS后,HTTP先和SSL\TLS通信,再由SSL\TLS和TCP通信。SSL提供了认证加密等功能。《图解HTTP》一书中对HTTPS的描述是HTTPS=HTTP+加密+认证+完整性保护

1.加密

常用的加密方式有两种:对称加密非对称加密

对称加密是指使用同一把秘钥加密和解密的算法。这种算法的优点是加密速度快,缺点是必须把秘钥发送给对方。可秘钥传输的过程中,我们怎样才能确保它不会被拦截呢?

非对称加密是指需要一对(两个)秘钥加密和解密的算法。这一对秘钥一个是公钥,另一个是私钥。顾名思义,公钥可以随意对外公开,私钥只能自己持有。用公钥加密的密文只有用对应的私钥才能解密,而用私钥加密的密文也只有对应的公钥才能解密。这种加密算法的缺点就是速度慢。

SSL/TLS采用两者结合的混合加密方式。服务器首先采用非对称加密的方式将一个对称秘钥加密后传输给客户端,客户端使用公钥解密出对称秘钥。随后双方采用对称加密通信。这样就可以兼顾对称加密和非对称加密各自的优点。

2.证书

上述的SSL/TLS加密还存在一个问题,那就是无法验证公钥的正确性。比如,服务器在向客户端传输公钥的过程中,公钥已经被篡改了。为了解决这个问题,可以使用数字认证机构和其颁发的公钥认证证书。数字认证机构是一个客户端和服务器都信任的第三方机构,其颁发的公钥认证证书在目前看来是很难被篡改的。我们简单介绍一下证书的工作流程。首先,服务器的运营人员向数字证书认证机构提出公秘证书申请。请求通过后,数字证书认证机构会对公钥做数字签名,并将公钥和数字证书绑定。服务器会将这个证书发送给客户端,接到证书的客户端可以使用数字证书认证机构的公钥对数字签名进行验证。验证通过,则表明该证书可信。

这里需要额外说明的两点,一、客户端(浏览器或操作系统)会内置常用的可信数字证书认证机构的公开秘钥。二、数字证书除了包含公钥外还包含了服务器的身份信息,因此我们也可以用证书来确认对方身份。

3.对称秘钥的生成过程

这个过程,其实非常复杂。我将其简化如下图。