写点什么

在公司内部,做了一次 HTTP(S) 的分享

作者:程序员小毕
  • 2022 年 8 月 25 日
    湖南
  • 本文字数:11835 字

    阅读完需:约 39 分钟

在公司内部,做了一次 HTTP(S) 的分享

什么是 HTTP?

HTTP 是什么,又不是什么?

HTTP 的全称是 Hypertext Transfer Protocol,也就是超文本、传输、协议。我们从后往前解释~

  1. 协议。什么是协议,我们可以联想我们的租房协议、三方协议,其实都是一样的含义,协议的“协”,代表有两个以上的参与方,协议的“议”呢,代表约定和规范,约定你可以做什么、不能做什么。

  2. 传输。然后是传输,我们可以联想快递,专门在两点之间传输,其关键有两点。第一点是双向性,我们可以寄快递,也可以收快递;第二点是传输过程可以有中转,比如我们寄快递,会经过快递小哥、快递公司、物流仓库等,最后才会到达收件方,同时这些中间者也都遵守协议。

  3. 超文本。最后关于超文本,顾名思义就是超越了普通文本的文本。这里我想问大家一个问题,超文本除了文字、图片、音频、视频格式,还有一个最关键的格式,是什么?对了,就是超链接。超链接能够让我们从一个“超文本”跳跃到另一个“超文本”,让我们的文本从以前的线性结构,变成了非线性的网状结构。

一句话总结就是:“HTTP 是一个在计算机世界里专门在两点之间传输文字、图片、音频、视频等超文本数据的约定和规范”。

这张图是我们基本的 HTTP 通信过程中的参与者们。从这张图,我们可以捋清 HTTP 它不是什么,从而对 HTTP 有一个更清晰的认识。

  • HTTP 不是实体,比如左边的 Web 浏览器(发送方),右边的 Web 服务器(接收方)。

  • HTTP 不是互联网,HTTP 传输的超文本资源是互联网资源中的一部分。

  • HTTP 不是一门编程语言,但是 HTTP 支持各种编程语言来实现。

  • HTTP 不是 HTML,HTTP 可以传输 HTML,HTML 是超文本的常见格式。

  • HTTP 不是一个孤立的协议。通常在 HTTP 的下方,会有一些底层协议支持,比如 TCP、IP、DNS 等等;在 HTTP 的上方,也有一些依赖于 HTTP 的协议,比如 WebSocket、HTTPDNS 等等。这些协议相互交织,构成了一个协议网,而 HTTP 则处于中心地位。

HTTP 世界全览

我们再对 HTTP 世界的全貌进行一下浏览,主要分为应用相关和理论相关。有了对 HTTP 通信链路更宏观的认识,我们在定位问题的时候,能够更清楚问题可能是由哪个环节导致的。

HTTP 相关应用

我们从右往左看:

  • Internet - WWW:Internet 就是互联网,里面存储着各种信息资源。WWW 是互联网的一个子集,简称万维网,它基于 HTTP,所以存储的都是超文本资源,这些资源在互联网中的占比大约在 90%。

  • Web Browser:Web 浏览器,是 HTTP 通信过程中的请求方,并可以显示请求到的资源。

  • Web Server:Web 服务器,是 HTTP 通信过程中的响应方,由它来管理网络资源。一般会将它分为硬件和软件,硬件指物理服务器、云服务器之类的机器,软件指 Nginx、Apache 之类的应用程序。

  • CDN:Content Delivery Network,也就是内容分发网络。前面说到 HTTP 的传输过程可以有中转,这里 CDN 的定位就是中转方,起到网络代理的作用。它可以缓存服务器的资源,加快网络响应,还可以提供负载均衡、安全防护等能力。

  • Crawler:就是爬虫。和 Web 浏览器类似,它也可以理解为是一种用户代理,一般是给各大搜索引擎自动抓取数据、存入数据库用的。

  • Others: 其它还有 HTML、Web 服务、WAF。Web 服务可以理解为运行在 Web 服务器上的具体服务或者服务开发规范。WAF 的全称是网络应用防火墙,它其实也是一种代理。

HTTP 相关理论

同样从右往左看,右边的 HTTP/1.1、HTTPS、HTTP/2、HTTP/3 就是今天我们要聊的主要协议,左边:

  • TCP/IP:它代表的其实是一个协议栈,里面包含了很多网络通信协议。

这里我们把基于 TCP/IP 的 HTTP 通信过程与快递运输又做一个类比:

