HTTP
2022-08-01 15:53:04 2 举报
AI智能生成
包含了 http 协议的发展,历史,从 HTTP1.0 到 HTTP3.0 的总结
作者其他创作
大纲/内容
前置知识
RTT
消息往返时延
表示从发送端发送数据开始,到发送端收到来自接收端的确认(接收端收到数据后便立即发送确认),总共经历的时延
前世今生
HTTP 起始
1989 年蒂姆·博纳斯-李的一篇论文
URI: 统一资源标识符
HTML: 超文本标记语言,超文本文档
HTTP: 超文本传输协议,传输超文本
HTTP/0.9
20 世纪 90 年代
结构简单,便于服务器和客户端处理
采用了纯文本格式
只允许"GET"请求,响应后立即关闭连接
HTTP/1.0
正式发布于 1996 年
主要变更
增加了 HEAD、POST 等新方法
增加了响应状态码
引入了协议版本号概念
引入了 HTTP Header 概念
传输不再仅限文本
HTTP/1.1
1999 年 HTTP/1.1发布了 RFC 文档,编号为 2616
2014 年再次修订,将原来的文档拆分成了六份小文档,编号为 7230-7235,优化了一些细节
2014 年再次修订,将原来的文档拆分成了六份小文档,编号为 7230-7235,优化了一些细节
正式成为了标准
主要变更
增加 PUT、DELETE 等新方法
增加缓存管理和控制
明确了连接管理,允许持久连接
允许响应数据分块(chunked),利于传输大文件
强制要求 Host 头
开启了 Web1.0 和 Web2.0 时代
性能改善
从纯文本到二进制协议
可以发起多个请求,废弃了 1.1 的管道
使用专用算法压缩头部,减少数据传输量
允许服务器主动向客户端推送数据
增强了安全性,要求加密通信
HTTP/2
2015 年发布 HTTP/2 RFC 编号 7540
以 Google 的 SPDY 为基础指定的新版本的 HTTP 协议
衍生出 gRPC 等新协议
目前为止,暂且还干不过 HTTP/1.1
HTTP/3
QUIC,在 HTTP/2 处于草案的时候 Google 发明的新协议
2018 年,互联网标准化组织提议将"HTTP over QUIC"更名为 HTTP/3 获得批准
2018 年,互联网标准化组织提议将"HTTP over QUIC"更名为 HTTP/3 获得批准
HTTP 是什么/不是什么
全称
HTTP 是超文本传输协议,英文 HyperTextTransfer Protocol
HTTP 是什么
超文本
HTTP 是一个在计算机世界里专门在两点之间传输文字、图片、音频、视频等超文本数据的约定和规范
传输
HTTP 是一个在计算机世界里专门用来在两点之间传输数据的约定和规范
协议
HTTP 是一个用在计算机世界里的协议,它使用计算机能够理解的语言确立了一种计算机之间交流通信的规范,以及相关的各种控制和错误处理方式
HTTP 不是什么
不是互联网
不是编程语言
不是 HTML
不是孤立的协议
HTTP 通常跑在 TCP/IP 协议栈之上
依靠 IP 协议实现寻址和路由
TCP 协议实现可靠数据传输
DNS 协议实现域名查找
SSL/TLS 协议实现安全通信
依靠 IP 协议实现寻址和路由
TCP 协议实现可靠数据传输
DNS 协议实现域名查找
SSL/TLS 协议实现安全通信
与 HTTP 相关的各种概念
CDN
全称:Content Delivery NetWork
运用了 HTTP 协议里的缓存和代理技术
作用
负载均衡
安全防护
周边计算
跨运营商网络
加速访问
位于浏览器和服务器之间
WAF
网络应用防火墙
位于 Web 服务器之前
主要检测 HTTP 流量
可以阻止SQL 注入,跨站脚本等
总结
互联网大部分资源都使用 HTTP
浏览器是 HTTP 协议里的请求方,即 User Agent
服务器是 HTTP 协议里的应答方,常用 Apach 和 Nginx
CDN 位于浏览器和服务器之间,主要做缓冲,加速访问之类的事
爬虫是另一类 User Agent,是自动访问网络资源的程序
与 HTTP 相关的各种协议
TCP/IP
是一系列网络协议的统称
最核心部分是 TCP,IP 两个协议
TCP,IP,UDP,ICMP,ARP 等构成了一个复杂单有层次的协议栈
协议栈
应用层
传输层 TCP 所在的层,又叫 TCP 层
网际层 IP 所在的层,又叫 IP 层
链接层
IP 协议
Internet Protocol
解决寻址和路由问题,以及如何在两点之间传数据包
分为 V4 和 V6 两个版本
V4 使用"."分割数字,最大 2^32 次方个,大概 4 亿
V6 使用 8 组":"分割,最大 2^128 次方个
TCP 协议
Transmission Control Protocol "传输控制协议"
位于IP 之上,基于 IP 协议提供可靠的,字节流通信
可靠,是指保证数据不丢失
字节流,是指保证数据完整
HTTP 全称: HTTP over TCP/IP
DNS
域名系统(Domain Name System)
DNS 是IP 地址的等价代替,便于记忆,需要用域名解析实现到 IP 地址的映射
URI/URL
URI
统一资源标识符
唯一的标记互联网上的资源
构成
协议名
http
主机名
nginx.org
路径
/etc/download.html
URL
统一资源定位符(网址)
URI 的子集
HTTPS
全称: HTTP over SSL/TLS
HTTP 全称: HTTP over TCP/IP
SSL/TLS
负责加密通信的安全协议
建立在 TCP 之上
可靠的传输协议
全称: Secure Socket Layer
网景公司发明,在 3.0 版本成功成为了标准,改名为 TLS 即Transport Layer Security
代理
Proxy
作为 HTTP 请求方和应答方中间的中转站
即可以代发客户端请求,也可以转发服务端应答
分类
匿名代理
透明代理
正向代理
靠近客户端,代表客户端向服务端发送请求
反向代理
靠近服务端,代表服务端响应客户端请求
中间层
负载均衡
把访问请求均匀分散到多台机器,实现访问集群化
内容缓存
暂存上下行的数据,减轻后端的压力;
安全防护
隐匿 IP, 使用 WAF 等工具抵御网络攻击,保护被代理的机器
数据处理
提供压缩、加密等额外的功能
代理协议 Proxy Protocol
HAProxy代理软件定制
不属于 RFC 标准
四层和七层的区别
TCP/IP 网络分层模型(四层模型)
有层次的协议栈
分层
application layer 应用层
面向各种具体应用协议
如:Telnet,SSH,FTP,SMTP 等
transport layer 传输层
负责保证数据在 IP 地址标记的两点之间"可靠"传输
TCP 是有状态协议,需要先与对方建立连接后,才能发送数据,而且保证数据不重复,不丢失
UDP比较简单,无状态,不用事先建立连接就可以任意发送数据,但是不保证数据一定会到达对方
TCP 是连续的"字节流"顺序发送,顺序接收
UDP 是顺序发送,无序接收
也叫 TCP 层
Internet layer 网际层/网络互联层
用 IP 代替 MAC 地址
也叫 IP 层
link layer 链接层
负责在底层网楼哦上发送原始数据包
工作在网卡层
使用 MAC 地址标记网络设备
也叫 MAC 层
传输单位(统称数据包)
HTTP 的单位是 消息/报文 message
TCP 层是 段 segment
IP 层是 包 packet
MAC 层是 帧 frame
OSI 网络分层模型(七层模型)
全称: 开放式系统互联通信参考模型(Open System Interconnection Reference Model)
设计初衷:TCP/IP 发明于 1970 年,当时除了它还有很多其它网络协议,整个网络世界太过混乱,
国际标准组织 OSI 注意到了这种现象,觉得野路子太多,于是设计出了一个新的网络分层模型,想用新的框架统一已有的各种网络协议
国际标准组织 OSI 注意到了这种现象,觉得野路子太多,于是设计出了一个新的网络分层模型,想用新的框架统一已有的各种网络协议
分层
应用层
面向具体应用传输数据
表示层
把数据转换成合适的,可理解的语法和语义
会话层
维护网络中的连接状态,保持会话和同步
传输层
相当于 TCP/IP 的传输层
网络层
相当于 TCP/IP 的网际层
数据链路层
基本相当于 TCP/IP 的链接层
物理层
网络的物理形式,如电缆,光纤,网卡,集线器
每一层有对应编号,TCP/IP 没有编号
两个分层模型的映射关系
第五六七层: 统一对应到 TCP/IP 的应用层
OSI 中五六七层分的太细
而在 TCP/IP 实际应用中,会话管理, 编码转换,压缩等和具体应用经常联系的很紧密,很难分开
第四层: 传输层,对应 TCP/IP 的传输层
第三层: 网络层, 对应 TCP/IP 的网际层
第二层: 数据链路层, 对应 TCP/IP 的链接层
第一层: 物理层, TCP/IP 里无对应
所谓负载均衡
啥叫负载均衡
就是分摊到多个操作单元上执行
例如多个请求过来之后,Nginx 分发到各个不同的服务器上
四层负载均衡
在传输层上
基于 TCP/IP 协议特性
如 IP 地址,端口号等实现对后端服务器的负载均衡
七层负载均衡
在应用层上
检查 HTTP 协议,解析 HTTP 报文里的 URI,主机名,资源类型等数据,再用适当策略转发给后端服务器
TCP/IP 协议栈工作方式
应用层: 将要传输的数据打包,HTTP协议为它加一个 HTTP 专用附加数据,也就是HTTP 正文, HTTP 头
传输层/ TCP 层: 在数据包上再包装一层,加上 TCP 头
网际层/IP 层: 再加一层 IP 地址
链接层/MAC 层: 加一层 MAC 地址
区分四层和七层的方法
凡是由操作系统负责处理的就是四层或四层以下
凡是需要应用程序负责的就是七层
域名里的门道
域名的形式
域名是有层次的
用一串"."来分割
最右边的是"顶级域名"依次递减叫"二级域名","三级域名"
最左边的是主机名,通常表明主机的用途,
如"www" 表示提供万维网服务
"mail" 提供邮件服务
如"www" 表示提供万维网服务
"mail" 提供邮件服务
域名的作用
代替 IP 地址
用来标识虚拟主机
在 Apache、Nginx 的web 服务器里,通过指令决定由哪个虚拟主机来对外服务
在 Apache、Nginx 的web 服务器里,通过指令决定由哪个虚拟主机来对外服务
作为一种身份标识
因为域名是独一无二的,所以可以通过域名来划分出不同国家,地区,组织,公司,部门等
因为域名是独一无二的,所以可以通过域名来划分出不同国家,地区,组织,公司,部门等
域名的解析
IP 必须转换成 MAC 地址才能访问主机,域名也必须转换成 IP 地址,这个转换的过程就叫 "域名解析"
DNS
域名解析必须通过 DNS
DNS 的核心是一个三层的树状、分布式服务
根域名服务器(Root DNS Server)
管理顶级域名的服务器 ".com .cn .org"
顶级域名服务器(Top-level DNS Server)
管理各自域名下的权威域名服务器,如 com 顶级域名服务器可以返回 Google.com 域名服务器的 IP 地址
权威域名服务器(Authoritative DNS Server)
管理自己域名下主机的 IP 地址,如 Google.com 权威域名服务器可以返回 www.Google.com 的 IP 地址
DNS 如何更快速,更稳定的提供结果
缓存
DNS 分为几种
缓存,系统缓存,浏览器缓存
权威域名服务器
某个域名如何解析我说了算
非权威域名服务器("野生" DNS 服务器)
这里的非权威服务器指的是公司,网络运行商自己建立的 DNS 服务器
需要访问 DNS 解析系统去获取各个域名的解析结果
HOST 文件映射
Linux 在 /etc/hosts
windows 在 c:\windwos\system32\drivers\etc\hosts
域名解析过程
浏览器缓存
找到了,直接请求
找不到,去操作系统DNS缓存服务中找
操作系统DNS缓存
在系统缓存中查找
找到了返回并同步缓存
找不到,去找 Hosts 文件
HOSTS 文件
找到了,逐层返回并同步
找不到,去非权威域名服务器找
特殊文件,不缓存任何 DNS,除非手动添加
非权威域名服务器
找到了,逐层返回并同步
找不到,去根域名服务器
根域名服务器
找到域名对应的顶级服务器,如 Google.com,去找".com"服务器
顶级域名服务器
通过顶级域名来 "google.com",如果找到了,
就去二级域名查看(这里去不去二级域名服务器主要是看有没有主机名)
没有主机名就逐层返回并且逐层同步缓存
没找到,逐步返回报错信息
二级域名服务器
如果有主机名,才会来这里查找
如果找到了,去权威域名服务器
找不到,逐层返回错误信息
权威域名服务器
解析域名对应的 IP 地址,返回,并逐层更新缓存
域名的新玩法
一、重定向
当主机需要迁移,下线的时候,更改 DNS 记录,让域名指向其它主机
二、内部 DNS 服务器
通过 bind9 等开源软件搭建内部 DNS ,这样,内部服务就可以用域名来标记
三、基于域名实现负载均衡
1、给域名绑定多个 IP 地址,让客户端自己使用轮询算法依次向服务器发请求,实现负载均衡
2、域名解析配置内部策略,返回离客户端最近的主机,或者返回当前服务质量最好的主机
四、域名屏蔽
对域名直接不解析,返回错误,让你无法拿到 IP 地址
五、域名劫持
也叫域名污染,需要访问 A 网站,但 DNS 给了 B 网站
如 hosts 中 把某些软件的地址给设置成 127.0.0.1
Http请求的流程
使用 IP 地址访问 Web 服务器
一、建利 TCP 连接
地址栏输入 "127.0.0.1"
浏览器使用 52085 端口,服务器使用 80 端口
浏览器依照 TCP 协议规范,使用"三次握手"建利与 web 服务器的连接
三次握手依次是 SYN 、SYN/ACK、ACK 三个包
浏览器与服务器的 TCP 连接建立起来了
二、HTTP 通过 TCP 发送 "GET/HTTP/1.1" 请求报文
这里浏览器也是按照 HTTP 协议规定的格式,通过 TCP 发包
三、Web 服务器 TCP 协议层回复浏览器 ACK 确认
这个回复是在 TCP 协议层面的,这个 TCP 包 HTTP 协议是看不见的
四、Web 服务器收到报文后再内部处理这个请求
Web 服务器依据 HTTP 协议规定解析报文,查看浏览器请求要干啥
五、把根据浏览器的请求将需要的东西发回去
通过 TCP 发送 "HTTP/1.1 200 OK"
通过 TCP 发送 "HTTP/1.1 200 OK"
这里是需要把对应的文件读出来,再拼成符合 HTTP 格式的报文
六、浏览器给服务器回复 TCP 的 ACK 确认收到
七、浏览器解析报文,渲染页面
八、四次挥手断开 TCP 连接
四次挥手依次是
浏览器向服务器发送 FIN 断开信号
服务器发送 ACK 确认
服务器发送 FIN 断开信号
浏览器向服务器发送 ACK 确认
使用域名访问 Web 服务器
使用域名访问服务器只是在 IP 地址访问Web 服务器的基础上增加了域名解析
真实网络环境中
通过某种介质连接网络
网络运用商给你得设备分配 IP,可能是动态的,可能是静态的
请求网站,进行域名解析
在 DNS 解析可能会给出 CDN 服务器的 IP 地址
所有的请求先去请求 CDN 服务器,把有的数据拿到,把CDN 无法缓存的数据通过服务器拿到
CDN 会缓存网站大部分资源
这个时候可能还有负载均衡设备
如四层的 LVS 或者七层的 Nginx
负载均衡设备先访问系统里的缓存服务器,如果缓存服务器里也没有,负载均衡设备才把请求转发给应用服务器
最后网站的响应数据原路返回到浏览器
HTTP 报文
工作流程
一发一收的模式
报文结构
起始行(start line)
描述请求或响应的基本信息
请求行
在浏览器中叫 General
头部字段集合(header)
响应头(数据返回的时候才会有) Response Headers
请求头(发送的时候才会有) Requests Headers
消息正文(entity)
实际传输的数据,不一定是纯文本,也可以是图片,视频等二进制数据
又叫body
协议规定
HTTP 协议规定报文必须有 header
可以没有 body
header 后面必须有一个"空行","CRLF",十六进制的"0D0A"
实际数据查看分析
起始行
GET / HTTP/1.1
响应头
Bdpagetype: 2
Bdqid: 0xc5464998000054ba
Cache-Control: private
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html;charset=utf-8
Date: Tue, 30 Nov 2021 13:59:18 GMT
Expires: Tue, 30 Nov 2021 13:59:17 GMT
Server: BWS/1.1
Set-Cookie: BDSVRTM=581; path=/
Set-Cookie: BD_HOME=1; path=/
Set-Cookie: H_PS_PSSID=35359_35104_31660_34902_34584_34504_35234_34872_35328_35315_26350_35145_22160; path=/; domain=.baidu.com
Strict-Transport-Security: max-age=172800
Traceid: 1638280758033839156214215130191025427642
X-Frame-Options: sameorigin
X-Ua-Compatible: IE=Edge,chrome=1
Transfer-Encoding: chunked
Bdqid: 0xc5464998000054ba
Cache-Control: private
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html;charset=utf-8
Date: Tue, 30 Nov 2021 13:59:18 GMT
Expires: Tue, 30 Nov 2021 13:59:17 GMT
Server: BWS/1.1
Set-Cookie: BDSVRTM=581; path=/
Set-Cookie: BD_HOME=1; path=/
Set-Cookie: H_PS_PSSID=35359_35104_31660_34902_34584_34504_35234_34872_35328_35315_26350_35145_22160; path=/; domain=.baidu.com
Strict-Transport-Security: max-age=172800
Traceid: 1638280758033839156214215130191025427642
X-Frame-Options: sameorigin
X-Ua-Compatible: IE=Edge,chrome=1
Transfer-Encoding: chunked
请求头
Host: www.baidu.com
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.55 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: PSTM=1610960062; BD_UPN=123253; BIDUPSID=413D02B41275409B2F813A64AAEE746C;
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.55 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: PSTM=1610960062; BD_UPN=123253; BIDUPSID=413D02B41275409B2F813A64AAEE746C;
消息正文
因为是个 GET 请求,所以没有消息正文
请求行(request line)
结构
请求方法
如 GET/POST,表示对资源的操作
请求目标
通常是 URI,标记了请求方法要操作的资源
版本号
表示报文使用的 HTTP 协议版本
这三个部分使用空格分隔,最后要用 CRLF 换行表示结束
示例
GET / HTTP/1.1
"GET" 是请求方法
"/" 表示请求目标
"HTTP/1.1"表示 HTTP 的版本号
连起来就是,通过 GET 请求获取根目录下的默认文件,并且用 HTTP1.1 版本通信
状态行(status line)
服务器响应的状态
出现在响应头
结构
版本号
报文的 HTTP 版本号
状态码
一个三位数,用代码表示状态,200 成功,500 服务器错误等
原因
数字装填码的补充,是更详细的解释文字
示例
HTTP/1.1 200 OK
HTTP/1.1 404 Not Found
头部字段
头部字段名不区分大小写
字段名里不允许出现"空格,下划线'_' ",可以使用连字符"-"
字段名后面必须紧接着":",不能有空格,":"后的字段值可以有多个空格
字段顺序没有意义
有的网站反爬会做字段名排序检查
字段原则上不能重复,除非这个字段本身的语义允许,如 Set-Cookie
常用头字段
通用字段
在请求头和响应头里都可以出现的
Date
请求字段
仅能在请求头中出现
如 Cookie,Host,user-Agent 等字段
响应字段
仅能出现在响应头中
Server 非必要出现的字段
实体字段
实际上属于通用字段,但专门描述 body 的额外信息
小问题
在拼 HTTP 报文的时候,如果在头字段后面多加一个 CRLF,导致出现了空行,则 body 为空行
头字段":"后面可以有多个空格,但是一般只用一个,因为每多加一个空格,报文里将多一个传输的字节
请求方法
HTTP/1.1 规定了八种方法,单词都必须是大写的形式
常用方法
GET
从服务器获取资源
HEAD
获取资源的元信息
服务器不会返回请求的实体数据,只会传回响应头,这就是"元信息"
想要检查一个文件是否存在,发个 HEAD 请求就可以了
POST
向资源提交数据,相当于上传,写入
表示新建 "Create"的含义
数据放在报文的 body 里
PUT
类似于 POST
UPDATE 的含义
非常用方法
DELETE
删除资源
CONNECT
建利特殊的连接隧道
类似于代理IP 中的隧道代理
OPTIONS
列出可对资源实行的方法
在响应头的 Allow 字段里返回,功能有限,用处不大
TRACE
追踪请求- 响应的传输路径
多用于 HTTP 链路的测试或诊断,可以显示出请求 - 响应的传输路径
存在漏洞,会泄漏网站信息,所以 web 服务器通常也是禁用的
扩展方法
MKCOL
COPY
MOVE
LOCK
锁定资源暂时不允许修改
UNLOCK
删除锁
PATCH
给资源打小补丁,部分更新数据
安全与幂等
安全
在HTTP协议里,所谓的安全,是指请求方法不会对服务器上的资源造成实质的修改,所以只有GET和HEAD是安全的,因为是只读操作。
幂等
概念:多次执行相同的操作,结果也都是相同的
GET和HEAD 即是安全的也是幂等的
DELETE可以多次删除同一个资源,效果都是“资源不存在”,所以也是幂等
POST是新增或提交数据,多次提交会创建多个资源,所以不是幂等的
PUT是替换或更新数据,多次更新一个资源,资源还是第一次更新的状态。所以是幂等的
非幂等
POST
URI
概念
URI 全称叫统一资源标识符
本质上是一个字符串,这个字符串的作用是唯一地标记资源的位置或者名字
URL 全称叫统一资源定位符
URI 有两个子集,
URI 的格式
scheme :// host:port path ?query
基本组成
scheme
协议名
资源应该用那种协议访问
://
特定字符
作用是分隔 协议名 和后面部分
authority
资源所在的主机名
通常以"host:port"形式存在,即主机名加端口号
不写端口号默认 80 或者 443
path
路径
标记资源所在位置
采用了 unix 的风格
path 部分必须以"/"开始,也就是必须包含"/"
query
查询参数
以"?"开始,但不包含问号,这里的"?"和"://"一样,是分隔符
有自己的查询格式,多个"key=value"字符串
KV 值用字符"&"连接
http://www.chrono.com:8080/11-1?uid=1234&name=mario&referer=xxx
例子
http://nginx.org
主机名: Nginx.org
端口号省略,默认是 80 端口
路径被省略了,默认就是一个"/",表示根目录
http://www.chrono.com:8080/11-1
主机名: "www.chrono.com"
端口号: "8080"
路径: "/11-1"
https://tools.ietf.org/html/rfc7230
主机名: "tools.ietf.org"
端口号: 默认的"443"
路径: "/html/rfc7230"
file:///D:/http_study/www/
协议名:"file" , 表示本地文件
"://" 分隔符
路径: "/D:/http_study/www/"
file 协议是一种特殊协议,允许省略主机名,默认是本机 localhost
http://www.chrono.com:8080/11-1?uid=1234&name=mario&referer=xxx
协议名: "http"
主机名:"www.chrono.com"
端口号:"8080"
路径:"/11-1"
查询参数:"name=mario&referer=xxx"
URI 的完整格式
完整格式和基本格式的区别
一、身份信息 "user:passwd@"
根据 RFC7230 ,现在不推荐这种形式
暴露了敏感信息,存在安全隐患
二、片段标识符 "#fragment"
URI 所定位的资源内部的一个"锚点","标签"
浏览器可以在获取资源后直接跳转到它指示的位置
仅能浏览器这样的客户端使用,服务器看不到
不会把 "#fragment" 发送给服务器
URI 编码
escape(目前已被废除)
俗称转义
URI 中只能使 ASCII 码
把非 ASCII 字符或特殊字符转换成十六进制字节值,并且前面加上一个"%"
中文,日文等使用 UTF-8 编码后再转义
encodeURI
把字符(unicode)编码成 utf-8,然后再每个字节转换成 16 进制,并在前面用"%"连接
例子
空格
%20
"?"
"%3F"
"银河"
“%E9%93%B6%E6%B2%B3”
后记
为啥浏览器上看不到转义后的"乱码"
其实是可以看到的,浏览器会友好一点,隐藏 URI 编码后的结果
响应状态码
RFC 标准里规定的状态码是三位数,所以 0~99 不使用
RFC 标准里目前有 41 个错误码
状态码的定义是开放的,允许自行扩展
状态码的含义不仅是错误,更重要的意义在于表达 HTTP 数据处理的"状态"
具体的错误还是需要看错误信息的
比如前段时间 github 证书问题,报错 503 ,错误信息是证书过期
这里的 503 只是表示了 HTTP 的状态是服务器拒绝了你得请求,但是为什么拒绝,就是因为 github 的 ssl 证书到期了,这次请求不安全
分类
1xx:
提示信息,表示目前是协议处理的中间状态,还需要后续的操作
101
"101 Switching Protocols"
客户端使用 Upgrade 头字段,要求在 HTTP 协议的基础上改成其它的协议继续通信
如果服务器也统一变更协议,就会发送状态码 101,之后的数据就不再使用 HTTP 了
2xx:
成功,报文已经收到并被正确处理
200 OK
最常见的,表示一切正常
如果是非 HEAD 请求,会在响应头后有 body 数据
204 No Content
基本上和 200 一样,响应头后没有 body 数据
要正确的和 200 区分
206 Partial Content
常用于分块下载或者断点续传
客户端发送"范围请求",要求资源的部分数据时出现
和 200 一样,也是表示服务器成功处理了请求
body 里不是资源的全部,只是一部分
3xx:
重定向,资源位置发生变化,需要客户端重新发送请求
301 Moved Permanently
永久重定向,此次请求的资源已经不存在,需要改用新的 URI 再次访问
比如 HTTP 切换到 HTTPS
浏览器会存在缓存
302 Found
临时重定向,此次请求的资源还在,但需要暂用另一个 URI 来访问
比如晚上系统维护,服务暂不可用,需要指向静态通知页面,第二天提供服务还是会访问原来地址
不会做缓存优化
303 See Other
类似 302
但要求重定向后的请求改为GET方法
避免POST/PUT重复操作
304 Not Modified
缓存重定向
用于 If-Modified-Since等条件请求,表示资源未修改
用于缓存控制
可以理解成"重定向已到缓存的文件"
307 Temporary Redirect
类似302
但重定向后请求里的方法和实体不允许变动,含义比302更明确
308 Permanent Redirect
类似307
不允许重定向后的请求变动,但它是301“永久重定向”的含义
4xx:
客户端错误,请求报文有误,服务器无法处理
400 Bad Request
通用错误码,请求报文有错误
不知道具体错误内容,只是一个笼统错误
服务端应尽量避免给客户端返回 400
403 Forbidden
服务器禁止访问资源
非客户端错误
404 Not Found
资源在本服务器上未找到
405 Method Not Allowed
不允许使用某些方法操作资源
例如: 只能 GET 不允许 POST
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
请求头某个字段或总体太大
5xx:
服务器错误,服务器在处理请求时内部发生错误
500 Internal Server Error
类似于 400,说了,但是好像又没说
501 Not Implemented
客户端请求的功能还不支持
502 Bad Gateway
服务器作为网管或者代理时返回的错误,具体原因看返回的错误信息
503 Service Unavailable
服务器当前很忙,暂时无法响应服务
"临时"状态
响应报文里通常有个"Retry-After"字段,指示客户端多久后再次尝试发送请求
HTTP 的特点
灵活可扩展
HTTP 协议是一个“灵活可扩展”的传输协议
可靠传输
HTTP 协议是一个“可靠”的传输协议。
基于 TCP/IP,而 TCP 本身就是一个"可靠"的传输协议
具体做法和 TCP/UDP 差不多,对实际传输数据包装,再加上一个头,然后调用 socket API,通过 TCP/IP 协议栈发送或接收
"可靠"只是面向使用者的一个承诺,下层会用多种手段"尽量"保证数据的完整
如果要 100%保证数据收发成功,就不能使用 HTTP 或者 TCP 协议,而是要用各种消息中间件
应用层协议
HTTP 协议是一个应用层的协议
请求-应答
HTTP 协议使用的是请求 - 应答通信模式
就是一发一收,有来有去
类似于程序中的函数,调用后给回复
明确了 HTTP 协议里通信双方的定位,请求方先发起连接和请求,是主动的,应答方只有在接收到请求后才能回复,是被动的,没有请求时不会有任何动作
请求应答方角色不是绝对的,代理服务器就是一个既是请求方,又是应答方
向目标服务器请求,对客户端做出应答
完全符合 RPC(Remote Procedure Call) 工作模式
把 HTTP 请求处理封装成远程函数调用
产物
WebService
RESTful
gRPC
无状态
HTTP 协议是无状态的
每个请求都是互相独立、毫无关联的,协议不要求客户端或服务器记录请求相关的信息。
例如: 浏览器向服务器请求一个有权限的文件,第一次服务器需要检查请求的权限,返回文件,当第二次想服务器请求文件的时候,服务器是不会记录第一次的请求状态的,所以第二次请求还是需要检查请求的权限
"无状态"特点与响应头里的"状态码" 是两个概念
其它特点
传输的实体数据可以缓存,可以压缩
可分段获取数据
支持身份认证
支持国际化语言等
HTTP 优缺点(HTTP/1.1)
优点
简单、灵活、易于扩展
HTTP 最大的优点是简单、灵活和易于扩展;
简单: header+body 都是简单的文本格式,常用的英文单词,降低了学习难度和使用门槛
灵活和易于扩展:
允许开发者任意定制、扩充或解释
不限制具体的下层协议
可以使用 TCP、UNIX Domain Socket、SSL/TLS、UDP、QUIC
应用广泛、环境成熟
HTTP 拥有成熟的软硬件环境,应用的非常广泛,是互联网的基础设施
从简单的 web 页面到 json、XML 数据
从台式机浏览器上到 APP、新闻、论坛、游戏等等
天然具有"跨语言、跨平台",不局限于某种特定语言或者操作系统
无状态
服务器不需要额外资源来记录状态信息
没有"状态"差异,容易组成集群,让负载均衡把请求转发到任何一台服务器
轻松实现高并发
缺点
无状态
HTTP 是无状态的,可以轻松实现集群化,扩展性能,但有时也需要用 Cookie 技术来实现“有状态”;
服务器没有"记忆能力",无法支持需要连续多个步骤的"事务"操作
例如: 电商购物,登录,添加购物车,下单,结算,支付这个流程每次请求都需要检查用户的身份信息
明文
HTTP 是明文传输,数据完全肉眼可见,能够方便地研究分析,但也容易被窃听
HTTP 报文的所有信息暴露
容易伪造
不安全
HTTP 是不安全的,无法验证通信双方的身份,也不能判断报文是否被窜改;
缺少身份认证和完整性校验
HTTPS 解决了这一点
性能
HTTP 的性能不算差,但不完全适应现在的互联网,还有很大的提升空间
关键在于"请求-应答"
当顺序发送的请求序列中有一个请求因为某种原因被阻塞,之后的所有请求都被阻塞
官方为了决绝这个问题,在 HTTP 官方标准里有"缓存"一章(RFC7234)
现有解决方案: HTTP/2 和 HTTP/3
HTTP 的实体数据
数据类型与编码
如果在数据传输过程中不告诉服务器或者浏览器当前传输的数据是什么格式的,那么需要浏览器或者服务器去猜,这种方法很低效,而且大概率会检查不出来文件类型
MIME( Multipurpose Internet Mail Extensions) 多用途互联网邮件扩展
在 HTTP 诞生之前就有了解决方案,应用于电子邮件系统里,让电子邮件可以发送 ASCII 码以外的任意数据
HTTP 只是使用了一部分 MIME 标准,用来标记 body 的数据类型
通常被叫做 MIME type
分类
text
文本格式的可读数据
image
图像文件
audio/video
音频和视频数据
application
数据格式不固定,可能是文本也可能是二进制
必须由上层应用程序来解释
application/json,application/javascript、application/pdf
压缩
gzip
GNU zip 压缩格式,目前最流行的压缩格式
deflate
zlib(deflate)压缩格式,流行程度仅次于 gzip
br
一种专门为 HTTP 优化的新压缩算法 (Brotli)
数据类型使用的头字段
Accept
请求头中
作用: 标记客户端可理解的 MIME type
可以用 "," 分隔列出多个类型,让服务器选择
Accept: text/html,application/xml,image/webp,image/png
Accept-Encoding
请求头中
标记客户端支持的压缩格式
可以给服务端传多个,服务端选一个
如果请求头中没有这个字段,表示客户端不支持压缩数据
Accept-Encoding: gzip, deflate, br
Content-Type
响应头中
作用是告诉浏览器,实体数据的真实类型
Content-Type: text/html
Content-Type: image/png
Content-Type: image/png
Content-Encoding
响应头中
标记服务器返回的数据压缩格式
只能选择客户端传过来的其中一种
Content-Encoding: gzip
语言类型与编码
语言类型,使用"-"分隔符
en-US, en-GB, zh-CN
语言类型使用的头字段
用于客户端和服务器语言与编码"内容协商"
Accept-Language
请求头中
标记客户端可理解的自然语言
允许用","分隔
Accept-Language: zh-CN, zh, en
Accept-Charset
字符集在 HTTP 里的使用字段
请求头中
Accept-Charset: gbk, utf-8
Content-Language
响应头中
告诉客户端实体数据使用的实际语言类型
Content-Language: zh-CN
Content-Type
在响应头里包含了 body 的编码格式
响应头里
Content-Type: text/html; charset=utf-8
一般不会发送Accept-Language这个字段,因为现在浏览器都支持多种字符集,而服务器也不会发送 Content-Language
请求头中一般只会有 Accept-Language 字段
响应头里一般只会有 Content-Type 字段
Content-Type: text/html; charset=utf-8
内容协商质量值
在 Accept、Accept-Encoding、Accept-Language 等请求字段进行内容协商的时候,可以用特殊的"q"参数表示权重来设定优先级
q=quality factor
权重最大值时 1,最小值是 0.1
默认权重是 1, 0 表示拒绝
在数据类型或语言代码后加";"分隔
在 HTTP 里","的分隔意义比";"大
Accept: text/html,application/xml;q=0.9,*/*;q=0.8
意思是它表示浏览器最希望使用的是 HTML文件,权重是 1
其次是 XML 文件,权重是 0.9
最后是任意数据类型,权重是 0.8
Vary
vary 字段表示服务器依据什么字段,然后决定发回的响应报文
Vary: Accept-Encoding,User-Agent,Accept
表示依据Accept-Encoding,User-Agent,Accept这三个字段来发回响应报文
响应报文的特殊的"版本标记"
HTTP 传输大文件的方法
数据压缩
请求头中添加 Accept-Encoding 字段
支持 gzip、deflate、br
缺点: gzip 等压缩算法通常只对文本文件有较好的压缩率
音频,图片等多媒体本身就已经经过高度压缩了,再压缩也没啥用
分块传输
把大文件拆开发送给浏览器,浏览器收到后再组装复原
"chunked"分块传输编码
响应头中用字段 "Transfer-Encoding: chunked" 来表示
意思是报文里的 body 部分不是一次性发过来的,而是分块逐个发送的
表示 body 数据的长度未知
Content-Length
表示 body 里数据长度是已知的
在响应头中 "Transfer-Encoding: chunked" 和 "Content-Length" 是互斥的
要嘛有明确的数据长度,用 Content-Length
要嘛未知数据长度,用 Transfer-Encoding:chunked
分块传输的编码规则
采用了明文,类似响应头
具体规则
每个分块包含两部分,长度头和数据块
长度头是以 CRLF (回车换行,即\r\n) 结尾的一行明文,用 16 进制数字表示长度
数据块紧跟在长度头后,最后夜用 CRLF 结尾,但数据不包含 CRLF
最后一个长度为 0 的块表示结束,即"0\r\n\r\n"
范围请求
由客户端发送请求
请求头中使用 Range 字段
Range: bytes=x-y
x,y 表示请求范围
x,y 都是可以省略的
假设文件是 100 字节
"0-"表示从起点到文档终点
"10-"表示从第 10 个字节开始到文档末尾
"-1"表示文档的最后一个字节
"-10"表示从文档的倒数 10 个字节到末尾,相当于"90-99"
服务器收到 Range 字段后的四件事
第一,检查范围是否合法,避免越界
越界返回状态码 416
Range: bytes=0-31
第二,根据 Range 头计算偏移量,读取文件的片段
返回状态码 206 Partial Content
第三, 添加一个响应头字段 Content-Range,告诉客户端片段的实际偏移量和资源的总大小,
格式 bytes x-y/length
Content-Length: 32
Accept-Ranges: bytes
Content-Range: bytes 0-31/96
Accept-Ranges: bytes
Content-Range: bytes 0-31/96
第四,发送数据
响应头中返回两个字段
Accept-Ranges:bytes
当Accept-Ranges 的值时 bytes,那么当前资源支持范围请求
Accept-Ranges:none
当Accept-Ranges 的值时 none,那么当前资源不支持范围请求
Content-Length: 146515
返回范围请求的具体长度
断点续传也是基于范围请求来实现的
断点续传的要点
先发个 HEAD,查看服务器是否支持范围你请求,同时获取文件大小
开 N 个线程,每个线程使用 Range 字段划分出各自负责下载的片段,发请求传输数据
下载意外中断,使用 Range 请求剩下的一部分就可以
多段数据
在 Range 头里使用多个"x-y"的范围,一次性获取多个片段数据
需要使用热书的 MIME 类型: "multipart/byteranges"
表示报文的 body 是由多段字节序列组成
还需要一个参数"boundary=xxx"给出段之间的分隔标记
例子
请求
GET /16-2 HTTP/1.1
Host: www.chrono.com
Range: bytes=0-9, 20-29
Host: www.chrono.com
Range: bytes=0-9, 20-29
响应
HTTP/1.1 206 Partial Content
Content-Type: multipart/byteranges; boundary=00000000001
Content-Length: 189
Connection: keep-alive
Accept-Ranges: bytes
--00000000001
Content-Type: text/plain
Content-Range: bytes 0-9/96
// this is
--00000000001
Content-Type: text/plain
Content-Range: bytes 20-29/96
ext json d
--00000000001--
Content-Type: multipart/byteranges; boundary=00000000001
Content-Length: 189
Connection: keep-alive
Accept-Ranges: bytes
--00000000001
Content-Type: text/plain
Content-Range: bytes 0-9/96
// this is
--00000000001
Content-Type: text/plain
Content-Range: bytes 20-29/96
ext json d
--00000000001--
这里的"--00000000001"是多段分隔符
HTTP 的连接管理
服务器端通常不会主动关闭连接
短连接
概念: 基于 TCP/IP,每次发送请求需要先与服务器建利连接,收到响应报文后立即关闭连接
常见于 HTTP/1.0 及以前
又叫做"无连接"
缺点: 在需要多次请求的场景中非常的耗时
原因: 每发一次请求,都需要三次握手和四次挥手
长连接
为了解决"短连接"带来的缺点,所以 HTTP 协议提出了"长连接"通信方式
又叫"持久连接(persistent connections)"、"连接保活(keep alive)"、"连接复用(connection reuse)"
常见于 HTTP/1.1 及以后版本
思路: 成本均摊,将 TCP 的连接和关闭消耗时间均摊到多个"请求-应答"上
缺点: "长",TCP 连接长时间不关闭,将占用服务器资源在内存中保存它的装填,如果有大量的空闲长连接只连不发,会很快耗尽服务器的资源
解决办法:
在请求头里加上"Connection:close" 字段,服务器看到这个字段,会在响应头里也加上这个字段,发送后调用 Socket API 关闭 TCP 连接
Nginx 中
使用"keepalive_timeout"指令,设置长连接的超时时间,在一段时间内连接上没有任何数据收发就主动断开连接
使用"keepalive_requests"指令,设置长连接上可发送的最大请求次数
如设置成1000,当 Nginx 在这个连接上处理了 1000 个请求后,也会主动断开连接
以上两个可以分开,也可以一起用
使用 "keepalive_timeout 60" 和 "keepalive_requests 5" 表示,空闲连接最多 60 秒内关闭或者最多发送 5 个请求关闭
长连接和短连接的比较
连接相关的头字段
HTTP/1.1
HTTP/1.1 中的连接默认启动长连接,不需要特殊头字段指定
只要向服务器发送一次请求,后续的请求都会重复利用第一次打开的 TCP 连接,也就是长连接
也可以使用 Connection: keep-alive
如果服务器支持长连接,会在响应头中返回 "Connection: keep-alive"
断开可以在最后一个请求头中添加"Connection:close"字段
HTTP/1.0 及以下
请求头中
请求头中添加"Connection:keep-alive"字段
断开可以在最后一个请求头中添加"Connection:close"字段
队头阻塞
Head-of-line blocking 也叫"队首阻塞"
原因:
由 HTTP 的"请求-应答" 模型所致
因为 HTTP 规定报文必须是"一发一收",形成了一个先进先出的队列
队列里的请求没有优先级,只有入队的先后顺序
先进来的请求处理太慢,后面的请求全部阻塞等待
性能优化
队头阻塞问题在 HTTP/1.1 里无法解决,只能缓解
并发连接
对同一域名发起多个长连接,用数量解决质量问题
RFC2616 里明确了每个客户端最多并发 2 个连接,被众多浏览器无视,后来在修订 RFC7230 中取消了
目前浏览器中使用 6-8 个连接
域名分片
还是用数量来解决质量的思路
多开域名,将多个域名指向同一服务器
每次请求访问多个域名,都会指向同一服务器,这样就算是提高了并发量
HTTP 的重定向和跳转
跳转
主动跳转
由浏览器的使用者主动发起的,可以称为"主动跳转"
如: 点击网页上的一个超链接
被动跳转
由服务器来发起的,浏览器使用者无法控制的跳转
HTTP 协议里叫做"重定向"(Redirection)
重定向
重定向的过程
请求返回状态码 302/301
响应头里面标记了"Location: URI"
Location 字段属于响应字段,必须出现在响应头中
只有配合 "302/301"状态码才有意义
标记了服务器要求重定向的 URI
既可以是绝对路径
也可以是相对路径
只能在站内跳转使用
站内重定向和站外重定向的区别
站内重定向地址栏的地址不会发生显式变化,但是浏览器会隐式的去请求并把结果显示在浏览器中
站外重定向
第一个是浏览器肯定是显式变化,uri 地址会发生改变,浏览器会显式请求
重定向状态码
301
永久重定向
302
临时重定向
303
类似 302,但重定向后的请求改为 GET 方法
避免 POST/PUT 重复操作
307
类似 302, 但重定向后请求里的方法和实体不允许变动
含义比 302 更明确
308
类似 307,不允许重定向后的请求变动
是 301 的含义
应用场景
资源不可用,需要用另一个新的 URI 来代替
域名变更
服务器变更
网站改版
系统维护
避免重复
某个网站有多个域名,让这多个网址都跳转到同一个 URI
让多个域名重定向到主站
在决定要用重定向后一定要考虑是用 301 还是 302
重定向的相关问题
性能损耗
重定向机制会有多次"请求-应答"
重定向需要适度使用,不能滥用
循环跳转
如果重定向策略欠考虑,可能会形成 A=>B=>C=>A
HTTP 协议特别规定,浏览器必须具有检测"循环跳转"的能力
HTTP 的 Cookie
Cookies 的作用
Cookie 是服务器委托浏览器存储的一些数据,让服务器有了"记忆能力"
Cookies 的工作过程
响应头字段 Set-Cookie
用于服务器给浏览器发送身份标识
可以多行
请求头字段 Cookie
用于身份甄别
多个字段使用";"分隔
Cookie 的属性
作用:防止外泄或窃取
方法:
设置 Cookie 的生存周期
设置字段
Expires
过期时间
Max-Age
相对时间,单位是秒
两个可以同时出现,失效时间可以一致也可以不一致
浏览器优先采用 Max-Age 计算失效期
如果不指定这两个字段,那么 Cookie 仅在浏览器运行时有效,一旦浏览器关闭就会失效
设置 Cookie 的作用域
让浏览器仅发送给指定的服务器和 URI,避免被其他网站盗用
Domain
从 URI 中提取 host
Path
从 URI 中提取 path,一般为了省事,用"/"或者省略
例子
Cookie: JSESSIONID=rVLCnVAjcFkkxgECAKHyUOqSPHM0l41ji9iOXxm93QowaTs7MgKg!-2091789827;domain=search.baidu.com; path=/; HttpOnly
Cookie 的安全性
为了不让服务器以外的人看到
HttpOnly
此 Cookie 只能通过 HTTP 协议传输,禁止其他方式访问
浏览器 JS 会禁用 document.Cookie 的一切相关 API
SameSite
可以防范"跨站请求伪造"(SXRF)攻击
SameSite=Strict
设定严格,Cookie 不能随着跳转链接跨站发送
SameSite=Lax
设定宽松,允许 GET/HEAD 等安全方法
禁止 POST 跨站发送
Secure
当前 Cookie 仅能用 HTTPS 协议加密传输,明文 HTTP 协议会禁止发送
浏览器中 Cookie 还是明文的
浏览器中查看方法
chrome -> F12 -> Network -> Cookies
Cookie 的应用
身份识别
保存用户登录信息,实现回话事务
状态保持
广告跟踪
广告网站会给你贴上 Cookie 标签,(这里的 Cookie 标签是第三方 Cookie,就是广告商的 Cookie)这样,上其它网站,别的广告就能用 Cookie 读到你得身份,然后进行行为分析,再精确的给你推广告
如果广告商势力比较大,这样你走到哪里广告都会精准推送
为了防止 Cookie 搜集用户隐私,互联网组织提出 DNT(Do Not Track) 和 P3P(platform for Privacy Preferences Project) 但是作用不大
Cookie 不属于 HTTP 标准
Cookie 标准在 RFC6265,而不是 RFC2616/7230
RFC2616/7230 是 HTTP 标准
HTTP 的缓存控制
需要缓存的原因
因为链路漫长,网络延时不可控,浏览器使用 HTTP 获取资源成本较高
服务器的缓存控制
流程
1、浏览器发现缓存无数据,发送请求,向服务器获取资源
2、服务器响应请求,返回资源,同时标记资源的有效期
响应头字段 cache-control:max-age=30
3、浏览器缓存资源,等待下次重用
响应头字段
Cache-Control: max-age=31536000
浏览器只能缓存当前页面 31536000 秒
标记资源有效期
max-age “生存时间”
no-store
不允许缓存
no-cache
可以缓存,但使用之间必须去服务器验证是否过期,是否有新版本
must-revalidate
缓存不过期可以继续使用
过期了还想用必须去服务器验证
客户端的缓存控制
如果浏览器不想使用缓存,而是想请求最新的资源
在请求头添加“Cache-Control:max-age=0”或者“no-cache”
意思是请求一个最新资源
在 HTTP/1.0 中,使用 "Pragme: no-cache" 效果和 Cache-Control 一致
这个头字段只支持 HTTP/1.0
强制刷新:
windows:Ctrl+F5
Mac:cmd+shift+r
其实就是发送了一个“Cache-Control:no-cache”
当在 Starts Code 字段发现有“from disk cache”,表明这个资源是缓存资源
条件请求
作用
如果验证资源是否失效,发送多个验证比较消耗资源
这个在head里增加“条件请求”字段,专门用来验证资源是否过期,把多个请求合并成一个,降低资源消耗
使用字段
请求头
if-Modified-Since
只能用在 GET 和 HEAD 请求中
当前资源过了有效期,需要修改才用
如果资源已经发生了改变,返回状态码 200,并更新缓存
如果资源没有发生改变,返回 304,并更新资源有效期
与 If-None-Match 一起出现时,会被忽略掉,除非服务器不支持 If-None-Match
if-None-Match
当服务器上没有任何资源的 ETag 属性值与当前资源匹配时,服务器返回所请求资源,响应码为 200
GET 和 HEAD 请求验证失败时,服务器必须返回 304
意思是发送一个请求,去服务段验证资源是否发生变化,如果资源发生了变化,ETag 肯定对不上,这个时候服务器返回请求资源,返回状态码 200
当 ETag 对上了,表示资源没有发生变化,然后服务器返回 304,更新资源过期时间
如果使用了弱 ETag,那么两个人间仅仅在页脚的生成时间有所不同,那也可以认为二者是相同的
If-Unmodified-Since
只有当资源在过期时间之后没有进行修改的情况下,服务器才会返回请求的资源,或者接受 POST 等方法的请求
如果在过期时间之后发生了改变,返回 412
If-Match
GET 和 HEAD 下,服务器仅在请求的资源满足 ETag 值时才会返回资源
对于 PUT 或其它非安全方法,只有在满足条件的情况下才可以讲资源上传
PUT 方法用这个参数可以避免更新丢失问题
If-Range
当字段值中的条件满足时,range 头字段才会起作用,同时服务器回复 206 部分状态码,以及 range 字段请求的相应部分
如果字段值条件不满足,返回 200 状态码,并返回完整的请求资源
响应头
Last-modified
文件的最后修改时间
ETag
资源唯一标识
有强弱之分
弱 ETag 在值前有个 “W/”标记
强 ETag 必须完全一致
请求流程
HTTP 的代理服务
正常的 HTTP 模型通信只有两个角色,一个是“请求方”浏览器,另一个是“应答方”服务器
在引入了代理之后,HTTP 模型成了三个角色的
起点:客户端,浏览器
中间:代理服务器(proxy server)
终点:源服务器(origin server)
代理服务
服务本身不生成内容,而是处于中间位置转发上下游的请求和响应,具有双重身份
面对用户,表现为服务器
面对服务器,表现为客户
代理的作用
作用
欺上瞒下
即可以保护客户端,又可以保护服务端
功能
负载均衡
反向代理
有多台服务器时,只需要请求负载均衡服务器,然后由负载均衡服务器决定由哪台服务器来响应请求
负载均衡算法,如轮询,一致性哈希等
健康检查
使用“心跳”等机制监控后端服务器,发现有故障就及时“踢出”集群,保证服务高可用
安全防护
保护被代理的后端服务器,限制 IP 地址或流量,抵御网络攻击和过载
加密卸载
对外网使用 SSL/TLS 加密通信认证,而在安全的内网不加密,消除加解密成本
数据过滤
拦截上下行的数据,任意指定策略修改请求或响应
内容缓存
暂存、复用服务器响应
代理相关头字段
Via
通用字段
标明代理身份
只是解决了客户端和源服务器判断是否存在代理的问题,不能知道对方的真实信息
如果有多个代理服务器,那么请求经过多少个代理服务器,Via 字段后面会追加多少个代理标识
Via:proxy1,proxy2
有时候响应头里会使用“X-Via”,意义和 Via 相同
X-Forwarded-For
不属于 HTTP 标准,但是属于“事实标准”
意思是为谁转发
添加的是请求方的 IP 地址,最左边的 IP 就是源请求方的 IP 地址
X-Real-IP
另一种获取客户端真是 IP 的手段
作用:只记录客户端的 IP 地址,没有中间的代理信息
RFC7239 定义了字段“Forwarded”代替 X-Forwarded-For 和 X-Real-IP,但是应用的不多
代理协议
原因
因为 X-Forwarded-For 等头字段需要修改原始报文,在有些情况下是不允许甚至不可能被修改的,比如使用了 HTTPS 通信被加密
所以出现了专门的“代理协议”
属于“事实协议”
HAProxy 所定义
有两个版本 V1 和 V2
V2 是二进制格式
V1
V1 是明文,只是在 HTTP 报文前增加了一行 ASCII 码文本,相当于又多了一个头
格式
开头必须是“PROXY”五个大写字母
然后是“TCP4”或者“TCP6”表示客户端的 IP 地址类型
之后是请求方地址、应答方地址、请求方端口号、应答方端口号,最后用回车换行符(\r\n)结束
例子
PROXY TCP 192.168.12.108 192.168.12.105 5555 8080\r\n
GET / HTTP/1.1\r\n
Host:www.xxx.com\r\n
\r\n
GET / HTTP/1.1\r\n
Host:www.xxx.com\r\n
\r\n
代理协议不支持 X-Forwarde-For 的链式地址形式
HTTP 的缓存代理
支持缓存控制的代理服务
作用:减少响应时间,节约带宽,提升客户端的用户体验
服务端缓存适用于“读多写少”的数据
HTTP 的服务器缓存功能主要由代理服务器来实现
缓存代理服务
既是客户端,又是服务端
即不是客户端,又不是服务端
浏览器通过缓存代理服务器向源服务器发送请求,源服务器收到请求后原路返回,在原路返回过程中,缓存代理服务器保留一份报文到缓存,然后返回给浏览器
之后的所有相同的请求,都会由缓存代理返回报文
属于数据中转站
源服务器的缓存控制
在响应头字段增加属性
需要区分客户端缓存和代理缓存
Cache-Control 新属性
private
只能在客户端保存,不能放在代理上
例子 Cache-Control:private,max-age=5
禁止代理缓存,客户端缓存有效期 5 秒
public
缓存完全开放,谁都可以存,谁都可以用
例子:Cache-Control:public,max-age=5,s-maxage=10
代理上缓存有效期 10 秒,客户端有效期 5 秒
Last-modified 新属性
must-revalidate
过期就必须回源服务器验证
proxy-revalidate
代理的缓存过期后必须验证,客户端不必回源
只验证到代理这个环节
s-maxage
只限定在代理上能够存多久
客户端仍然使用 max-age
no-transform
代理专用的属性
禁止代理对缓存下来的数据做一些优化
比如:把图片生成 png、webp 等几种格式
例子:Last-modified: max-age=30,proxy-revalidate,no-transform
客户端、代理上缓存有效期都是 30 秒,代理缓存过期后去源服务器更新,并且禁止对缓存文件优化
客户端的缓存控制
请求头处理
Cache-Control 新属性
max-stale
允许代理上的缓存过期的最大时间内可用
例子:如果响应头Cache-Control:max-age=5,但是已经超过 3 秒了,如果有请求在请求头写“Cache-Control:max-stale=3” 刚好能发出去
过期 3 秒也能接受
min-fresh
缓存必须有效,而且必须在 x 秒后依然有效
如果响应头 max-age=10,过了 5 秒,请求是请求头上 “min-fresh=1”,5+1<10,就还能发出去
在缓存有效期内,在 1 秒后还有效才可以成功
其它问题
Vary
内容协商的结果
报文版本标记
同一个请求,经过内容协商后可能会有不同的字符集、编码、浏览器等版本
当收到同一个请求,代理就读取缓存里的 Vary,对比请求头里的所有字段
当用了 Vary,要求当前请求和上一个请求得完全匹配,包括压缩,user-agent,Accept-Encoding 等都得一致
Purge
缓存清理
对代理非常重要
功能
过期数据及时淘汰,避免占用空间
源站资源更新,需要删除旧版本,主动换成新版本
缓存了一些本不该存储的信息,及时删除,如:网络谣言,危险连接等
作法:使用自定义请求方法“PURGE”,发送给代理服务器,要求删除 URI 对应的缓存数据
HTTPS,SSL/TLS
为啥要有 HTTPS
因为 HTTP 不安全
HTTP 任何人都能截获、修改、伪造请求、响应报文等,数据不具有可信性
怎样通信过程才会安全
拥有以下四个特性可以被认为是安全的
机密性(Secrecy/Confidentiality)
对数据的“保密”
只能由可信的人访问,对其他人是不可见的
完整性(Inegrity)
也叫一致性
指在传输过程中没有被篡改,“完完整整”的保持着原样
身份认证(Authentication)
确认对方的真是身份
保证消息只能发送给可信的人
不可否认(Non-repudiation/Undeniable)
也叫不可抵赖
不能否认已经发生过的行为
收到了就是收到了,没收到就是没收到
防止耍赖皮
什么是 HTTPS
是一个“非常简单”的协议
RFC 文档很小,只有 7 页
新协议名“HTTPS”,默认端口号 443
其它沿用 HTTP
由“HTTP over TCP/IP”变成了“HTTP over SSL/TLS”
收发报文不再使用 Socket API 而是调用专门的安全接口
SSL/TLS
SSL 安全套接层(Secure Sockets Layer)
在 OSI 模型中的第 5 层(会话层)
由网景公司于 1994 年发明
有 V2 和 V3 两个版本,V1 有缺陷所以从未公开
1999 年由互联网工程组 IETF 改名为 TLS(传输层安全,Transport Layer Security)从此正式成为标准化,版本号从 1.0 重新算起
TLS1.0 实际上就是 SSLv3.1
TLS 版本
1.1 2006 年发布
已经被认定为不安全的
各大浏览器将在 2020 年左右停止支持
1.2 2008 年发布
目前最广泛的版本
1.3 2018 年发布
TLS 的构成
记录协议
握手协议
警告协议
变更密码规范协议
扩展协议等几个自协议组成
浏览器与服务器使用 TLS 建立连接时会选择一组恰当的加密算法来实现安全通信
多个加密算法,最后协定使用 ECDHE-RSA-AES256-GCM-SHA384
命名规范,固定格式:
秘钥交换算法+签名算法+对称加密算法+摘要算法
ECDHE-RSA-AES256-GCM-SHA384
握手时使用 ECDHE 算法进行秘钥交换,用 RSA 签名和身份认证,握手后的通信用 AES 对称算法,秘钥长度 256,分组模式是 GCM,摘要算法 SHA384 用于消息认证和产生随机数
OpenSSL
著名的开源密码学程序库和工具包
几乎支持所有公开的加密算法和协议,已经成为了事实标准
是 SSL/TLS 的具体实现
版本分支
1.02
1.1.0
1.1.1
最新
对称加密与非对称加密
作用:实现 TLS 的机密性
对称加密
加密和解密使用同一个密钥
只要保证了秘钥的安全,整个通信过程就具有了机密性
类型
RC4
DES
3DES
AES
解释:高级加密标准(Advanced Encryption Standard)
密钥长度
128
192
256
安全强度很高,性能好,有的硬件会做特殊优化
DES 算法的替代者,应用最广泛的对称加密算法
ChaCha20
Google 设计的
密钥长度
固定为 256
纯软件运行性能超过 AES
以前在移动客户端上比较流行,后来 ARMv8 加入了 AES 硬件优化,不再具有明显优势
效率高,安全性差
加密分组模式
作用:可以让算法用固定长度的密钥加密任意长度的明文
分类
ECB
CBC
CFB
OFB
AEAD
最新的分组模式(Authenticated Encryption with Associated Data)
加密的同时增加了认证功能
GCM
AES128-GCM
密钥长度 128 位,分组模式是 GCM
CCM
Poly1305
ChaCha20-Poly1305
ChaCha20 算法,使用分组模式Poly1305
非对称加密
原理:生成两个密钥,一个公钥(public key)一个私钥(private key)两个密钥不相同,公钥可以公开给任何人用,私钥必须严格保密
类型
DH
DSA
RSA
安全性基于“整数分解”的数学难题,使用两个超大素数的乘积作为生成密钥的材料
推算私钥非常困难
10 年前推荐 RSA 密钥长度为 1024 位,现在普遍认为至少需要 2048 位
ECC
基于“椭圆曲线离散对数”,使用特定曲线方程和基点生成公私钥
子算法 ECDHE 用于密钥交换,ECDSA 用于数字签名
160 位的 ECC 相当于 1024 位的 RSA,224 位的 ECC 相当于 2048 位 RSA
因为密钥短,加密时间和计算量,消耗的内存,带宽少,加密解密性能上去了,安全性也高
安全性高,效率差
混合加密
混合加密就是把对称加密和非对称加密混合起来使用
TLS 中使用的就是混合加密
流程
非对称加密解决密钥交换问题(这里交换的是对称加密密钥)
然后用随机数产生对称算法使用的“会话密钥”(session key)再用公钥加密
会话密钥就是对称加密密钥
对方拿到密钥后用私钥解密,取出会话密钥,双方实现了对称密钥的安全交换
之后不再使用非对称加密
数字签名与证书
作用:完整性,身份认证
摘要算法
实现完整性的主要手段
通常是散列函数、哈希函数(Hash Function)
常用的
MD5
16 字节摘要
SHA-1
20 字节摘要
TLS 推荐使用 SHA-2
SHA-2 系列
SHA224
28 字节摘要
SHA256
32 字节摘要
SHA384
48 字节摘要
摘要算法不具备机密性,如果明文传输也会被一起修改,所以要配合混合加密
完整性
只要在原文后附上它的照耀,就能保证数据的完整性
就算输入极其相似的数据,在摘要算法的结果上也会产生非常大的变化
网站收到数据后,通过计算摘要,然后和附带的摘要对比,就能知道数据是否发生修改,来确保数据的完整可信
完整性必须建立在机密性之上
在混合加密系统里用会话密钥加密消息和摘要
术语:哈希消息认证码(HMAC)
数字签名
作用:确认身份,防止被骗
加密算法结合摘要算法,通信过程是保密了,但是通信的两端,客户端和服务端到现在是还没有说怎么来确定他是客户或者是服务器而不是非法窃取你机密的人
数字签名就是用来确定客户端和服务端的身份
方法:把公钥和私钥反过来,私钥加密,公钥解密
过程
私钥通过对 SHA-2 加密
签名
拿到文件之后通过公钥解密,拿到 SHA-2 之后和原文的 SHA-2 对比
验签
这里的公私钥服务器和客户端都有
专业术语叫“签名”和“验签”
例子
客户端用自己的私钥签名一个消息
网站收到后用客户端的公钥验签
确认身份之后,用服务端的私钥签名一个消息
客户端收到后用服务端的公钥进行验签
之后就可以用混合加密进行安全通信了
数字证书和 CA
解决“公钥的信任”问题
作用:避免公钥被伪造
不适用类似于密钥交换的方法来解决公钥认证问题,是为了避免套娃
CA
(Certificate Authority 证书认证机构) 用自身信誉来保证公钥无法伪造,必须是具有极高可信度的
CA 对公钥签名认证是有格式的
包含了序列号、用途、颁发者、有效时间等,打包再签名形成了“数字证书”
证书办法机构
DigiCert
VeriSign
Entrust
Let's Encrypt等
证书类型
DV
可信度最低,只对域名可信
不能证明网站拥有者身份
OV
EV
可信度最高,进过了法律和审计的严格核查
可以证明网站拥有者的身份
CA 如何证明自己
大 CA 给小 CA 签名认证
最终 Root CA 自己证明自己(自签证书(Self-Signed Certificate)/根证书(Root Certificate))
证书链
证书的验证
操作系统和浏览器内置了各大 CA 的根证书
当服务器发过来它的证书的时候,验证证书里的签名,顺着证书链验证,直到找到根证书,才能确定证书是可信的
证书体系的弱点
CA 被欺骗或者失误,造成错发证书
CA 被黑客攻陷
2011 年荷兰 CA DigiNotar 被黑客攻陷,颁发了 531 个伪造 CA
CA 有恶意
解决办法
面对前面两个,失误或者被黑客攻陷,开发出了 CRL(证书吊销列表,Certificate revocation list)和 OCSP(在线证书状态协议,Online Certificate Status Protocol),及时废止有问题的证书
面对恶意 CA,因为设计太多证书,所以只能从操作系统或者浏览器上删除“根证书”
TLS1.2连接过程解析
TLS1.2 诞生于 2008 年
HTTPS 建立连接
TLS 会进行类似于 TCP 一样的握手
TLS 协议的组成
记录协议(Record Protocol)
规定了 TLS 收发数据的基本单位,记录(record)
类似于 TCP 的 segment
警报协议(Alert Protocol)
向对方发出警报信息
像 HTTP 协议里的状态码
例子
Protocol_version 就是不支持旧版本
bad_certificate 就是证书有问题
收到报警后另一方可以选择继续,也可以终止连接
握手协议(Handshake Protocol)
TLS 里最复杂的子协议
比 TCP 的 SYN/ACK 复杂
浏览器和服务器需要通过握手协商 TLS 版本,随机数,密码套件等信息
然后交换证书和密钥参数,最终双方协商得到会话密钥
变更密码规范协议(Change Cipher Spec Protocol)
通知对方,后续数据都将使用加密保护
在它之前,所有数据都是明文的
ECDHE 握手过程
TCP 建立连接之后
1、Client -> Server :发送一个 Client Hello 消息,里面包含了(客户端的版本号、支持的密码套件、一个随机数(Client Random))
用于生成后续会话密钥
Handshake Protocol: Client Hello
Version: TLS 1.2 (0x0303)
Random: 1cbf803321fd2623408dfe…
Cipher Suites (17 suites)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
Version: TLS 1.2 (0x0303)
Random: 1cbf803321fd2623408dfe…
Cipher Suites (17 suites)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
2、Server -> Client:服务器收到 Client Hello 后,返回一个 Server Hello 消息,包含了(随机数,确认 TLS 版本号,从客户端的列表里选一个通信密码套件)
密码套件:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
作用就是对一下版本号和选一个密码套件,然后给一个随机数,用于加密解密
Handshake Protocol: Server Hello
Version: TLS 1.2 (0x0303)
Random: 0e6320f21bae50842e96…
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
Version: TLS 1.2 (0x0303)
Random: 0e6320f21bae50842e96…
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
3、Server -> Client:服务器把证书发送给客户端(Server Certificate)
证明自己的身份
4、Server -> Client:服务器在证书发送后发送“Server Key Exchange”消息,包含了 椭圆曲线的公钥(Server Params)
实现密钥交换算法,再加上自己的私钥签名认证
这里使用椭圆曲线生成公钥是因为这里的前面交换的信息中使用的密码套件是 ECDHE 的他用的是椭圆曲线生成公钥
Handshake Protocol: Server Key Exchange
EC Diffie-Hellman Server Params
Curve Type: named_curve (0x03)
Named Curve: x25519 (0x001d)
Pubkey: 3b39deaf00217894e...
Signature Algorithm: rsa_pkcs1_sha512 (0x0601)
Signature: 37141adac38ea4...
EC Diffie-Hellman Server Params
Curve Type: named_curve (0x03)
Named Curve: x25519 (0x001d)
Pubkey: 3b39deaf00217894e...
Signature Algorithm: rsa_pkcs1_sha512 (0x0601)
Signature: 37141adac38ea4...
5、Server -> Client:服务器再发送一个“Server Hello Done”消息,告诉客户端,我的信息发送完了
这里,客户端拿到了 Client Random,Server Random 和 Server Params,还有服务器的证书
Server Params 是服务器的公钥
6、客户端开始走证书链逐级验证证书,确认证书的真实性,再用证书公钥验证签名
确认服务器身份
这里的证书是加密了公钥的
7、Client -> Server :客户端按照密码套件要求,也生成一个椭圆曲线的公钥(Client Params),用“Client key Exchange”消息发给服务器
交换密钥算法参数
服务器拿到了客户端的公钥
Handshake Protocol: Client Key Exchange
EC Diffie-Hellman Client Params
Pubkey: 8c674d0e08dc27b5eaa…
EC Diffie-Hellman Client Params
Pubkey: 8c674d0e08dc27b5eaa…
客户端和服务端都拿到了 双方的公钥,通过 ECDHE 算法计算出一个随机数,这个随机数被称为“Pre-Master”
然后用 PRF 伪随机函数把 Client Random、Server Random 和 Pre-Master 通过密码套件的摘要算法再次强化,得到结果值 master secert 的随机性
计算出主密钥 Master Secret
主密钥不是会话密钥
还会再用 PRF 扩展出更多的密钥,比如客户端发送用的会话密钥(client_write_key)、服务器发送用的会话密钥(server_write_key)
PRF 基于密码套件里的最后一个参数进强化
master_secret = PRF(pre_master_secret, "master secret",
ClientHello.random + ServerHello.random)
ClientHello.random + ServerHello.random)
为什么非要三个随机数
因为不信任客户端或服务器伪随机书的可靠性
为了保证真正的“完全随机”“不可预测”
为了随机程度高,不让黑客容易猜到
8、Client -> Server :生成主密钥和派生的会话密钥,客户端发一个“Change Cipher Spec” 告诉服务器,之后所有的消息都改用商定好的对称加密通信
9、Client -> Server :客户端再发送一个“Finished”消息,把之前所有发送的数据做个摘要,再加密一下,让服务器验证
10、Server -> Client:服务器也重复了 8,9 的操作
11、双方都验证加密解密 ok,握手正式结束
RSA 握手过程
1、客户端向服务端发送 Client Hello
随机数 C,客户端的 TLS 版本号,密码套件列表,扩展列表
2、服务端向客户端发送 Server Hello
随机数 S,确认 TLS 版本号和使用的密码套件(RSA)
3、服务端向客户端发送 Server Certificate
服务器使用的证书
4、server -> client Server Hello Done
服务器 Hello 完成
5、client 验证证书
通过证书链
6、client->server Client Key Exchange
服务端 RSA 公钥加密 Pre-Master
这里的 Pre-Master 是一个客户端生成的随机数,没有用算法生成
密钥交换
7、server 用私钥解密 Pre-Master
8、client,server 用 随机数 C/S 加 Pre-Master 算出主密钥 master secret
这里的 “随机数c/s” 的意思是 client 随机数和 server 随机数
9、client -> server Change Cipher Spec
告诉服务器,之后改用会话密钥加密通信
10、client->server Finished
发送所有握手数据的摘要,让 server 端校验
11、server -> client Change Cipher Spec
告诉 client,之后所有会话机密通信
12、server-> client Finished
所有握手数据的摘要,让客户端校验
握手结束
双向认证
上面的都是单向认证,只认证了服务器的身份,没有认证客户端的身份
区别
只是在“Server Hello Done” 之后 “Client Key Exchange”之前,客户端要发送 “Client Certificate”消息
服务器收到后也把证书链走一遍,验证客户端的身份
要点
HTTPS 协议会闲鱼服务器执行 TCP 握手,然后执行 TLS 握手,才能建立安全连接
握手的目标是安全的交换对称密钥,需要三个随机数,第三个随机数“Pre-Master”必须加密传输,绝对不能让黑客破解
“Hello”消息交换随机数,“Key Exchange”消息交换“Pre-Master”
“Change Cipher Spec” 之前传输的都是明文,之后都是对称密钥加密的秘闻
流程总结
第一阶段,交换随机数
第二阶段,证书验证
第三阶段,主密钥生成
TLS1.3 特性解析
TLS1.3 诞生于 2018 年
诞生原因,TLS1.2 在安全,性能方面跟不上互联网的发展速度了
改进目标:兼容、安全、性能
TLS1.2 和 TLS1.3 的区分方法
TLS1.2
0x303
TLS1.3
0x304
最大化兼容性
为啥要兼容
因为 TLS1.1,1.2 协议出现了很多年,很多的软件,中间代理等只认老的记录协议格式
更新改造很困难,
一旦变更了记录头字段里的版本号,大量的代理服务器,网关都将无法正常处理,TLS 握手失败
为了老设备能够继续使用,避免新协议带来的冲击
兼容方法
扩展协议(Extension Protocol)
类似于补充条款
握手 Hello 消息后面添加 supported_versions
用于区分新旧版本
如果服务器不支持 1.3,那就“向后兼容”降级成为 1.2
supported_groups
支持的曲线
key_share
曲线对应的参数
signature_algorithms
server_name
例子
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)
Version: TLS 1.2 (0x0303)
Extension: supported_versions (len=11)
Supported Version: TLS 1.3 (0x0304)
Supported Version: TLS 1.2 (0x0303)
强化安全
伪随机函数由 PRF 升级为 HKDF(HMAC-based Extract-and-Expand Key Derivation Function)
明确禁止在记录协议里使用压缩
废除 RC4、DES 对称加密算法
废除 ECB、CBC 等传统分组模式
废除 MD5、SHA1、SHA-224 摘要算法
废除 RSA、DH 密钥交换算法和许多命名曲线
废除原因,不具有“前向安全”
如果有黑客长期收集混合加密系统收发的所有报文,如果系统使用服务器证书里的 RSA 做密钥交换,一旦私钥泄漏或破解,黑客能够使用私钥解密出之前所有报文的“Pre-Master”,再算出会话密钥,破解所有密文
只保留了 AES、ChaCha20 对称加密算法
分组模式只能用 AEAD 的 GCM、CCM 和 Poly1305
摘要算法只能用 SHA256、SHA384
密钥交换算法只有 ECDHE 和 DHE
每次握手时都会生成一对临时公钥和私钥,即“一次一密”
每次通信的密钥对都是不同的
椭圆曲线只剩 P-256 和 X25519等 5 种
密码套件
TLS1.3 只有 5 个套件
提升性能
TLS 握手时减少了几个步骤
压缩了以前 Hello 协商过程
删除了“Key Exchange”消息
做法:
Client Hello 消息里用 “supported_groups”带上支持的曲线
“key_share”带上曲线对应的客户端公钥参数
用“signature_algorithms”带上签名算法
服务器收到后再扩展里选定一个曲线和参数,再用“key_share”扩展返回服务器公钥参数,实现了双方密钥交换
用 pre_shared_key 和 early_data 扩展
握手分析
1、Client -> Server :Client Hello
随机数 C、密码套件、密钥交换算法参数(key_share)
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
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
supported_versions
表示这是 TLS1.3
supported_groups
表示支持哪些曲线
key_share
表示曲线对应的参数
2、Server -> Client:Server Hello
随机数 S、确认密码套件、交换算法参数(key_share)
Handshake Protocol: Server Hello
Version: TLS 1.2 (0x0303)
Random: 12d2bce6568b063d3dee2…
Cipher Suite: TLS_AES_128_GCM_SHA256 (0x1301)
Extension: supported_versions (len=2)
Supported Version: TLS 1.3 (0x0304)
Extension: key_share (len=36)
Key Share extension
Key Share Entry: Group: x25519, Key Exchange length: 32
Version: TLS 1.2 (0x0303)
Random: 12d2bce6568b063d3dee2…
Cipher Suite: TLS_AES_128_GCM_SHA256 (0x1301)
Extension: supported_versions (len=2)
Supported Version: TLS 1.3 (0x0304)
Extension: key_share (len=36)
Key Share extension
Key Share Entry: Group: x25519, Key Exchange length: 32
supported_versions
确认使用 TLS版本号
key_share
带上曲线和对应公钥参数
以上两条消息,客户端和服务端就共享了四个信息:
Client Random
Server Random
Client Params
Server Params
3、Server 用各自的 ECDHE 算出“Pre-Master”,再用 HKDF 生成主密钥“Master Secret”
4、Server -> Client:Change Cipher Spec
告诉Client,之后都用会话密钥加密通信
比 TLS1.2 提早进入加密通信,后面的证书等都是加密的,减少握手时明文信息泄漏
5、Server -> Client:Encrypted Extensions
被加密的扩展信息
6、Server -> Client:Server Certificate
服务器使用的证书
7、Server -> Client:Server Certificate Verify
使用证书签名握手数据
用服务器私钥把前面的曲线、套件、参数等握手数据加了签名,作用和“Finished”差不多
由于是私钥签名,强化了身份认证和防篡改
8、Server -> Client:Finished
所有握手数据的摘要
9、Client 开始验证证书和签名,然后 ECDHE 计算 Per-Master,再用 HKDF 生成主密钥“Master Secret”
10、Client -> Server :Change Cipher Spec
11、Client -> Server :Finished
握手结束
图示
HTTPS优化,解决连接太慢
慢的原因
第一建立连接时的非对称加密握手
第二是握手后的对称加密报文传输
HTTPS 比 HTTP 增加了一个 TLS 握手步骤,这个步骤最长多花 2-RTT
握手消息网络耗时之外还有其它消耗
ECDHE 生成用于密钥交换的临时公钥对
验证证书时访问 CA 获取 CRL 或者 OCSP
非对称加密解密处理“Pre-Master”等
硬件优化
HTTPS 连接是计算密集型
所以,网卡,带宽,SSD 存储没有优化作用
想要快,就要选更快的 CPU,最好内建 AES 优化,可以加速握手,加速传输
SSL 加速卡
加解密时调用它的 API
让专门的硬件来做非对称加解密,分担 CPU 的计算压力
缺点:升级慢、支持算法有限
SSL 加速服务器
专门的服务器集群来“卸载”TLS 握手时的加解密计算
软件优化
软件升级
将软件尽量升级到最新版本
Linux 内核由 2.x 升级到 4.x
Nginx 由 1.6 升级到 1.16
OpenSSL 由 1.0.1 升级到 1.1.0/1.1.1
最容易的方法
协议优化
TLS升级,最好升级到 TLS1.3
TLS1.3 完全握手只要 1-RTT,而且更加安全
非对称加密
只能用 1.2时,握手时使用的密钥交换协议尽量用椭圆曲线的 ECHDE 算法
能够把握手消息往返由 2-RTT 减少到 1-RTT
运算快,安全性高
支持“False Start”
使用椭圆曲线选择高性能曲线,最好是 x25519,次优选择P-256
对称加密
AES_128_GCM
比 AES_256_GCM 略快一点
Nginx 中可以用“ssl_ciphers”“ssl_ecdh_curve”等指令配置服务器使用的密码套件和椭圆曲线,把优先使用的放在前面
ssl_ciphers TLS13-AES-256-GCM-SHA384:TLS13-CHACHA20-POLY1305-SHA256:EECDH+CHACHA20;
ssl_ecdh_curve X25519:P-256;
ssl_ecdh_curve X25519:P-256;
证书优化
优化点
证书传输
证书验证
服务器的证书可以选择椭圆曲线(ECDSA)证书而不是 RSA 证书
224 位的 ECC 相当于 2048 位的 RSA,所以椭圆曲线证书比 RSA 证书小很多
能够节省宽带
失效证书查询 CRL(Certificate revocation list,证书吊销列表)
由 CA 定期发布
所有被撤销信任的证书序列号
只查这个列表就可以知道证书是否有效
不太常用
被 OCSP 替代
OCSP(在线证书状态协议 Online Certificate Status Protocol),向 CA 发送查询请求,让 CA 返回证书的有效状态
使用 OCSP Stapling (OCSP 装订)
让服务器预先访问 CA 获取 OCSP 响应,然后在握手时随证书一期发送给客户端,免去了客户端连接 CA 服务器查询的时间
会话复用
TLS session resumption
和 HTTP Cache 一样
Session ID (会话 ID)
Server 和 Client 首次连接之后各自保存一个会话 ID
内存里存储主密钥和其他相关信息
当 Client 再次连接时发 ID 过来,服务器直接在内存找,找到了直接用主密钥恢复会话状态,跳过证书验证和密钥交换
只用一个消息往返就可以建立安全通信
缺点
服务器得保存每一个客户端的会话数据,客户端太多对服务器是一种压力,百万级,千万级用户存储量会成问题
Handshake Protocol: Client Hello
Version: TLS 1.2 (0x0303)
Session ID: 13564734eeec0a658830cd…
Cipher Suites Length: 34
Handshake Protocol: Server Hello
Version: TLS 1.2 (0x0303)
Session ID: 13564734eeec0a658830cd…
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
Version: TLS 1.2 (0x0303)
Session ID: 13564734eeec0a658830cd…
Cipher Suites Length: 34
Handshake Protocol: Server Hello
Version: TLS 1.2 (0x0303)
Session ID: 13564734eeec0a658830cd…
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
会话票证
解决服务器存储会话 ID 成本,负担问题
Session Ticket
类似于 Cookie
服务器加密会话信息,用 New Session Ticket 消息发送给客户端,由客户端存储
重连方法
客户端使用扩展“session_ticket”发送“Ticket”,服务器解密后验证有效期,然后开始会话,开始加密通信
预共享密钥
实现了0-RTT
HTTPS 迁移
申请证书
网站
DigiCert
GlobalSign
Let’s Encrypt
ACME 自动化部署
只有 90 天有效期,在 crontab 设置定时任务,定时更新
注意要点
应当同时申请 RSA 和 ECDSA 两种证书,Nginx 配置双证书验证
RSA 证书私钥至少要 2048 位,摘要算法应该选 SHA-2
配置 HTTPS
Nginx
在 listen 指令后面加上参数 ssl,再配上证书文件
为了提高 HTTPS 的安全系数和性能,还可以强制 Nginx 只支持 TLS1.2 以上协议,打开“Session Ticket”会话复用
密码套件以服务器套件优先,避免客户端选择较弱套件,密码套件看齐 TLS1.3,只使用 ECDHE、AES 和 ChaCha20,支持“False Start”
如果服务器使用了 OpenSSL 分支 BorringSSL,使用特殊“等价密码组”
如果客户端没有 AES 优化,服务器会优先选择与 AES“等价”的 ChaCha20,让客户端能快一点
服务器名称指示
虚拟主机
多个域名可以在同一个 IP 地址上运行
Web 服务器会使用请求头里的 Host 字段来选择
HTTPS 里请求头只有在 TLS 握手后才能发送,握手时必须选择“虚拟主机”对应的证书,TLS 无法得知域名信息,只能用 IP 区分,早期时候,每个 HTTPS 域名必须使用独立 IP
解决办法 TLS 扩展,给协议增加 SNI(Server Name Indication)“补充条款”
作用和 Host 字段差不多
客户端会在 Client Hello 时带上域名信息,也是在 Nginx 配置
重定向跳转
Nginx 配置做重定向,将 HTTP 的站点跳转到 HTTPS
一定要利用 HSTS(HTTP 严格传输安全,HTTP Strict Transport Security)技术来消除安全隐患
重定向增加网络成本
安全隐患:重定向响应被中间人篡改,实现“会话劫持”
HTTPS 服务器需要在发出的响应头里添加一个“Strict-Transport-Security”字段,再设置一个有效期
告诉服务器,这个网站,必须严格使用 HTTPS 协议,半年内不允许用 HTTP
Nginx 使用 add_header 添加“HSTS”
HTTP/2 特性
HTTP 没有 2.0,2.1的版本区别,之后将是 HTTP/2,HTTP/3
HTTP/2 要求必须实现的密码套件是“TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256”
HTTP/1.1,1.0 造成了很多混乱和误解,不容易区分差异,所以 HTTP 不再使用小版本号
HTTP/2 所有头字段必须全部小写
兼容 HTTP/1
为了兼容,HTTP/2 把 HTTP 分成了“语义”和“语法”
“语义”层不动
如请求方法
URI
状态码
头字段等概念不变
“语法”层天翻地覆,变更了 HTTP 报文的传输格式
头部压缩
HTTP/1里面,请求头用了大量的明文,传输体积太大
HTTP/2里面对请求头进行了“头部压缩”
HTTP/2 没有使用传统压缩算法,开发了专门的“HPACK”算法
在客户端和服务器两端建立“字典”
用索引号表示重复字符串,还用哈夫曼编码来压缩整数和字符串,压缩率可达 50%~90%
二进制格式
HTTP/2的报文不再使用明文,全面采用了二进制格式
这样做效率提高,方便计算机解析
向 TCP/IP 协议靠拢了
格式
把原来的“Header+Body”打散为二进制“帧”(Frame)
“HEADERS”帧存放头数据
“DATA”帧存放实体数据
虚拟的“流” (Stream)
流,一个概念,是二进制帧的双向传输序列
同一个消息往返的帧会分配一个唯一的流 ID
多路复用
“流”是虚拟的,在 HTTP/2 可以在一个 TCP 连接上用“流”同时发送多个“碎片化”消息,
多个往返通信都复用一个连接来处理
“流”的层面上,消息是有序的“帧”序列
TCP 层面还是有序的
连接层面上,消息确实乱序收发的“帧”
多个请求 / 响应之间没有顺序,不需要排队
不会出现“队头阻塞”,降低了延迟,提高了连接的利用率
HTTP/2添加了一些控制帧来管理虚拟“流”,实现了优先级和流量控制
服务器推送(Server Push 或 Cache Push):服务器可以新建“流”主动向客户端发送消息
如 浏览器要请求 HTML 的时候,提前把会用到的 JS、CSS 文件发送给客户端,减少等待的延迟
强化安全
出于兼容性考虑,HTTP/2可以像 HTTP/1一样使用明文传输数据,不强制使用加密通信,格式还是二进制,只是不需要解密
在主流浏览器上只支持加密的 HTTP/2
区分
加密
h2 表示加密的 HTTP/2
明文
h2c 表示明文的 HTTP/2 ,c 是 clear text
HTTP/2标准制定在 2015 年,已经发现了很多 SSL/TLS弱点
在 TLS1.3 还未发布,所以 HTTP/2加密版在安全方面做了强化,要求下层通信协议必须在 TLS1.2以上
还要支持向前安全和 SNI,并且把几百个弱密码套件列入“黑名单”
协议栈
HTTP/2 是建立在“HPack”“Stream”“TLS1.2”基础上
HTTP/2内核剖析
连接前言
TLS 握手成功后,客户端必须发送一个“连接前言”,用来确认建立 HTTP/2 连接
标准的 HTTP/1 请求报文,使用纯文本 ASCII 码格式
请求方法是特别注册的关键字“PRI”
被称为“Magic”意思为“不可知的魔法”
服务器收到这个字符串,就知道客户端在 TLS 使用 HTTP/2 协议
头部压缩
必须使用“HPACK”算法压缩头部数据
伪头字段
将起始行里的请求方法、URI、状态码等统一转换成头字段形式
"伪头字段"会在名字前加一个“:”
:authority
:method
:status
静态表
将常用的头字段定义了一张只读表
客户端和服务端通过查静态表就可以通过 Index 查到具体的请求方法等
比如 2 代表 GET,8 代表状态码 200
动态表
添加在静态表后面,结构相同
采用哈夫曼压缩编码发送,客户端和服务器都得更新自己的动态表
最终只会两边的字典越来越大,但是发送的头部字段小很多,只有一两个字节的代码
二进制帧
类似于 TCP 的段或者 TLS 里的记录
报头很小,只有 9 字节,非常节省
格式
长度
帧开头 3 个字节表示
帧类型
第 4 个字节表示
总共定义了 10 种类型的帧
gRPC 利用了可以在标准之外定义其它类型实现扩展
数据帧
DATA 帧
HEADERS 帧
CONTINUATION
只要前一帧在同一个流上并且是没有设置 END_HEADERS 标志的 HEADERS,PUSH_PROMISE 或 CONTINUATION 帧,就可以发送任意数量的 CONTINUATION 帧。
控制帧
PRIORITY
SETTINGS
PUSH_PROMISE
PING
RST_STREAM
GOAWAY
WINDOW_UPDATE
帧标志
第 5 个字节表示
可以保存 8 个标志位
常用
END_HEADERS 表示数据结束
END_STREAM 表示单方向数据发送结束
流标识符
最后 4 个字节
接收方使用它从乱序的帧里识别出具有相同流 ID 的帧序列
最高位被保留不用,只有 31 位可以使用,上限位 2^31,越 21 亿
00010a 表示数据长度 266 字节,
01 表示 HEADERS 帧,
25 为 0x25(16 进制)转换成二进制为 111 表示设置 PRIORITY 流的优先级,END_HEADERS 表示这一个帧就是完整的头数据,END_STREAM 表示单方向发送结束,后续不会再有数据
00 00 00 01 表示流表示符是整数 1,表示这是客户端发起的第一个流,后面的响应数据帧也会是这个 ID,就是 stream[1]里完成这个请求响应
01 表示 HEADERS 帧,
25 为 0x25(16 进制)转换成二进制为 111 表示设置 PRIORITY 流的优先级,END_HEADERS 表示这一个帧就是完整的头数据,END_STREAM 表示单方向发送结束,后续不会再有数据
00 00 00 01 表示流表示符是整数 1,表示这是客户端发起的第一个流,后面的响应数据帧也会是这个 ID,就是 stream[1]里完成这个请求响应
流与多路复用
流是二进制帧的双向传输序列
HTTP/2 的核心
帧虽然是乱序收发,但只要是都拥有相同的流 ID,就都属于一个流,在流里,帧是顺序的
“流”是虚拟的,他的顺序由 TCP 来保证
流的特点
流是可并发的,一个 HTTP/2 连接可以同时多个流传输数据,实现“多路复用”
客户端和服务端都可以创建流,互不干扰
流是双向的,一个流里客户端,服务端都可以发送或接收数据帧
流之间没有固定关系,彼此独立,流内部的帧有严格顺序
流可以设置优先级,让服务器优先处理
流 ID 不能重复,只能顺序底层
客户端发起的 ID 是奇数,服务器发起的 ID 是偶数
在流上发送“RST_STEAM”帧可以随时终止流,取消接收或发送
第 0 号流比较特殊,既不能关闭,也不能发送数据帧,只能发送控制帧
当 ID 用完的时候,再发一个控制帧“GOAWAY”,真正关闭 TCP 连接
流状态转换
HTTP/2 借鉴了 TCP,根据帧的标志位实现流状态转换
流程
最开始流都是“空闲”(idle)状态
client->server 发送 HEADERS帧
有了流 ID
流进入“打开”状态
两端口可以收发数据
client->server 发送请求
client->server 发送 END_STREAM 帧
流进入“半关闭”状态
意味着客户端的请求数据已经发送完成
server -> client 发送数据
server -> client 发送 END_STREAM 帧
数据发送完毕
两端都进入关闭状态,流结束
流程图
流 ID 不能重用,流的生命周期就是从流开始到流关闭就是一次通信结束
下次再发请求就要开一个新流
流 ID 不断增加,直到到达上线,发送“GOAWAY”帧开一个新的 TCP 连接,流 ID 就可以重头计算
HTTP/3
HTTP/2的“队头阻塞”
HTTP/2虽然用了“帧”,“流”,“多路复用”,在应用层没有了“队头阻塞”,但是在 TCP 层还是会发生“队头阻塞”
基本上解决了队头阻塞
TCP 队头阻塞原因
“丢包重传”机制
当 TCP 发出 N 个段,有其中一个段没有收到回复,那么,其它的回复都会在TCP 协议栈中暂存,等那个没有收到回复的重发,然后收到回复再一起发送到上层
TCP 在这里会把 http 中的包拆分成更小的包,这里叫段 segment
例子:
TCP 发送了1,2,3 个包,服务端收到了 2,3 两个包并做出回应,
TCP 把 2,3 两个包的回复数据暂存在缓冲区,重发 1 包的数据,等收到 1 包的回复数据再把整体发送给上层
TCP 发送了1,2,3 个包,服务端收到了 2,3 两个包并做出回应,
TCP 把 2,3 两个包的回复数据暂存在缓冲区,重发 1 包的数据,等收到 1 包的回复数据再把整体发送给上层
这种队头阻塞是 TCP 固有的
就算 HTTP/2 解决的再好,也解决不了 TCP 上的队头阻塞问题
HTTP/3 的出现,完美的解决了队头阻塞问题,HTTP/3 原名 QUIC,全称 HTTP over QUIC
HTTP/3 协议栈图
QUIC
QUIC 是指 iQUIC 也叫 QUIC-transport
把 TCP 换成了 UDP,并且把 TCP 的连接管理、拥塞窗口、流量控制搬过来了
处于传输层,和 TCP 平级
发展
QUIC 最早是由 Google 发明,被称为 gQUIC
混合了 UDP、TLS、HTTP,属于应用层
目前由 IETF 标准化的 QUIC 被称为 iQUIC,两者差异非常大
把应用层部分分离出来,放到了传输层,形成了 HTTP/3
原来的 UDP 部分“下放”到了传输层
特点
基于 UDP,无需“握手”和“挥手”,比 TCP 快
引入了类似 HTTP/2 的“流”和“多路复用”,单个“流”是有序的,可能会因为丢包而阻塞,但其他“流”不会受到影响
采用加密通信,抵御篡改和“协议僵化”,防止网络中间设备识别协议细节
使用了 TLS1.3,获得了 0-RTT、1-RTT 连接的好处
并不是建立在 TLS 之上,是内部“包含”了 TLS,使用自己的帧“接管”了 TLS里的“记录”,握手消息、警报消息都不使用 TLS 记录,是封装成 QUIC 的帧发送,节省了一次开销
内部细节
详细的看 https://quicwg.org/
QUIC 和 TLS 是相互协作的关系
基本数据传输单位是包(packet)和帧(frame)
一个包由多个帧组成,包面向的是“连接”,帧面向的是“流”
一个包由多个帧组成,包面向的是“连接”,帧面向的是“流”
QUIC使用“连接 ID”来标记通信的两个端点,这个连接 ID 是在握手阶段生成的
连接 ID 代替了 TCP 中的“IP 地址+端口”,支持连接转移
连接转移:比如,你在家里用的 WIFI,突然断网了,转换成 4/5G手机流量了,IP 发生了变化,
TCP 必须重新建立连接
QUIC 因为两端使用的是连接 ID,所以没有出现逻辑上的终端,可以继续使用 IP 变化之前的连接
HTTP 各协议版本协议栈图
0 条评论
下一页