http 协议入门

255 阅读13分钟

状态码

含义备注
100目前一切正常,客户端继续发送内容
204请求成功,但响应报文中不包含具体内容链接
205reset content,也不应该返回响应体204,205 区别
206客户端进行范围请求,服务端已返回请求头中含有 Range 字段时返回的状态码
301永久重定向
302临时重定义
304命中缓存相当于重定向到本地
504网关或代理服务器未从上游服务器中获取到信息

请求头与响应头

这里涉及到多个字段,按要处理的部分分类别记录。基本上,accept 打头的都用于请求头,表示可支持的功能列表;content 打头用于响应头,表示本次响应的数据的具体实现

Age 与 Date

Age 表示对象在代理服务器中存贮的时长,以秒为单位

Date 表示对象创建的日期和时间

Content-Length

本次发送的数据长度,单位字节

如果数据被压缩了,那么 Content-Length 指的是压缩后的大小。因此,为了得到数据的原始长度,必须指定不能压缩,即请求头中添加 ("Accept-Encoding", "identity")

Content-Type

携带数据的 Mime type

除数据格式外,它还额外带有别的信息,比如编码方式等。

content-type: text/html; charset=UTF-8

数据格式 accept 与 Content-Type

数据格式就是 mime type

前者表示能接受的数据类型,后者表示本次携带数据的格式

// 请求头。表示发送方可接收的数据格式
Accept: text/html...
// 响应头。表示本次响应内容是 text/html 格式
Content-Type: text/html

压缩方式 Content-Encoding 与 Accept-Encoding

前者表示本次响应发送的数据采用的压缩算法,后者表示接收方支持的压缩算法

// 请求头,表示本客户端支持以下的压缩算法
Accept-Encoding: gzip, deflate, br A
// 响应头。表示本次响应的数据采用 gzip 进行压缩
Content-Encoding: gzip

语言 Content-Language 与 Accept-Language

前者表示发送方发送数据的语言,后者表示接收方可处理的语言

字符集 Content-Type 与 Accept-Charset

这里发送方重复使用了 Content-Type,并没有定义一个 Content-Charset

前者表示本次发送的数据使用的字符符,后者表示请求方支持的字符集。

Content-Type 的完整示例如下。分号前表示内容类型,分号后表示使用的字符集

Content-Type: text/html; charset=utf-8

Content-Length 与 Transfer-Encoding

前者表示本次发送的数据长度。

对于简单的返回很容易获取到 Content-Length。但在某些情况下,Content-Length 不太容易计算(比如动态生成的内容),这种情况下使用 Transfer-Encoding。

也就是说如果不方便或无法计算出 Content-Length,就在返回头中使用 Transfer-Encoding,并将其值设置为 chunked,这时会将大块资源分成小块依次传输。关于它的讲解可参考该链接

范围请求 Accept-Ranges,Range 及 Content-Range

在断点上传下载时指定范围

Accept-Ranges:用于响应头。值为 none 时表示服务端不支持续传,值为 bytes 表示服务器可以接受范围请求,即可使用续传功能。

Range: 用于请求头,表示本次请求的范围。它的格式固定为 Range: bytes=x-y,表示从 x 请求到 y。如果需要请求多段数据,中间使用逗号隔开,如 0-9, 20-30。针对不同的请求形式,响应头的返回也不一样:

  1. 单段数据,即 Range:bytes=x-y: 此时返回头会带 Content-Range。如 Content-Range: bytes 0-1/2443。其中 0-1 表示请求返回,2443 表示资源总大小。此时的 Content-Length 表示返回内容的大小, Content-Type 表示返回数据的格式
    # 通过 curl 请求 baidu,且带有请求头 Range:bytes=0-1 
    # -i 表示将响应头也打印出来
    # 此时请求方式是 GET。返回的状态码是 206
     ~ curl https://www.baidu.com/ --header "Range: bytes=0-1" -i
     HTTP/1.1 206 Partial Content
     Content-Length: 2
     Content-Range: bytes 0-1/2443
     Content-Type: text/html
    
  2. 多段数据,即 Range:bytes=x-y, a-b: 此时返回参数通过 Content-Type: multipart/byteranges; boundary=RANGE_SEPARATOR 指定响应体的分隔
    // 请求头
    Range: bytes=0-10, 20-40
    
    
    // 响应头。状态码为 206
    HTTP/1.1 206 Partial Content
    // 此时返回 Content-Type,不会返回 Content-Range 了
    Content-Type: multipart/byteranges; boundary=00000000000000000002 
    // 指返回体的大小,不是整个资源的大小
    Content-Length: 231
    
    // 响应体。会通过 Content-Type 中 boundary 进行分割
    --00000000000000000002
    Content-Type: text/html
    Content-Range: bytes 0-10/2443
    
    <!DOCTYPE h
    --00000000000000000002
    Content-Type: text/html
    Content-Range: bytes 20-40/2443
    
    -STATUS OK--><html> <
    --00000000000000000002--
    