1)超文本 => MAC:左图中左边这一列,要传输的超文本从应用层一直到链接层,每经过一层都会被加上对应的头,比如 HTTP 头、TCP 头、IP 头、MAC 头,这就像快递的打包过程;

2)MAC => 超文本:左图中右边这一列,被传输的数据每经过一层则都会被去除一个头,这就像快递的拆包过程。


  • URI - URL:URI(I - Identifier)是统一资源标识符,它又分为 URL 和 URN 两种形式,但因为后者在互联网世界并不常用,所以 URI 一般指的就是 URL。

1)URL(L - Locator):我们浏览器上方的地址就是 URL。

它的基本组成如上图,我们可以先关注红框部分:

  1. scheme:最左边的 scheme 代表协议,如 http、https、ftp 等等。注意这里协议后面紧跟的://符号是固定的、必须的。

  2. host:中间 host 是主机名,也叫域名,待会讲 DNS 的时候再细说。

  3. path:最后边的 path 代表资源路径。

Q:这里有一个问题,图中示例 URL 的域名是www.creatorseo.com/吗?

答案是否定的,最后面的斜杠/属于 path,它代表的是所访问主机的根目录,因为早期互联网上的计算机大多是 UNIX 系统,所以这里的路径格式是采用的是 UNIX 上的文件路径风格。

下面还有一张图,这张图是 URL 的完整格式

比上图还多了三个组成:

  1. user:passwd@:我们可以在 URL 里就填上用户密码信息,但因为安全原因已经不推荐使用它了。

  2. ?query:这个部分可以附加一些对资源的额外要求,以?开始,由多个键值对k=v组成,每个键值对用&连接。

  3. ##fragment:它代表一个片段标识,我们可以理解为资源内的一个锚,它是给客户端使用的,不会发送给服务器。平时我们在看一些博客时(如阅读原文跳转到我的博客网页),点击悬浮目录中的某个标题做跳转,就是用的这个部分。

💡 这里还有两个小提醒~一个是 host 后面还可以通过:port指定端口。另一个就是一般聊到 URL 时,还会聊到 escape 转义encode 编码两个概念,因为如果没有它们,服务器可能就无法正确地处理 URL。试着想一想如果 path 里也有?符号,服务器该怎么解析出 query 的起始位置呢?

  1. escape - 转义:针对特殊字符,我们一般会做转义,直接将其转换成 ASCII 码的十六进制后再加一个%前缀,比如SPACE对应%20?对应%3F

  2. encode - 编码:针对汉字等其它语言,我们一般会先进行 UTF-8 编码,再转义。如果你不信,可以试试把包含中文的 URL 复制粘贴到微信中(如阅读原文跳转到我的博客网页)。

2)URN(N - Name):下面再回到 URI 的第二种形式 URN,它是通过命名空间加具体标识符的形式来标记资源,如urn:<NAMESPACE-IDENTIFIER>:<NAMESPACE-SPECIFIC-STRING>。它在我们上网时不常用,但如果你去买书,可能会发现每本书的条形码位置有一串字符,如ISBN xxx-x-xx-xxxx,这其实就是 URN 的一种用法。



  • DNS:全称 Domain Name System,即域名系统,它是用来做域名解析的应用层协议,也就是将域名转换为 IP 地址。

我们先来看看域名的结构,还是这张图,这里红框部分就是域名,它是一个有层次的结构,用.分隔,越靠右边,层级越高。如从右到左,分别为顶级域名、二级域名、三级域名等等。

我们再来看看 DNS 的类型和 DNS 解析域名的步骤,如下图:

1)DNS 类型。DNS 分为根 DNS、顶级 DNS、权威 DNS 和非权威 DNS。根 DNS 共有 13 组,遍布全球,它可以根据请求的顶级域名将 DNS 解析指定给下面对应的顶级 DNS。顶级 DNS 又根据二级域名指定权威 DNS,直到解析出域名对应的 IP。而一些大公司还会自建 DNS,又叫非权威 DNS,它们的分布更广,比较知名的有 Google 的 8.8.8.8,Microsoft 的 4.2.2.1,还有 CloudFlare 的 1.1.1.1 等等。

2)DNS 解析域名步骤。实际的解析过程分为 4 步:系统首先会找 DNS 缓存,可能是浏览器里的,也可能是系统里的;如果找不到,再去查看 hosts 文件,里面有我们自定义的域名-IP 对应规则,Mac 下的 hosts 文件路径为/etc/hosts;如果匹配不到,再去问非权威 DNS,一般默认是走我们网络运营商指定的;如果还是没解析出来,就要走根 DNS 的解析流程喽~

