破冰篇
01 | 时势与英雄:HTTP的前世今生
- HTTP1.0
- 确立了大部分现在使用的技术
- 增加了 HEAD、POST 等新方法;
- 增加了响应状态码,标记可能的错误原因;
- 引入了协议版本号概念;
- 引入了 HTTP Header(头部)的概念,让 HTTP 处理请求和响应更加灵活;
- 传输的数据不再仅限于文本。
- HTTP1.1
- 互联网上使用最广泛的协议
- 增加了 PUT、DELETE 等新的方法;
- 增加了缓存管理和控制;
- 明确了连接管理,允许持久连接;
- 允许响应数据分块(chunked),利于传输大文件;
- 强制要求 Host 头,让互联网主机托管成为可能。
- HTTP2.0
- 基于 Google 的 SPDY 协议
- 二进制协议,不再是纯文本;
- 可发起多个请求,废弃了 1.1 里的管道;
- 使用专用算法压缩头部,减少数据传输量;
- 允许服务器主动向客户端推送数据;
- 增强了安全性,“事实上”要求加密通信。
- HTTP3.0
- 基于 Google 的 QUIC 协议
- QUIC协议
02 | HTTP是什么?HTTP又不是什么?
- HTTP 专门用来在两点之间传输数据,不能用于广播、寻址或路由。
- HTTP 传输的是文字、图片、音频、视频等超文本数据。
03 | HTTP世界全览(上):与HTTP相关的各种概念
- CDN
- 全称是“Content Delivery Network”,翻译过来就是“内容分发网络”。它应用了 HTTP 协议里的缓存和代理技术,代替源站响应客户端的请求。
- 提供负载均衡、安全防护、边缘计算、跨运营商网络等功能
- 实际上就是一种代理,它代替源站服务器响应客户端的请求,通常扮演着透明代理和反向代理的角色
- 爬虫
- 可以自动访问 Web 资源的应用程序。
- 绝大多数是由各大搜索引擎“放”出来的,抓取网页存入数据库,再建立关键字索引,这样才能够在搜索引擎中搜索到互联网的页面。
04 | HTTP世界全览(下):与HTTP相关的各种协议
- TCP/IP
- TCP/IP 是网络世界最常用的协议,HTTP 通常运行在 TCP/IP 提供的可靠传输基础上;
- TCP/IP 协议实际上是一系列网络通信协议的统称,其中最核心的两个协议是TCP和IP,其他的还有 UDP、ICMP、ARP 等
- ip协议v4版,地址是四个用“.”分隔的数字,例如“192.168.0.1”
- ip协议v6版,使用 8 组“:”分隔的数字作为地址
- DNS
- DNS 域名是 IP 地址的等价替代,需要用域名解析实现到 IP 地址的映射;
- 域名用“.”分隔成多个单词,级别从左到右逐级升高,最右边的被称为“顶级域名”
- URI/URL
- URI 是用来标记互联网上资源的一个名字,由“协议名 + 主机名 + 路径”构成,俗称 URL;
- 协议名:即访问该资源应当使用的协议,在这里是“http”
- 主机名:即互联网上主机的标记,可以是域名或 IP 地址,在这里是“nginx.org”
- 路径:即资源在主机上的位置,使用“/”分隔多级目录,在这里是“/en/download.html”
- HTTPS
- HTTPS 相当于“HTTP+SSL/TLS+TCP/IP”,为 HTTP 套了一个安全的外壳;
- SSL 使用了许多密码学最先进的研究成果,综合了对称加密、非对称加密、摘要算法、数字签名、数字证书等技术,能够在不安全的环境中为通信的双方创建出一个秘密的、安全的传输通道
- 代理
- 代理是 HTTP 传输过程中的“中转站”,可以实现缓存加速、负载均衡等功能。
- 匿名代理:完全“隐匿”了被代理的机器,外界看到的只是代理服务器;
- 透明代理:顾名思义,它在传输过程中是“透明开放”的,外界既知道代理,也知道客户端;
- 正向代理:靠近客户端,代表客户端向服务器发送请求;
- 反向代理:靠近服务器端,代表服务器响应客户端的请求;
- 代理作用
- 负载均衡:把访问请求均匀分散到多台机器,实现访问集群化;
- 内容缓存:暂存上下行的数据,减轻后端的压力;
- 安全防护:隐匿 IP, 使用 WAF 等工具抵御网络攻击,保护被代理的机器;
- 数据处理:提供压缩、加密等额外的功能
05 | 常说的“四层”和“七层”到底是什么?“五层”“六层”哪去了?
- TCP的数据是连续的“字节流”,有先后顺序,而UDP则是分散的小数据包,是顺序发,乱序收。
- 第一层:物理层,TCP/IP 里无对应;
- 第二层:数据链路层,对应 TCP/IP 的链接层;
- 第三层:网络层,对应 TCP/IP 的网际层;
- 第四层:传输层,对应 TCP/IP 的传输层;
- 第五、六、七层:统一对应到 TCP/IP 的应用层。
06 | 域名里有哪些门道?
- 看一下极客时间的域名“time.geekbang.org”,这里的“org”就是顶级域名,“geekbang”是二级域名,“time”则是主机名。
- 根域名服务器(Root DNS Server):管理顶级域名服务器,返回“com”“net”“cn”等顶级域名服务器的 IP 地址;
- 顶级域名服务器(Top-level DNS Server):管理各自域名下的权威域名服务器,比如 com 顶级域名服务器可以返回 apple.com 域名服务器的 IP 地址;
- 权威域名服务器(Authoritative DNS Server):管理自己域名下主机的 IP 地址,比如 apple.com 权威域名服务器可以返回 www.apple.com 的 IP 地址。
- 域名的新玩法
- 因为域名代替了 IP 地址,所以可以让对外服务的域名不变,而主机的 IP 地址任意变动。当主机有情况需要下线、迁移时,可以更改 DNS 记录,让域名指向其他的机器。
- 因为域名是一个名字空间,所以可以使用 bind9 等开源软件搭建一个在内部使用的 DNS,作为名字服务器。这样我们开发的各种内部服务就都用域名来标记,比如数据库服务都用域名“mysql.inner.app”,商品服务都用“goods.inner.app”,发起网络通信时也就不必再使用写死的 IP 地址了,可以直接用域名
- 域名解析可以返回多个 IP 地址,所以一个域名可以对应多台主机,客户端收到多个 IP 地址后,就可以自己使用轮询算法依次向服务器发起请求,实现负载均衡。
- 域名解析可以配置内部的策略,返回离客户端最近的主机,或者返回当前服务质量最好的主机,这样在 DNS 端把请求分发到不同的服务器,实现负载均衡。
07 | 自己动手,搭建HTTP实验环境
略
基础篇
08 | 键入网址再按下回车,后面究竟发生了什么?
- 使用“三次握手”建立与 Web 服务器的连接,经过 SYN、SYN/ACK、ACK 的三个包之后,浏览器与服务器的 TCP 连接就建立起来了。
- 浏览器按照 HTTP 协议规定的格式,通过 TCP 发送了一个“GET / HTTP/1.1”请求报文,也就是 Wireshark 里的第四个包。
- Web服务器回复了第五个包,在 TCP 协议层面确认:“刚才的报文我已经收到了”,不过这个 TCP 包 HTTP 协议是看不见的。
- 从磁盘上把文件读出来,拼成符合 HTTP 格式的报文。这就是第六个包“HTTP/1.1 200 OK”,底层走的还是 TCP 协议。
- 浏览器也要给服务器回复一个 TCP 的 ACK 确认,“你的响应报文收到了,多谢。”,即第七个包。
- 之后还有两个来回,共四个包,浏览器自动请求了作为网站图标的“favicon.ico”文件,服务器在硬盘上找不到,返回了一个“404 Not Found”。
- 浏览器从地址栏的输入中获得服务器的 IP 地址和端口号;
- 浏览器用 TCP 的三次握手与服务器建立连接;
- 浏览器向服务器发送拼好的报文;
- 服务器收到报文后处理请求,同样拼好报文再发给浏览器;
- 浏览器解析报文,渲染输出页面。
- dns缓存
- 在域名解析的过程中会有多级的缓存,浏览器首先看一下自己的缓存里有没有,如果没有就向操作系统的缓存要,还没有就检查本机域名解析文件 hosts,也就是上一讲中我们修改的“C:\WINDOWS\system32\drivers\etc\hosts”。
- 真实的网络世界
- 用 DNS 协议开始从操作系统、本地 DNS、根 DNS、顶级 DNS、权威 DNS 的层层解析,当然这中间有缓存,可能不会费太多时间就能拿到结果。
- DNS 解析可能会给出 CDN 服务器的 IP 地址,这样你拿到的就会是 CDN 服务器而不是目标网站的实际地址。
- CDN 会缓存网站的大部分资源,比如图片、CSS 样式表,所以有的 HTTP 请求就不需要再发到 Apple,CDN 就可以直接响应你的请求,把数据发给你。
- 动态资源”,CDN 无法缓存,只能从目标网站获取,服务器对外表现的是一个 IP 地址,但为了能够扛住高并发,在内部也是一套复杂的架构。通常在入口是负载均衡设备
- 负载均衡设备会先访问系统里的缓存服务器,如果缓存服务器里也没有,那么负载均衡设备就要把请求转发给应用服务器了。
- 它们又会再访问后面的 MySQL、PostgreSQL、MongoDB 等数据库服务,然后把执行的结果返回给负载均衡设备,同时也可能给缓存服务器里也放一份。
- 应用服务器的输出到了负载均衡设备这里,请求的处理就算是完成了,就要按照原路再走回去,经过 CDN 的时候它也会做缓存,这样下次同样的请求就不会到达源站了。
09 | HTTP报文是什么样子的?
- HTTP 协议的请求报文和响应报文的结构
- 起始行(start line):描述请求或响应的基本信息;
- 头部字段集合(header):使用 key-value 形式更详细地说明报文;
- 消息正文(entity):实际传输的数据,它不一定是纯文本,可以是图片、视频等二进制数据。
- 前两部分起始行和头部字段经常又合称为“请求头”或“响应头”,消息正文又称为“body”。
- 请求行
- 请求方法:是一个动词,如 GET/POST,表示对资源的操作;
- 请求目标:通常是一个 URI,标记了请求方法要操作的资源;
- 版本号:表示报文使用的 HTTP 协议版本。
- GET / HTTP/1.1
- 状态行
- 版本号:表示报文使用的 HTTP 协议版本;
- 状态码:一个三位数,用代码的形式表示处理的结果,比如 200 是成功,500 是服务器错误;
- 原因:作为数字状态码补充,是更详细的解释文字,帮助人理解原因。
- HTTP/1.1 200 OK
- 头部字段
- 头部字段是 key-value 的形式,key 和 value 之间用“:”分隔,最后用 CRLF 换行表示字段结束。
- 常用头字段
- Host字段要求必须出现
- 在 127.0.0.1 上有三个虚拟主机:“www.chrono.com”“www.metroid.net”和“origin.io”。那么当使用域名的方式访问时,就必须要用 Host 字段来区分这三个 IP 相同但域名不同的网站,否则服务器就会找不到合适的虚拟主机,无法处理。
10 | 应该如何理解请求方法?
- GET:获取资源,可以理解为读取或者下载数据;
- HEAD:获取资源的元信息;
- POST:向资源提交数据,相当于写入或上传数据,更像新增;
- PUT:类似 POST,更像更新。
- DELETE:删除资源;
- CONNECT:建立特殊的连接隧道;
- OPTIONS:列出可对资源实行的方法;
- TRACE:追踪请求 - 响应的传输路径。
- 扩展方法
- 我们可以任意添加请求动作,只要请求方和响应方都能理解就行。
11 | 你能写出正确的网址吗?
- URI 的格式
- scheme 叫“方案名”或者“协议名”,表示资源应该使用哪种协议来访问;
- “host:port”表示资源所在的主机名和端口号;
- path 标记资源所在的位置;
- query 表示对资源附加的额外要求;
- URI 的编码
- URI 转义的规则有点“简单粗暴”,直接把非 ASCII 码或特殊字符转换成十六进制字节值,然后前面再加上一个“%”
12 | 响应状态码该怎么用?
- 1××:提示信息,表示目前是协议处理的中间状态,还需要后续的操作;
- 2××:成功,报文已经收到并被正确处理;
- 3××:重定向,资源位置发生变动,需要客户端重新发送请求;
- 4××:客户端错误,请求报文有误,服务器无法处理;
- 5××:服务器错误,服务器在处理请求时内部发生了错误。
- 2xx
- “200 OK”是最常见的成功状态码,表示一切正常,服务器如客户端所期望的那样返回了处理结果,如果是非 HEAD 请求,通常在响应头后都会有 body 数据。
- “204 No Content”是另一个很常见的成功状态码,它的含义与“200 OK”基本相同,但响应头后没有 body 数据。所以对于 Web 服务器来说,正确地区分 200 和 204 是很必要的。
- “206 Partial Content”是 HTTP 分块下载或断点续传的基础,在客户端发送“范围请求”、要求获取资源的部分数据时出现,它与 200 一样,也是服务器成功处理了请求,但 body 里的数据不是资源的全部,而是其中的一部分。
- 状态码 206 通常还会伴随着头字段“Content-Range”,表示响应报文里 body 数据的具体范围,供客户端确认。
- 3××
- “301 Moved Permanently”俗称“永久重定向”,含义是此次请求的资源已经不存在了,需要改用改用新的 URI 再次访问。
- 302 Found”,曾经的描述短语是“Moved Temporarily”,俗称“临时重定向”,意思是请求的资源还在,但需要暂时用另一个 URI 来访问。
- 301 和 302 都会在响应头里使用字段Location指明后续要跳转的 URI,最终的效果很相似,浏览器都会重定向到新的 URI。
- 304 Not Modified” 是一个比较有意思的状态码,它用于 If-Modified-Since 等条件请求,表示资源未修改,用于缓存控制。
- 4××
- “400 Bad Request”是一个通用的错误码,表示请求报文有错误
- “403 Forbidden”实际上不是客户端的请求出错,而是表示服务器禁止访问资源。
- 如果服务器友好一点,可以在 body 里详细说明拒绝请求的原因
- “404 Not Found”可能是我们最常看见也是最不愿意看到的一个状态码,它的原意是资源在本服务器上未找到。
- 405 Method Not Allowed:不允许使用某些方法操作资源,例如不允许 POST 只能 GET;
- 406 Not Acceptable:资源无法满足客户端请求的条件,例如请求中文但只有英文;
- 408 Request Timeout:请求超时,服务器等待了过长的时间;
- 409 Conflict:多个请求发生了冲突,可以理解为多线程并发时的竞态;
- 413 Request Entity Too Large:请求报文里的 body 太大;
- 414 Request-URI Too Long:请求行里的 URI 太大;
- 429 Too Many Requests:客户端发送了太多的请求,通常是由于服务器的限连策略;
- 431 Request Header Fields Too Large:请求头某个字段或总体太大;
- 5××
- “500 Internal Server Error”与 400 类似,也是一个通用的错误码,服务器究竟发生了什么错误我们是不知道的
- “501 Not Implemented”表示客户端请求的功能还不支持
- “502 Bad Gateway”通常是服务器作为网关或者代理时返回的错误码,表示服务器自身工作正常,访问后端服务器时发生了错误
- “503 Service Unavailable”表示服务器当前很忙,暂时无法响应服务,我们上网时有时候遇到的“网络服务正忙,请稍后重试”的提示信息就是状态码 503。
13 | HTTP有哪些特点?
- HTTP 是灵活可扩展的,可以任意添加头字段实现任意功能;
- HTTP 是可靠传输协议,基于 TCP/IP 协议“尽量”保证数据的送达;
- HTTP 是应用层协议,比 FTP、SSH 等更通用功能更多,能够传输任意数据;
- HTTP 使用了请求 - 应答模式,客户端主动发起请求,服务器被动回复请求;
- HTTP 本质上是无状态的,每个请求都是互相独立、毫无关联的,协议不要求客户端或服务器记录请求相关的信息。
14 | HTTP有哪些优点?又有哪些缺点?
- HTTP 最大的优点是简单、灵活和易于扩展;
- HTTP 拥有成熟的软硬件环境,应用的非常广泛,是互联网的基础设施;
- HTTP 是无状态的,可以轻松实现集群化,扩展性能,但有时也需要用 Cookie 技术来实现“有状态”;
- Cookie 技术
- HTTP 是明文传输,数据完全肉眼可见,能够方便地研究分析,但也容易被窃听;
- HTTP 是不安全的,无法验证通信双方的身份,也不能判断报文是否被窜改;
- 为了解决 HTTP 不安全的缺点,所以就出现了 HTTPS
- HTTP 的性能不算差,但不完全适应现在的互联网,还有很大的提升空间。
- “队头阻塞”(Head-of-line blocking),当顺序发送的请求序列中的一个请求因为某种原因被阻塞时,在后面排队的所有请求也一并被阻塞,会导致客户端迟迟收不到数据。
进阶篇
15 | 海纳百川:HTTP的实体数据
- 支持的格式
- 客户端:Accept: text/html,application/xml,image/webp,image/png
- 服务端:Content-Type: text/html
- 压缩格式
- Accept-Encoding: gzip, deflate, br
- Content-Encoding: gzip
- 语言
- Accept-Language: zh-CN, zh, en
- Content-Language: zh-CN
- 字符集
- Accept-Charset: gbk, utf-8
- Content-Type: text/html; charset=utf-8
- 权重
- Accept: text/html,application/xml;q=0.9,/;q=0.8
- 浏览器最希望使用的是 HTML 文件,权重是 1,其次是 XML 文件,权重是 0.9,最后是任意数据类型,权重是 0.8。
- 数据类型表示实体数据的内容是什么,使用的是 MIME type,相关的头字段是 Accept 和 Content-Type;
- 数据编码表示实体数据的压缩方式,相关的头字段是 Accept-Encoding 和 Content-Encoding;
- 语言类型表示实体数据的自然语言,相关的头字段是 Accept-Language 和 Content-Language;
- 字符集表示实体数据的编码方式,相关的头字段是 Accept-Charset 和 Content-Type;
- 客户端需要在请求头里使用 Accept 等头字段与服务器进行“内容协商”,要求服务器返回最合适的数据;
- Accept 等头字段可以用“,”顺序列出多个可能的选项,还可以用“;q=”参数来精确指定权重。
16 | 把大象装进冰箱:HTTP传输大文件的方法
- 压缩 HTML 等文本文件是传输大文件最基本的方法;
- 分块传输可以流式收发数据,节约内存和带宽,使用响应头字段“Transfer-Encoding: chunked”来表示,分块的格式是 16 进制长度头 + 数据块;
- 范围请求可以只获取部分数据,即“分块请求”,实现视频拖拽或者断点续传,使用请求头字段“Range”和响应头字段“Content-Range”,响应状态码必须是 206;
- 也可以一次请求多个范围,这时候响应报文的数据类型是“multipart/byteranges”,body 里的多个部分会用 boundary 字符串分隔
17 | 排队也要讲效率:HTTP的连接管理
- HTTP 0.9/1.0 短连接
- HTTP 1.1 长连接
- 同样发送三次请求,因为只在第一次时建立连接,在最后一次时关闭连接
- 如果服务器支持长连接,它总会在响应报文里放一个“Connection: keep-alive”字段
- 关闭策略
- 在请求头里加上“Connection: close”字段,服务器看到这个字段,就知道客户端要主动关闭连接,于是在响应报文里也加上这个字段,发送之后就调用 Socket API 关闭 TCP 连接。
- 服务端设置长连接的超时时间,如果在一段时间内连接上没有任何数据收发就主动断开连接,避免空闲连接占用系统资源。
- 服务端设置长连接上可发送的最大请求次数。比如设置成 1000,那么当 Nginx 在这个连接上处理了 1000 个请求后,也会主动断开连接。
- “队头阻塞”问题会导致性能下降,可以用“并发连接”和“域名分片”技术缓解。
18 | 四通八达:HTTP的重定向和跳转
- 浏览器收到 301/302 报文,会检查响应头里有没有“Location”。如果有,就从字段值里提取出 URI,发出新的 HTTP 请求,相当于自动替我们点击了这个链接。
- 301俗称“永久重定向”(Moved Permanently),意思是原 URI 已经“永久”性地不存在了,今后的所有请求都必须改用新的 URI。
- 302俗称“临时重定向”(“Moved Temporarily”),意思是原 URI 处于“临时维护”状态,新的 URI 是起“顶包”作用的“临时工”。
- 例如域名变更、服务器变更、网站改版、系统维护
- 有的网站都会申请多个名称类似的域名,然后把它们再重定向到主站上。
19 | 让我知道你是谁:HTTP的Cookie机制
- Cookie 的有效期可以使用 Expires 和 Max-Age 两个属性来设置。
- “Expires”俗称“过期时间”,用的是绝对时间点,可以理解为“截止日期”(deadline)。
- “Max-Age”用的是相对时间,单位是秒,浏览器用收到报文的时间点再加上 Max-Age,就可以得到失效的绝对时间。
- Cookie 的作用域
- “Domain”和“Path”指定了 Cookie 所属的域名和路径,浏览器在发送 Cookie 前会从 URI 中提取出 host 和 path 部分,对比 Cookie 的属性。如果不满足条件,就不会在请求头里发送 Cookie。
- Cookie 的安全性
- 属性“HttpOnly”会告诉浏览器,此 Cookie 只能通过浏览器 HTTP 协议传输,禁止其他方式访问,浏览器的 JS 引擎就会禁用 document.cookie 等一切相关的 API,脚本攻击也就无从谈起了。
- 另一个属性“SameSite”可以防范“跨站请求伪造”(XSRF)攻击,设置成“SameSite=Strict”可以严格限定 Cookie 不能随着跳转链接跨站发送,而“SameSite=Lax”则略宽松一点,允许 GET/HEAD 等安全方法,但禁止 POST 跨站发送。
- 还有一个属性叫“Secure”,表示这个 Cookie 仅能用 HTTPS 协议加密传输,明文的 HTTP 协议会禁止发送。
20 | 生鲜速递:HTTP的缓存控制
- 服务器标记资源有效期使用的头字段是“Cache-Control”,里面的值“max-age=30”就是资源的有效时间。
- max-age 是“生存时间”(又叫“新鲜度”“缓存寿命”,类似 TTL,Time-To-Live),时间的计算起点是响应报文的创建时刻(即 Date 字段,也就是离开服务器的时刻),而不是客户端收到报文的时刻,也就是说包含了在链路传输过程中所有节点所停留的时间。
- no_store:不允许缓存,用于某些变化非常频繁的数据,例如秒杀页面;
- no_cache:它的字面含义容易与 no_store 搞混,实际的意思并不是不允许缓存,而是可以缓存,但在使用之前必须要去服务器验证是否过期,是否有最新的版本;
- must-revalidate:又是一个和 no_cache 相似的词,它的意思是如果缓存不过期就可以继续使用,但过期了如果还想用就必须去服务器验证。
- 条件请求
- 条件请求一共有 5 个头字段,我们最常用的是“if-Modified-Since”和“If-None-Match”这两个。需要第一次的响应报文预先提供“Last-modified”和“ETag”,然后第二次请求时就可以带上缓存里的原值,验证资源是否是最新的。
- 浏览器也可以发送“Cache-Control”字段,使用“max-age=0”或“no_cache”刷新数据
- 验证资源是否失效需要使用“条件请求”,常用的是“if-Modified-Since”和“If-None-Match”,收到 304 就可以复用缓存里的资源
21 | 良心中间商:HTTP的代理服务
- 所谓的“代理服务”就是指服务本身不生产内容,而是处于中间位置转发上下游的请求和响应,具有双重身份。
- 代理最基本的一个功能是负载均衡。因为在面向客户端时屏蔽了源服务器,客户端看到的只是代理服务器,源服务器究竟有多少台、是哪些 IP 地址都不知道。
- 负载均衡算法,轮询、一致性哈希等等,这些算法的目标都是尽量把外部的流量合理地分散到多台源服务器,提高系统的整体资源利用率和性能。
- 在负载均衡的同时,代理服务还可以执行更多的功能
- 健康检查:使用“心跳”等机制监控后端服务器,发现有故障就及时“踢出”集群,保证服务高可用;
- 安全防护:保护被代理的后端服务器,限制 IP 地址或流量,抵御网络攻击和过载;
- 加密卸载:对外网使用 SSL/TLS 加密通信认证,而在安全的内网不加密,消除加解密成本;
- 数据过滤:拦截上下行的数据,任意指定策略修改请求或者响应;
- 内容缓存:暂存、复用服务器响应
- 代理相关头字段
- Via 是一个通用字段,请求头或响应头里都可以出现。每当报文经过一个代理节点,代理服务器就会把自身的信息追加到字段的末尾
- Via 是一个通用字段,请求头或响应头里都可以出现。每当报文经过一个代理节点,代理服务器就会把自身的信息追加到字段的末尾
- X-Forwarded-For
- 每经过一个代理节点就会在字段里追加一个信息。但“Via”追加的是代理主机名(或者域名),而“X-Forwarded-For”追加的是请求方的 IP 地址。
- X-Real-IP
- 记录客户端 IP 地址,没有中间的代理信息
- 整体流程
- 客户端 55061 先用三次握手连接到代理的 80 端口,然后发送 GET 请求;
- 代理不直接生产内容,所以就代表客户端,用 55063 端口连接到源服务器,也是三次握手;
- 代理成功连接源服务器后,发出了一个 HTTP/1.0 的 GET 请求;
- 因为 HTTP/1.0 默认是短连接,所以源服务器发送响应报文后立即用四次挥手关闭连接;
- 代理拿到响应报文后再发回给客户端,完成了一次代理服务。
22 | 冷链周转:HTTP的缓存代理
- private和public
- “private”表示缓存只能在客户端保存,是用户“私有”的,不能放在代理上与别人共享。而“public”的意思就是缓存完全开放,谁都可以存,谁都可以用。
- s-maxage
- 限定在代理上能够存多久
- no-transform
- 代理有时候会对缓存下来的数据做一些优化,比如把图片生成 png、webp 等几种格式,方便今后的请求处理,而“no-transform”就会禁止这样做,不许“偷偷摸摸搞小动作”
- 以下是服务器端缓存控制策略,可以同时控制客户端和代理。
- 以下是客户端的缓存控制
- max-stale和min-fresh
- “max-stale”的意思是如果代理上的缓存过期了也可以接受,但不能过期太多,超过 x 秒也会不要。“min-fresh”的意思是缓存必须有效,而且必须在 x 秒后依然有效。
安全篇
23 | TLS又是什么?
-
如果通信过程具备了四个特性,就可以认为是“安全”的,这四个特性是:机密性、完整性,身份认证和不可否认。
- 机密性(Secrecy/Confidentiality)是指对数据的“保密”,只能由可信的人访问,对其他人是不可见的“秘密”
- 完整性(Integrity,也叫一致性)是指数据在传输过程中没有被窜改,不多也不少,“完完整整”地保持着原状
- 身份认证(Authentication)是指确认对方的真实身份,也就是“证明你真的是你”,保证消息只能发送给可信的人
- 不可否认(Non-repudiation/Undeniable),也叫不可抵赖,意思是不能否认已经发生过的行为,不能“说话不算数”“耍赖皮”
-
浏览器和服务器在使用 TLS 建立连接时需要选择一组恰当的加密算法来实现安全通信,这些算法的组合被称为“密码套件”
- ECDHE-RSA-AES256-GCM-SHA384
- TLS 的密码套件命名非常规范,格式很固定。基本的形式是“密钥交换算法(机密性) + 签名算法(身份认证) + 对称加密算法(机密性) + 摘要算法(不可否认、完整性)”
- 握手时使用 ECDHE 算法进行密钥交换,用 RSA 签名和身份认证,握手后的通信使用 AES 对称算法,密钥长度 256 位,分组模式是 GCM,摘要算法 SHA384 用于消息认证和产生随机数。
24 | 固若金汤的根本(上):对称加密与非对称加密
- 对称加密只使用一个密钥,运算速度快,密钥必须保密,无法做到安全的密钥交换,常用的有 AES 和 ChaCha20;
- 非对称加密使用两个密钥:公钥和私钥,公钥可以任意分发而私钥保密,解决了密钥交换问题但速度慢,常用的有 RSA 和 ECC;
- 把对称加密和非对称加密结合起来就得到了“又好又快”的混合加密,也就是 TLS 里使用的加密方式。
25 | 固若金汤的根本(下):数字签名与证书
- 数字签名
- 同时实现“身份认证”和“不可否认”
- 同时实现“身份认证”和“不可否认”
- 数字证书和 CA
- 有了这个证书体系,操作系统和浏览器都内置了各大 CA 的根证书,上网的时候只要服务器发过来它的证书,就可以验证证书里的签名,顺着证书链(Certificate Chain)一层层地验证,直到找到根证书,就能够确定证书是可信的,从而里面的公钥也是可信的。
- CA证书验证过程?
26 | 信任始于握手:TLS1.2连接过程解析
- TLS 协议的组成
- 记录协议(Record Protocol)规定了 TLS 收发数据的基本单位:记录(record)。它有点像是 TCP 里的 segment,所有的其他子协议都需要通过记录协议发出。但多个记录数据可以在一个 TCP 包里一次性发出,也并不需要像 TCP 那样返回 ACK。
- 警报协议(Alert Protocol)的职责是向对方发出警报信息,有点像是 HTTP 协议里的状态码。比如,protocol_version 就是不支持旧版本,bad_certificate 就是证书有问题,收到警报后另一方可以选择继续,也可以立即终止连接。
- 握手协议(Handshake Protocol)是 TLS 里最复杂的子协议,要比 TCP 的 SYN/ACK 复杂的多,浏览器和服务器会在握手过程中协商 TLS 版本号、随机数、密码套件等信息,然后交换证书和密钥参数,最终双方协商得到会话密钥,用于后续的混合加密系统。
- 变更密码规范协议(Change Cipher Spec Protocol),它非常简单,就是一个“通知”,告诉对方,后续的数据都将使用加密保护。那么反过来,在它之前,数据都是明文的。
- 仔细剖析 TLS 的握手过程
- 在 TCP 建立连接之后,浏览器会首先发一个“Client Hello”消息,也就是跟服务器“打招呼”。里面有客户端的版本号、支持的密码套件,还有一个随机数(Client Random) ,用于后续生成会话密钥。
- 这个的意思就是:“我这边有这些这些信息,你看看哪些是能用的,关键的随机数可得留着。”
- 服务器收到“Client Hello”后,会返回一个“Server Hello”消息。把版本号对一下,也给出一个随机数(Server Random) ,然后从客户端的列表里选一个作为本次通信使用的密码套件,在这里它选择了“TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384”
- 这个的意思就是:“版本号对上了,可以加密,你的密码套件挺多,我选一个最合适的吧,用椭圆曲线加 RSA、AES、SHA384。我也给你一个随机数,你也得留着。”
- 服务器为了证明自己的身份,就把证书也发给了客户端(Server Certificate)。
- 因为服务器选择了 ECDHE 算法,所以它会在证书后发送“Server Key Exchange”消息,里面是椭圆曲线的公钥(Server Params) ,用来实现密钥交换算法,再加上自己的私钥签名认证。
- 这相当于说:“刚才我选的密码套件有点复杂,所以再给你个算法的参数,和刚才的随机数一样有用,别丢了。为了防止别人冒充,我又盖了个章。”
- 之后是“Server Hello Done”消息,服务器说:“我的信息就是这些,打招呼完毕。”
- 这样第一个消息往返就结束了(两个 TCP 包),结果是客户端和服务器通过明文共享了三个信息:Client Random、Server Random 和 Server Params。
- 客户端开始走证书链逐级验证,确认证书的真实性,再用证书公钥验证签名,就确认了服务器的身份:“刚才跟我打招呼的不是骗子,可以接着往下走。”
- 客户端按照密码套件的要求,也生成一个椭圆曲线的公钥(Client Params) ,用“Client Key Exchange”消息发给服务器。
- 现在客户端和服务器手里都拿到了密钥交换算法的两个参数(Client Params、Server Params),就用 ECDHE 算法一阵算,算出了一个新的东西,叫“Pre-Master”,其实也是一个随机数。
- 现在客户端和服务器手里有了三个随机数:Client Random、Server Random 和 Pre-Master。用这三个作为原始材料,就可以生成用于加密会 话的主密钥,叫“Master Secret”。
- 有了主密钥和派生的会话密钥,握手就快结束了。客户端发一个“Change Cipher Spec”,然后再发一个“Finished”消息,把之前所有发送的数据做个摘要,再加密一下,让服务器做个验证。
- 意思就是告诉服务器:“后面都改用对称算法加密通信了啊,用的就是打招呼时说的 AES,加密对不对还得你测一下。”
- 服务器也是同样的操作,发“Change Cipher Spec”和“Finished”消息,双方都验证加密解密 OK,握手正式结束,后面就收发被加密的 HTTP 请求和响应了。
- 传统的握手
- 第一个,使用 ECDHE 实现密钥交换,而不是 RSA,所以会在服务器端发出“Server Key Exchange”消息。
- 第二个,因为使用了 ECDHE,客户端可以不用等到服务器发回“Finished”确认握手完毕,立即就发出 HTTP 报文,省去了一个消息往返的时间浪费。这个叫“TLS False Start”,意思就是“抢跑”,和“TCP Fast Open”有点像,都是不等连接完全建立就提前发应用数据,提高传输的效率。
- 大体的流程没有变,只是“Pre-Master”不再需要用算法生成,而是客户端直接生成随机数,然后用服务器的公钥加密,通过“Client Key Exchange”消息发给服务器。服务器再用私钥解密,这样双方也实现了共享三个随机数,就可以生成主密钥。
27 | 更好更快的握手:TLS1.3特性解析
- 最大化兼容性
- 在记录头的 Version 字段被兼容性“固定”的情况下,只要是 TLS1.3 协议,握手的“Hello”消息后面就必须有“supported_versions”扩展,它标记了 TLS 的版本号,使用它就能区分新旧协议。
Handshake Protocol: Client Hello
Version: TLS 1.2 (0x0303)
Extension: supported_versions (len=11)
Supported Version: TLS 1.3 (0x0304)
Supported Version: TLS 1.2 (0x0303)
- 强化安全
- 算法精简后带来了一个意料之中的好处:原来众多的算法、参数组合导致密码套件非常复杂,难以选择,而现在的 TLS1.3 里只有 5 个套件,无论是客户端还是服务器都不会再犯“选择困难症”了。
- 算法精简后带来了一个意料之中的好处:原来众多的算法、参数组合导致密码套件非常复杂,难以选择,而现在的 TLS1.3 里只有 5 个套件,无论是客户端还是服务器都不会再犯“选择困难症”了。
- 提升性能
- HTTPS 建立连接时除了要做 TCP 握手,还要做 TLS 握手,在 1.2 中会多花两个消息往返(2-RTT),可能导致几十毫秒甚至上百毫秒的延迟,在移动网络中延迟还会更严重。
- TLS1.3 压缩了以前的“Hello”协商过程,删除了“Key Exchange”消息,把握手时间减少到了“1-RTT”,效率提高了一倍。
- 其实具体的做法还是利用了扩展。客户端在“Client Hello”消息里直接用“supported_groups”带上支持的曲线,比如 P-256、x25519,用“key_share”带上曲线对应的客户端公钥参数,用“signature_algorithms”带上签名算法。
- 服务器收到后在这些扩展里选定一个曲线和参数,再用“key_share”扩展返回服务器这边的公钥参数,就实现了双方的密钥交换,后面的流程就和 1.2 基本一样了。
Handshake Protocol: Client Hello Version: TLS 1.2 (0x0303) |
| | Random: cebeb6c05403654d66c2329… |
| | Cipher Suites (18 suites) |
| | Cipher Suite: TLS_AES_128_GCM_SHA256 (0x1301) |
| | Cipher Suite: TLS_CHACHA20_POLY1305_SHA256 (0x1303) |
| | Cipher Suite: TLS_AES_256_GCM_SHA384 (0x1302) |
| | Extension: supported_versions (len=9) |
| | Supported Version: TLS 1.3 (0x0304) |
| | Supported Version: TLS 1.2 (0x0303) |
| | Extension: supported_groups (len=14) |
| | Supported Groups (6 groups) |
| | Supported Group: x25519 (0x001d) |
| | Supported Group: secp256r1 (0x0017) |
| | Extension: key_share (len=107) |
| | Key Share extension |
| | Client Key Share Length: 105 |
| | Key Share Entry: Group: x25519 |
| | Key Share Entry: Group: secp256r1
- 为了兼容 1.1、1.2 等“老”协议,TLS1.3 会“伪装”成 TLS1.2,新特性在“扩展”里实现;
- 1.1、1.2 在实践中发现了很多安全隐患,所以 TLS1.3 大幅度删减了加密算法,只保留了 ECDHE、AES、ChaCha20、SHA-2 等极少数算法,强化了安全;
- TLS1.3 也简化了握手过程,完全握手只需要一个消息往返,提升了性能。