缓存

缓存实质上是请求头、响应头中几个特殊字段。

缓存分为强制缓存与协商缓存。前者在缓存命中时不向服务器进行询问,直接使用缓存;后者会先询问,在缓存可用时才使用缓存

  1. 强制缓存优先级高于协商缓存
  2. 缓存应该分为无缓存、强制缓存、协商缓存三种方式,通过使用 cache-control 进行区分
    • 值为 no-store 时,表示不使用缓存
    • 值为 no-cache 时,表示使用协商缓存
    • 除上述两值为,为强制缓存
  3. 使用协商缓存时,必须先将 Cache-control 设置成 no-cache
  4. 缓存有一个 revalidate 过程:缓存在过期后,重新验证的过程

Vary

HTTP 协议中 Vary 的一些研究

跟缓存相关的一个响应头字段。同一份 url 可能会有不同的返回资源。最简单的比如有接收 gzip 压缩,有不接收压缩。因此,资源在缓存时必须根据不同的配置缓存不同的资源。这些配置有可能是上面已经列表过的 Accept 与 Content 等,也有可能是一些别的(比如 UA,Cookie 等),Vary 就是用来存储这些字段的。

Vary 用来列表缓存应根据哪些头信息进行区分。比如 Vary: Accept-Encoding 响应头,明确告知缓存服务器按照 Accept-Encoding 字段的内容,分别缓存不同的版本

cache-control

缓存的总控制字段

  1. no-store 表示不使用缓存,即内容不会被存储到本地
  2. no-cache 使用协商缓存。表示使用缓存,但使用前需要与服务端协商,即由强制缓存改为协商缓存
  3. private 只能被浏览器缓存,代理服务器不能缓存。所谓代理服务器指浏览器与服务器之间的一些服务器
  4. public 表示资源即可被浏览器缓存,又可被代理服务器缓存
  5. max-age:指定强制缓存时资源存活时长,如 Cache-Control: max-age=0。请求头与响应头都可指定,在计算缓存时以两者最小值为准
  6. must-revalidate:缓存过期前直接使用缓存,过期后必须跟服务端验证通过后才可使用。在某些情况下,缓存即使过期了,在协商失败时(服务器挂了),还可以继续使用。这个 must-revalidate 避免了这种情况
  7. only-if-cached:只用于请求头,客户端只希望获得缓存的响应,并且不应该联系原始服务器以查看是否存在新的数据
  8. max-stale:只用于请求头,表示客户端能接收过期的缓存,但过期时间不能超过 max-stale 指定的值
  9. min-refresh:用于请求头,表示客户端希望读取到的缓存能在 min-refresh 内不能过期,如果没有缓存这个值没啥用。有些缓存可能在请求的瞬间没有过期,但其存活时间不足 min-refresh,这个缓存就不满足当前请求,需要执行网络请求。

cache-control 可取多个值,之间使用逗号分隔开即可

cache-control: public, max-age=31536000

Expires

强制缓存

定义资源过期的绝对时间,在该时间内不需要请求服务器可直接使用缓存。它的缺点也很明显:严重依赖客户端本地时间 ,导致缓存过期时间不准确

cache-control + max-age

强制缓存。max-age 的值以秒为单位,表示该资源在被请求后多长时间内有效

由于 max-age 并没有直接指定资源的绝对过期时间,而是指定资源存活时间,所以它不依赖于客户端的时间准确性。

另外,max-age 优先级高于 expires

last-modified 与 if-modified-since

协商缓存。last-modified 用于响应头,表示资源的上次修改时间。if-modified-since 用于请求头,是上次响应头给的 last-modified。

服务器应先读取 if-modified-since,并和本地资源修改时间进行比较。如果两个时间一致,应该返回状态码 304,同时不返回文件

缺点:由于只是根据资源修改时间判断资源是否修改过,所以会存在 ABA 问题。即资源名由甲改为乙,再改回甲,资源的修改时间也会更新,导致缓存失效。但实际上资源内容并没有修改,理论上可以使用缓存。

另外,比对的时间单位是秒,如果修改过快,在毫秒级别,那即使资源修改了,也判断不出来

etag 与 if-none-match

协商缓存。etag 响应头,服务器生成的资源的哈希值。if-none-match 请求头,表示上次请求返回的 etag

etag 的生成会消耗一定性能,但不会出现 ABA 问题

协议版本

1.0

每一个请求单独使用一个 TCP 连接,有多少请求就有多少三次握手,四次挥手

  1. 连接无法复用。建立一个 tcp 连接很消耗资源,一个 http 请求就新建一个 tcp 连接很浪费。
  2. 队头阻塞(前一个请求完成之后第二个请求才可以执行)