💡 这里还有一些域名解析相关的常用命令(dig、host、nslookup),如果你感兴趣,可以去终端试一试~

1. DNS addressing process: dig www.baidu.com +trace @8.8.8.82. domain name <=> IP: host www.baidu.com3. domain => IP: nslookup www.baidu.com
复制代码

如果你知道用 WireShark,还可以通过filter: port 53过滤出 DNS 解析相关的抓包。


  • Proxy:就是代理。代理一般分为正向代理和反向代理,正向代理靠近客户端,反向代理靠近服务端。刚刚我们提到的 CDN 属于反向代理,而我们访问外网用的 VPN 就属于正向代理。

HTTP 报文

铺垫了这么多(实际上也是值得的),终于到了 HTTP 最重要的部分!

所谓 HTTP,超文本传输协议,其中最重要的部分其实是最后的协议,里面约定了 HTTP 报文的格式和用法。

基本格式

我们先来看看 HTTP 报文的基本格式,它可以简单分为头部身体两部分:

1)头部:一般可以包含起始行部分,也就是头部由 Start line 和 Header 组成。下图展示了一次请求中,请求头和返回头的结构:

  1. 请求头


  2. Start line 由请求方法、URI、HTTP 版本、空格间隔符以及最后的换行符组成。

    Header 由一个个的key:value键值对和最后的换行符组成,注意这里:前不能有多余的空格,不信你用telnet命令试试(Mac 上可以使用brew install telnet来安装telnet,并且推荐搭配极客时间提供的实战仓库 chronolaw/http_study[8]来用)。

  3. 返回头


  4. Start line 由 HTTP 版本、状态码、状态码对应解释、空格间隔符以及最后的换行符组成。

    Header 结构和请求头一样。

2)身体:一般根据业务来约定 body 的具体内容,它是可有可无的。


下面我们再来看看请求行中的请求方法和状态码具体有哪些~

请求方法

HTTP/1.1 里规定了八种请求方法,这里把它们分成了常用和不常用两类,另外还有一类是扩展的请求方法,注意这些方法都必须是大写的形式。

这里主要介绍下常用的请求方法:

  1. GET 和 HEAD,用来获取服务器资源。两者的区别在于 HEAD 只会获取到头部信息,GET 会获取到完整的头部和身体信息。所以如果你只是想确认某个资源存不存在或者只需要头部信息,可以用 HEAD 请求,从而减少传输量。

  2. POST 和 PUT,用来发送资源给服务器。两者的区别在于前者是在服务器创建资源,类似数据库的 CREATE 操作,后者是修改服务器的资源,类似 UPDATE 操作。两者比较类似,实际应用中 PUT 使用较少。

💡 说到请求方法,一般还会提到安全和幂等两个概念:

  1. 安全:指的是对服务器资源不会有实质的修改,所以上面提到的 GET 和 HEAD 是安全的。

  2. 幂等:指的是相同的操作执行多次后,结果是否相同,所以上面提到的 GET、HEAD 和 PUT 都是幂等的。

返回状态码(5 类)

这次到了我们的第一个目标:🔍 通过状态码快速定位 HTTP 问题。

状态码一般分为 5 大类

1xx:提示信息类。一般是一次请求的中间状态,比较少见。

2xx:成功类。这说明请求是符合预期的,也是我们最愿意看到的。

3xx:重定向类。资源发生了变动,需要客户端向另一个域名重发请求。

4xx:客户端错误。看到这个就要想想请求报文是不是填错了。

5xx:服务端错误。看到这个就要找服务端的同学确认问题原因了。

常见的具体状态码可参考下图:

  • 301:永久重定向,可以把请求的 URL 改一下了。

  • 302:临时重定向。

  • 304:服务器资源没有变,所以重定向到了本地缓存。

  • 401:未认证错误,一般与鉴权、登陆相关。

  • 403:访问被拒绝,可能访问到了敏感信息。

  • 404:资源没找到,可能资源路径写错了,也可能是没有权限访问(错误码是服务端自定义设置的,404 一般用于表示资源没找到,但也可以延伸其用途,隐蔽具体原因)。

  • 405:请求方法不被允许。

  • 502:网关或代理返回的错误码,一般是访问网关或代理背后的服务器出错。

  • 503:服务器暂时不可用,请稍后重试。

💡 对于 400、500,它们是比较笼统的错误码,有时候是作为兜底错误码返回的,说明发生了未知的错误;有时候是因为服务端不想暴露过多的细节返回的。总之服务端在尽可能遵守公共认知的情况下,是可以自定义状态码的。

常见头字段(8 种)

很快,我们又到了本文的第二个目标:🥣 熟悉 HTTP 报文里的常见头字段。

首先,我们把头字段分为 Request、Response、Universal 三大类,Universal 类里又包含 Entity 子类。Request 类头字段是请求方用的,Response 类头字段是响应方用的,Universal 类头字段是请求方和响应方都可以用的,Entity 类头字段一般是用来描述 body 属性的。

上图列出了常见的头字段们,看着眼花缭乱,别急,我们分功能来解释。补充:填充色相同的头字段们一般是搭配着使用的或者相关的。

下面,我们将头字段们按照功能主要分为 8 种来解释。


1)Body:body 属性相关,可以是描述请求报文中的,也可以是描述响应报文中的。

提到 body 的类型,我们首先需要了解什么是 MIME(Multipurpose Internet Mail Extensions,多用途互联网邮件扩展)类型,它是从电子邮件系统中诞生的,现在也被用来描述 body 的类型。这里有 MIME 类型汇总[9],你点开链接看一下就会觉得它们很眼熟,比如application/jsontext/htmltext/javascript……它的前半部分是一个大的类别,后半部分是具体的格式。

  • Accept表示的是请求方可以接受的 body 类型,可能不止一个。

  • Content-Type表示的是实际传输的 body 类型。

为了减少 body 的大小,我们一般会对它进行压缩,常见的压缩格式有 gzip、deflate 和 br,它们对 text 的压缩效果很好。

  • Accept-Encoding表示的是请求方可以支持的压缩格式,可能不止一个。

  • Content-Encoding表示的是传输的 body 实际采用的压缩格式。

因为上面的压缩方式一般只对文本有较好的压缩率,对于压缩效果不好的图片、音视频等多媒体格式,还有一种方式来解决大文件的问题,那就是分块传输。

  • Transfer-Encoding: chunked:这表示数据是被分块传输的。

对于视频类型的 body,比如我们在 b 站上看视频,这个视频不可能是一次就请求下来的,我们可以对视频进行分段请求。

  • Accept-Ranges: bytes:我们一般会通过 HEAD 请求先问问服务端是否支持范围请求,如果支持通过字节范围请求,服务端就会返回这个。

  • Range: bytes=x-y:在服务端支持的情况下,请求方就可以明确要请求第 x~y 字节的内容。

  • Content-Range: bytes x-y/length:这表示服务端返回的 body 是第 x~y 字节的内容,内容总长度为 length。

在国际化方面,我们还可以设置对语言的要求。

  • Accept-Language表示的是请求方可以理解的语言,可能不止一个。

  • Content-Language表示的是传输的 body 实际的语言。

这里还要举一个具体的例子:Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7,有两个细节我们要注意:

  1. 在 HTTP 规范里,,的优先级大于;,这和我们一般的编程语言语法相反,所以上面的en;q=0.9是一对。

  2. 上面的q是什么?它其实代表一个权重,默认是 1,响应方会尽可能使用权重最大的语言返回内容。


2)Connection:长连接相关。

在 HTTP/1.1 之前,客户端每次和服务端通信都需要重新建立连接,如果频繁通信,就会不断地重复建立和关闭 TCP 连接,如下图左边所示,即短连接:

所以要是可以让一次 TCP 连接保持久一点,每次连接两端多通信几次就好了,也就是图中右边所示,即长连接。这不,HTTP/1.1 就支持了。

  • Connection: keep-alive:即表示使用长连接,在 HTTP/1.1 中默认开启。

  • Connection: close:主动关闭长连接,一般是由客户端发出的。

对于服务端,它也可以设置长连接的断开时机,它们是在 Web 服务器中配置的。比如在 Nginx 中,keepalive_timeout代表长连接的超时时间,如果长时间没有数据收发就主动断开连接;keepalive_requests代表长连接过程中最多接收请求多少次。

因为长连接的方式,客户端也可以同时发起多个请求,而不必等第一个请求的结果回来再发第二个请求,这也就是管道通信。

不过不管短连接还是长连接,都还会有一个队头阻塞(HoL blocking)问题,这是因为 HTTP 的“请求-应答”模型规定报文必须是“一发一收”导致的,可以参考下图:

无论如何,接收方都必须先处理完红线请求后,才能处理后面发起的请求,即使后面的请求先到了,也就是“先发必须先处理”。

为了缓解这个问题,有一个办法,就是并发连接,也就是对一个域名发起多个长连接,每个长连接之间互不干扰。但是长连接的保持是需要消耗服务器资源的,而且也可能被恶意攻击,所以规定一般长连接的上限是 6~8 个。如果还不够用,还有一个取巧的方式,就是域名分片,同一台服务器有多个域名对应,那么上限也就翻倍了。


3)Redirection:重定向相关。

在聊状态码时,我们提到过 301(永久)、302(临时),不知道你还记不记得它们的含义。如果返回这种状态码,那么在响应头里一定还会标识重定向的位置。

  • Location就是重定向的位置,一般有绝对路径和相对路径两种形式,绝对路径对应的就是 URL 的基本格式,相对路径则没有 scheme 和 host:port,默认使用原请求的 URL 里的。

和重定向相关的还有 3 种状态码:303 类似 302,但请求方法只能是 GET;307、308 分别类似 302、301(这里是反的……),但它们都不允许重定向后的请求有任何变动。


4)Cookie:解决 HTTP 无状态特点带来的问题。

首先我们要清楚无状态指的是客户端还是服务端?没错,是服务端,也就是服务端不知道收到的这次请求和上次请求有什么关联,那这样会让服务端处理一些特殊场景时方案变得复杂,比如购物。

所以这里 Cookie 就是来解决这个问题的。简单来说,就是服务端给客户端贴了一个小纸条,标识某个客户端的身份,这个客户端在每次请求时带上这个小纸条,就可以证明自己的身份了。

  • Set-Cookie: a=xxxSet-Cookie: b=yyy:这是服务端返回的,一个 Cookie 本质上就是一个键值对,并且每个 Cookie 是分开的。

  • Cookie: a=xxx; b=yyy:这是客户端在发送请求时带上的,也就是之前服务端返回的 Cookie 们,它们是合在一起的。

注意,客户端在收到这些 Cookie 后会将它们保存到客户端里,我们去看看 Chrome 浏览器就可以发现它们。

咦?Cookie 除了 Name 和 Value,怎么还有这么多属性?其实,服务端返回的 Cookie 一般长这样:Set-Cookie: a=xxx; Domain=xx; Path=xx; Max-Age=xx; Expires=xx; HttpOnly; Secure; SameSite=xx...

  1. Domain、Path:只有在客户端请求的 URL 匹配上它们时,这个 Cookie 才会被带上。

  2. Max-Age、Expires:代表 Cookie 的失效时间,后面 Cache 也有类似的属性,要注意的是 Max-Age 的优先级大于 Expires。

  3. HttpOnly:其为真时,代表这个 Cookie 只能通过 HTTP(S)协议传输,禁止其他方式访问,比如在 JS 里就不再可以用 document.cookie 获取它了,以防脚本攻击。

  4. Secure:其为真时,代表这个 Cookie 只有在发起安全的 HTTPS 请求时,才会被带上。

  5. SameSite=xxx:设置 SameSite=Strict 可以严格限定该 Cookie 不能跨站发送;SameSite=Lax 则略宽松一点,允许在 GET/HEAD 等安全的请求里使用该 Cookie。


5)Cache:缓存相关。

缓存真的无处不在,HTTP 请求里也不例外。这里提到的缓存是存放在客户端的,目的就是尽可能地减少网络请求或着返回数据的大小,从而提升网络传输效率。

  • Cache-Control


  • 一般地,cmd + R 刷新页面会带上 max-age=0,意思是只要生存了 0 秒的数据,也就是不走本地缓存了,而是向服务器要一个最新生成的报文;cmd + shift + R 强制刷新页面会带上 no-cache,和前者基本一致,看服务端如何处理。

    那什么时候缓存会生效呢?一般是在浏览器前进、后退或者重定向时,客户端发起的请求就不会带上上面的两个属性了。

    max-age 的单位是秒,从返回那一刻就开始计算;

    no-store 代表客户端不允许缓存;

    no-cache 代表客户端使用缓存前必须先来服务端验证;

    must-revalidate 代表缓存失效后必须验证。

    服务端可以返回的属性有:max-age=10/no-store/no-cache/must-revalidate


    客户端可以发送的属性有:max-age=0no-cache