1.1

复用 tcp 连接

一个 http 请求后并不立即断开 tcp 连接,会保留一段时间。在连接有效期内,后续的 http 请求都会使用同一个 tcp 连接。

通过在请求头响应头中添加 Connection: Keep-Alive 实现。

缺点:http 请求是串行的,跟 h1 一样。即一个请求结束才发起另一个,只不过后者复用前者的 tcp 连接,所以依旧会出现队头阻塞

2.0

主要特性有:

  1. 头部压缩具体细节参考。简单说,有一个静态表(static table),里面存储一些常用的健值对,能匹配上时就传对应的下标;同时维护一个动态表,该表根据每次请求的 header 进行添加、修改,如果后续的 header 在表中,依旧只发送下标。

  2. 二进制分帧。帧指的是连接中传输的最小数据单元。一个完整的 http 请求(包括请求行、头、体)会被拆分成不同的片段,每一个片段加上一些别的信息就构成了一个帧。而 h2 就是将不同的帧传输给对方。接收方接收到不同的帧后会按照帧中的 streamId 将若干个帧还原成一个 http 请求。

    同时,与 h1 不同的是,h1 是文本传输,而 h2 将帧进行二进制编码,传输的是二进制数据。所以叫二进制分帧:传输的数据经过二进制编码,传输的最小粒度是帧,每一帧携带有若干请求信息

    常用的帧有:head frame,携带请求头信息;data frame 携带请求体信息。

  3. 多路复用:HTTP 1.1 中,一次链接成功后只要该链接还没断开就可以发送多个 http 请求,但这些请求是串行的,也就是说同一时间只有一个请求运行在网络中。而 h2 可以同时跑多个,这就是多路复用。多路复用的实现就是利用了二进制分帧技术。

https

非对称加密

https 内部使用了非对称加密,对非对称加密来说,公钥加密,私钥可以解密。私钥加密,公钥也可以解密。公钥与私钥是可以相互解密的,并不是私钥加密后就没办法解密。

数字证书

https 中,公钥并不是直接在网络上传输的,而是以数字证书的方式发送给对方。

假设公钥是直接传输的,那么就无法避免中间人攻击。服务端发送给客户端的公钥在传输过程中被四甲拦截,然后甲将公钥换成自己的。客户端收到公钥后,完全无法验证这个公钥是不是自己要请求的服务端的。

为了解决上述问题,引入了数字证书,因此数字证书的作用是:保证公钥是证书的中的持有者的

证书的颁发机构叫做 CA,CA 也有一套自己的公私钥,且公钥一般内置在操作系统中,或者可由操作系统中内置的公钥验证它的正确性。这里就假定 CA 是正确的。

用户将身份信息和公钥提交给 ca,ca 用自己的私钥对用户的公钥和身份信息等信息进行签名,并将公钥、身份信息、签名等信息混合生成一个文件,该文件就是数字证书。客户端拿到证书之后首先使用 ca 的公钥解密,然后验证解密后的信息是否与证书中所携带的信息一致。如果一致就认为证书是合法的。

在生成证书的过程中,ca 使用自己的私钥进行签名,客户端使用 ca 的公钥解密。这里利用了非对称加密的身份验证功能。如果客户端能解密,说明使用 ca 的私钥进行加密的,而私钥只可能由 ca 自己拥有,所以可以证明该证书来源于 ca,且没有被修改。

数字证书也有自己的格式。常用的如 X.509

tls/ssl 握手过程

https 在 http 与 tcp 之间加了 tls/ssl 层,这一层用于对 http 层下发的明文加密。tsl 的过程如下:

  1. 客户端向服务端发 client hello 消息,里面包一个随机数1 ,tls 版本以及加密套件。加密套件指的是:将对称加密、非对称加密、摘要算法等算法组成一个整体,如下图中的 Cipher Suite: TLS_AES_128_GCM_SHA256 (0x1301),一旦选定某个套件后后续的对称加密、非对称加密、摘要等算法便确定了。

    25B3DA46-4DF2-4415-8B4B-B4D427B32D0C.jpg

  2. 服务端回一个 server hello:商量好 tls 版本,随机数2,证书,以及选择一个加密套件

  3. 客户端收到证书后,会先验证证书是否安全。如果安全,会生成另一个随机数3(还有一个名字:预主密钥 premaster secret),同时使用公钥进行加密后发送给服务端

  4. 服务端收到后,使用自己的私钥解密,得到随机数 3。到目前为止,两端都拥有了三个随机数

  5. 两端使用约定好的算法,使用三个随机数生成相同的 key。这个 key 就是后续对称加密使用的密钥

  6. 随后两端使用生成的 key 对称加密 FINISHED 发送给对方。发送完后 tls 握手过程就结束了。