此外,为了增加缓存控制的灵活性,这里还有一些条件字段~

  • 服务端返回的有:


  • 前者不变的条件是资源在字节级别不变。

    后者不变的条件是资源在语义上不变即可,比如多了几个空格之类的。另外,弱 Etag 的值会在前面加一个W/标记。

    Last-Modified代表文件的最后修改时间。

    ETag全称是 Entity Tag,代表资源的唯一标识,它是为了解决修改时间无法准确区分文件变化的问题。比如一个文件在一秒内修改了很多次,而修改时间的最小单位是秒;又或者一个文件修改了时间属性,但内容没有变化。Etag 还分为强 Etag、弱 Etag:


  • 客户端请求时对应就用:


  • If-Modified-Since里放的就是上次请求服务端返回的 Last-Modified,如果服务端资源没有比这个时间更新的话,服务端就会返回 304,表示客户端用缓存就行。

    If-None-Match里放的就是上次请求服务端返回的 ETag 了,如果服务端资源的 Etag 没变,服务端也是返回 304。


6)Proxy:代理相关。

代理是具有双重身份的,因为在客户端眼里,它是服务端,而在服务端眼里,它又是客户端。

前面也提到了代理一般分为正向代理和反向代理,反向代理一般被用来做负载均衡(合理分配任务,决定由后面的哪台服务器来响应请求)、安全防护、加密卸载(在内网里通信就不加密了,减少加解密成本)、内容缓存(暂存服务器响应,这个下面会说,也就是代理缓存)等等。

在上面有代理服务器的场景中,涉及到的头字段是:

  • Via:代理服务器会在发送请求时,把自己的主机名加端口信息追加到该字段的末尾。

但是服务器一般需要知道客户端的真实 IP 信息,方便做访问控制、用户画像、统计分析等,所以在 HTTP 标准外还约定了下面的头字段:

  • X-Forwarded-For:类似 Via 的追加方式,但追加的内容是请求方的 IP 地址。

  • X-Real-IP:只记录客户端的 IP 地址,它更简洁一点。

不过上面的方式其实有一个很大的缺点:损耗性能!因为每次代理服务器需要解析出 HTTP 报文头,再修改报文数据;而且在有些情况下,报文是不允许甚至是不能(加密)被修改的。所以后来就又出现了一个专门的代理协议,这也是在标准外约定的。

基于这个协议,代理服务器只需要在 HTTP 报文前再加一行文本即可。比如:

PROXY TCP4 1.1.1.1 2.2.2.2 55555 80\r\nGET / HTTP/1.1\r\nHost: www.xxx.com\r\n\r\n
复制代码
  • 开头是PROXY五个大写字母;

  • 然后是客户端的 IP 地址类型,如TCP4或者TCP6

  • 再后面是请求方、应答方的地址,以及请求方、应答方的端口号;

  • 最后用一个回车换行结束。


7)Proxy Cache:代理缓存相关。

客户端可以缓存,中间商代理服务器当然也可以缓存。但因为代理的双重身份性,所以Cache-Control针对代理缓存还增加了一些定制化的属性~

  • 从服务端到代理服务器:


  • private代表数据只能在客户端保存,不能缓存在代理上与别人共享,比如用户的私人数据。

    public代表数据完全开放,谁都可以缓存。

    s-maxage代表缓存在代理服务器上的生存时间。

    no-transform代表禁止代理服务器对数据做一些转换操作,因为有的代理会提前对数据做一些格式转换,方便后面的请求处理。

  • 从客户端到代理服务器:


  • max-stale代表接受缓存过期一段时间。

    min-fresh则与上面相反,代表缓存必须还有一段时间的保质期。

    only-if-cached代表客户端只接受代理缓存。如果代理上没有符合条件的缓存,客户端也不要代理再去请求服务端了。


8)Others

我们再看看这张常见头字段图,你是不是已经清楚每一个头字段的含义和用法了呢~

等等,上面还漏了一些头字段的说明,我把它们统一放在这里补充一下:

  • Host代表要请求的主机名。它在 HTTP/1.1 里是必须出现的,用来给服务器区分具体选择哪个主机来处理请求的(如果计算机上托管了多个虚拟主机就有这个作用,否则服务器一般也不会去处理)。另外,在一般的网络框架里,它会帮我们从 URL 里解析出一个默认的 Host 值兜底,所以可能你没有手动填也没有问题,因为框架默认帮我们补充了。

  • User-Agent即用户代理,它是用来描述请求方的身份的,服务器可以根据它来返回合适的页面布局或者数据。不过由于历史原因,它的用法已经有些混乱了,比如每个浏览器都自称是“Mozilla Chrome Safari”之类的。

  • Date代表报文创建的时间,一般出现在响应头里。

  • Server展示的是提供 Web 服务的软件名称和版本号,但这样暴露了服务器的部分信息,可能存在安全隐患,所以有的时候返回里没有这个字段,或者仅仅是一个模糊的信息。

  • Content-Length代表报文里 body 的长度。如果没有这个字段,那么一般会有另一个字段Transfer-Encoding: chunked,前面我们说到过它。

至此,我们已经讲完了什么是 HTTP 了,可以试着用 Chrome 开发者工具或者 WireShark 抓包加深理解。

什么是 HTTPS?

HTTPS 比 HTTP 多了一个 S,这个 S 代表的是 SSL/TLS 协议。

现在来到了我们的第三个目标:🔐了解基本的加密知识。

这一节因为之前写过相关文章,所以我尽量减少篇幅,你可以点击下面的链接参考:

  1. 信息安全 | 互联网时代,如何建立信任?:三大常见密码学算法,数字签名,数字证书。

  2. 信息安全 | (加餐)互联网时代,如何建立信任?:SSL/TLS,SSH,iOS 签名,OpenSSL、WireShark 实践。


要补充的一个内容是:基于 ECDHE 的 TLS 主流握手方式 VS. 基于 RSA 的 TLS 传统握手方式。

两者的关键区别在于通信密钥生成过程中,第三个随机数Pre-Master的生成方式:

  • 前者:两端先随机生成公私钥,同时公钥(加签名)作为参数传给对方,然后两端基于双方的参数,使用 ECDHE 算法生成Pre-Master

  • 后者:客户端直接生成随机数Pre-Master,然后用服务器证书的公钥加密后发给服务器。

因为前者的公私钥是随机生成的,即使某次私钥泄漏了或者被破解了,也只影响一次通信过程;而后者的公私钥是固定的,只要私钥泄漏或者被破解,那之前所有的通信记录密文都会被破解,因为耐心的黑客一直在长期收集报文,等的就是这一天(据说斯诺登的棱镜门事件就是利用了这一点)。

也就是说,前者“一次一密”,具备前向安全;而后者存在“今日截获,明日破解”的隐患,不具备前向安全

更多的细节可以参看《透视 HTTP 协议》里 TLS1.2 连接过程解析[10]这一课,或者自己用 WireShark 抓包试一试~

两者从抓包上的区别来看,主要是:

  • 前者比后者多了一个“Server Key Exchange”消息。

  • 前者客户端可以不等连接完全建立就提前进行加密通信,也就是客户端不用等服务器发回“Finished”确认握手完毕,这个叫“TLS False Start”。

HTTP 的发展

我们通过下面表格梳理一下 HTTP 的发展过程,今天我们对 HTTP 发展有一个整体的认知就好~

  • 从 HTTP/1.0 开始,HTTP 已经被写入 RFC 文档(RFC 文档汇总[11])。

  • HTTP/1.1 是 HTTP 第一个正式标准,大多数的功能我们在常见头字段那一节介绍了。在这个阶段早期,Google、Sina、Sohu、Netease、Tencent 等公司被创立了,后期 Facebook、Twitter、Taobao、JD 等公司慢慢也出来了。

  • HTTP/1.1 在各方面还比较完善了,但在性能和安全上还存在很大优化空间。所以 HTTP/2、HTTP/3 都主要是针对 HTTP 的性能方面做优化。


  • HTTP/2 基于 Chrome 的 SPDY 协议,它是 Chrome 推动的。主要的变化有:


  • 传输数据格式从文本转成了二进制,大大方便了计算机的解析。

    基于虚拟流的概念,实现了多路复用能力,同时替代了 HTTP/1.1 里的管道功能。

    利用 HPACK 算法进行头部压缩,在之前都只针对 body 做压缩。

    允许服务端新建“流”主动推送消息。比如在浏览器刚请求 HTML 的时候就提前把可能会用到的 JS、CSS 文件发给客户端。

    在安全方面,其实也做了一些强化,加密版本的 HTTP/2 规定其下层的通信协议必须在 TLS1.2 以上(因为之前的版本有很多漏洞),需要支持前向安全和 SNI(Server Name Indication,它是 TLS 的一个扩展协议,在该协议下,在握手过程开始时通过客户端告诉它正在连接的服务器的主机名称),并把几百个弱密码套件给列入“黑名单”了。

PS:相比 HTTP/1.1 中的并发连接方式,虚拟流的概念更优美地解决了 HTTP 的队头阻塞问题。



  • HTTP/3 基于 Chrome 的 QUIC 协议,它也是 Chrome 推动的。


  • 它最大的改变就是把下层的传输层协议从 TCP 换成了 QUIC,完全解决了 TCP 的队头阻塞问题(注意,是 TCP 的,不是 HTTP 的),在弱网环境下表现更好。因为 QUIC 本身就已经支持了加密、流和多路复用等能力,所以 HTTP/3 的工作减轻了很多。

    头部压缩算法从 HPACK 升级为 QPACK。

    2022 年 6 月 6 日,HTTP/3 被正式写入 RFC 文档,同时,HTTP/1.1 和 HTTP/2 也更新了 RFC 文档。

    基于 UDP 实现了可靠传输,引入了类似 HTTP/2 的流概念。

    内含了 TLS1.3,加快了建连速度。

    连接使用“不透明”的连接 ID 来标记两端,而不再通过 IP 地址和端口绑定,从而支持用户无感的连接迁移。

    先看看 QUIC


    回到 HTTP/3


PS:TCP 为了保证可靠传输,有个特别的“丢包重传”机制,即丢失的包必须要等待重新传输确认,其他的包即使已经收到了,也只能放在缓冲区(kernel)里,上层的应用(user)拿不出来,可参考下图:红色方块请求是阻塞 TCP 的关键。

(其实这里有一点疑惑的是,合着 HTTP/3 之前解决的都只是 kernel 到 user 的阻塞问题?具体地说只是经过 TCP 之后的阻塞问题,在这个阶段的阻塞问题有哪些呢🤔?欢迎大佬们答疑解惑~)

HTTPS 的发展

这部分聊聊 TLS1.2 到 TLS1.3 的发展,在此之前的版本因为各种安全问题都已经被废弃了,我们可以从信息安全 | (加餐)互联网时代,如何建立信任?这篇文章里了解到。

对于 TLS1.3 来说,它的主要优化目标有 3 个:

  1. 兼容 TLS1.2。为了保证老设备能够更轻松地升级协议,TLS1.3 保持原有的记录格式不变,利用扩展协议在原有记录末尾增加一些“扩展字段”来增加新的功能,老版本的 TLS 不认识它们可以直接忽略,从而实现了“后向兼容”。

  2. 更安全。TLS1.3 基于安全考虑对支持的密码套件进行了瘦身,最终只剩下 5 个密码套件。前面提到的基于 RSA 的 TLS 传统握手方式就被废除了。

  3. 更高性能。HTTPS 建立连接的过程除了 TCP 握手,还有 TLS 握手,在 TLS1.2 中 TLS 握手需要花费 2-RTT,而这个时间在 TLS1.3 中被优化到了 1-RTT。怎么做的呢?


  4. 答案就在上一点中,因为密码套件只有这么多,TLS1.3 可以索性在 ClientHello 消息中带上所有支持的密码套件相关参数,服务端选定其中一种就可以直接生成通信密钥进行加密通信了!而客户端也省去了 TLS1.2 中要等服务端确认密码套件后再发参数的过程。

    除了标准的 1-RTT 握手,TLS1.3 在之前建立过连接从而缓存了服务端密码套件参数的情况下,还可以达到 0-RTT 握手,但这也存在前向不安全和重放攻击的问题,所以需要使用者去权衡。

    下面我再放上《透析 HTTP 协议》里的通信过程对比图,你可以再看看它们的差异。



尾声

好啦,今天就聊到这里啦,回到我们的目标,看看你还能想到具体的知识吗?

  1. 🔍 快速定位 HTTP 问题。

  2. 🥣 熟悉 HTTP 报文里的常见头字段。

  3. 🔐了解基本的加密知识。

当然,别忘了我们的终极目标🏁:如果你对 HTTP 感兴趣,试着通过 WireShark、Chrome、Telnet 工具以及 RFC 文档,去自行深入学习 HTTP 吧~

用户头像

领取资料添加小助理vx:bjmsb2020 2020.12.19 加入

Java领域;架构知识;面试心得;互联网行业最新资讯

评论

发布
暂无评论
在公司内部,做了一次 HTTP(S) 的分享_程序员_程序员小毕_InfoQ写作社区