需补充 HTTP1.0 1.1、2.0 区别
一个客户端对一个域名同时的并发请求次数
协议要求 2 个
chrome6 个
火狐 8 个
HTTP 是什么
HyperText Transfer Protocol
HTTP 是一个用在计算机世界里的协议,它确立了计算机之间交流通信的规范,以及相关的各种控制和错误处理方式。
HTTP 是专门用来在两点之前传输数据,不能用于广播、寻址或路由。
HTTP 传输的是文字、图片、音频、视频等超文本数据。
HTTP 是构建互联网的重要技术基础技术,它没有实体,依赖许多其他的技术来实现,同时许多技术也依赖于它。
HTTP 通常跑在TCP/IP
协议栈之上,依靠 IP 实现寻址和路由,TCP 协议实现可靠数据传输、DNS 协议实现域名查找,SSL/TLS
协议实现安全通信,此外还有一些协议依赖于 HTTP,如 WebSocket
、HTTPDNS
等。
HTTP 不是什么
HTTP 不是互联网,
HTTP 不是编程语言
HTTP 不是 HTML
HTTP 不是一个孤立的协议
HTTP 协议的主要特点
- 灵活可扩展: HTTP 允许传输任意类型的数据对象,正在传输的类型由
Content-Type
来标识。 - 可靠传输:尽量保证数据的送达
- 应用层协议:比 FTP、SSH 等更通用更多,能够传输任意数据
- 请求-应答通信模式:客户端主动发起请求,服务器被动回复请求
- 支持客户/服务器模式
- 简单快速
客户向服务器请求服务时,只需传送请求方法和路径。 - 无状态:每个请求都是互相独立、毫无关联的,协议不要求客户端或服务器记录请求相关的信息。
缺点
- 明文 信息泄漏
- 不安全 身份认证和完整性校验 HTTPS
- 性能 Web 性能优化 HTTP/2 HTTP/3
浏览器发起 HTTP 请求过程
- 浏览器从地址栏的输入中(可能通过 DNS 解析)获取服务器的 IP 地址(或 CDN 的 IP)和端口号
- 如果返回的是 CDN 的 IP,访问 CDN 时先看是否缓存了,缓存了响应用户,无法缓存,缓存失效或者无缓存,回源请求(back to the source request),来拉取最新的数据。
- 如果返回的是服务器的 IP,浏览器通过 TCP 三次握手与服务器建立连接
- 浏览器向服务器发送请求报文
- 服务器收到请求报文后回复确认收到请求的报文,然后将相应报文发给浏览器
- 浏览器解析响应报文,渲染出页面
域名解析(DNS)的过程
- 浏览缓存
- 操作系统缓存
- 本机域名解析文件 hosts
- 非权威域名服务器查询其缓存
- 根域名服务器,返回顶级域名服务器 IP
- 顶级域名服务器,com 返回权威域名服务器 IP
- 权威域名服务器,返回域名 IP,或报错
TCP 报文组成部分
- TCP 头,至少 20 字节
- 发送方端口号
- 接收方端口号
- 包序号
- 标志位
- ……
- 实际传输的数据,大小通常 1460 字节
HTTP 报文的组成部分
请求报文
请求行
请求方法:大写
客户端发出了一个“动作指令”,要求服务器端对 URI 定位的资源执行这个动作。- GET:获取资源,可以理解为读取或者下载资源
- HEAD:获取资源的元信息(响应头),GET 的简化版,检查一个文件是否存在
- POST:向资源提交数据,写入或上传数据(create)
- PUT:类似 POST(update)
- DELETE:删除资源
- CONNECT:建立客户端与另外一台服务器建立特殊的连接隧道
- OPTIONS:要求服务器列出可对资源实行的操作方法,在响应头的 Allow 字段里返回。
- TRACE:追踪请求-响应的传输路径
请求 URI
HTTP 协议版本号
这三个部分通常使用空格(space)来分隔,最后要用 CRLF 换行表示结束。1
GET / HTTP/1.1
| 请求方法 | 请求 URI | 协议版本号 |
| ——– | ——– | ———- |
| GET | / | HTTP |
请求头:请求和响应的条件和属性 key-value 形式
- 可以任意添加自定义头
- 字段名不区分大小写,一般首字母大写
- 字段名里不允许出现空格,可以使用连字符“-”,但不能使用下划线“_”
- 字段名后面必须紧接着“:”,不能有空格,而“:”后的字段值前可以有多个空格
- 字段的顺序是没有意义的,可以任意排列不影响语义;
- 字段原则上不能重复除非这个字段本身的语义允许,例如 Set-Cookie
空行:告诉服务器接下来是请求体
- 请求体
响应报文
状态行
- HTTP 协议版本
- 响应结果状态码,
- 原因短语,作为数字状态码补充,是更详细的解释文字,帮助人理解原因。
1
2HTTP/1.1 200 OK
HTTP/1.1 404 Not Found| 版本号 | 状态码 | 原因 |
| ——– | —— | ——— |
| HTTP/1.1 | 200 | OK |
| HTTP/1.1 | 404 | Not Found |
响应头
- 空行
- 响应体
常用头字段
- 通用字段:在请求头和响应头都可以出现
- 请求字段:仅能出现在请求头里,进一步说明请求信息或者额外的附加条件
- 响应字段:仅能出现在响应头里,进一步说明响应报文的信息
实体字段:属于通用字段,但专门描述 body 的额外信息
Host
请求字段,只能出现在请求头里。唯一一个 HTTP/1.1 规范里要求必须出现的字段
告诉服务器这个请求应该由哪个主机来处理,当一台计算机上托管了多个虚拟主机的时候,服务器就需要用 Host 字段来选择。User-Agent
请求字段,只能出现在请求头里。
使用一个字符串来描述发起 HTTP 请求的客户端,服务器可以依据它来返回最合适此浏览器显示的页面。Date
通用字段,通常出现在响应头。表示 HTTP 报文创建的时间,客户端可以使用这个时间再搭配其他字段决定缓存策略。Server
响应字段,只能出现在响应头。客户端当前正在提供 Web 服务的软件名称和版本号,1
Server: openresty/1.15.8.1
Server 字段不是必须的,黑客可能利用服务器 bug 攻陷服务器,所以响应头要么没有这个字段,要么是个无关的描述信息。
大文件传输相关头
1.数据压缩 2.分块传输 3.范围请求 4.多段数据
Content-Length
报文里 body 的长度,也就是请求头或响应头空行后面数据的长度。服务端/客户端通过它来得知后续要读取消息的长度。
Content-Length 首部指示出报文中实体主体的字节大小. 但如在请求处理完成前无法获取消息长度, 我们就无法明确指定 Content-Length, 此时应该使用 Transfer-Encoding: chunked
Accept-Ranges
响应头使用字段Accept-Ranges: bytes
告诉客户端在请求头里可以使用 Range 的范围。
如果服务器不支持范围请求返回Accept-Ranges:none
Range
- 请求一段
1 | Range: bytes=start-end //注意,这个表示[start,end],即是包含请求头的 start 及 end 字节的,所以,下一个请求,应该是上一个请求的[end+1, nextEnd] |
服务器收到Range
字段后,需要做四件事:
- 检查范围是否合法,如果超过范围(只有 100 字符,请求 200-300),返回 416
- 范围正确,服务器根据
Range
计算偏移量,读取文件片段(如果服务器需要压缩文件,也是按照计算范围后的)返回 206 - 服务器要添加一个响应头 Content-Range,片段的实际偏移量和资源总大小,
Content-Range: bytes 0-99/300
请求多段数据
1
Range: bytes=0-9,20-29
这种情况服务器的响应头需要一个新的文件内容Content-Type:multipart/byteranges;boundary=xxx
,
“multipart/byteranges”是一种特殊额 MIME 类型,boundary 是分段之间的分隔标记。
每个分段必须以分隔符--boundary
开始,之后是响应头Content-Type
和Content-Range
标记这段数据的类型和所在范围,然后跟正常数据一样加一个空行,加上分段数据,最后用--boundary--
结束分段。
Content-Range
表示响应报文里 body 数据的具体范围,供客户端确认,
1 | Content-Range: bytes 0-99/2000”//意思是此次获取的是总计 2000 个字节的前 100 个字节。对应Content-Length:100 |
Transfer-Encoding
Transfer-Encoding:chunked 与 Content-Length 互斥,这种方式分块传输无法得知 body 的长度。
数据格式:
分块的格式是 16 进制长度头 + 数据块
分块传输中数据里含有回车换行(\r\n)不影响分块处理,因为分块前有数据长度说明
数据类型使用的头字段
多用途互联网邮件扩展,Multipurpose Internet MailExtensions),简称为 MIME
MIME type 分类(请求体或响应体的类型)
- text 文本格式的可读数据
- text/html 超文本文档
- text/plain 纯文本
- text/css 样式表
- image 图片
- image/gif
- image/jpeg
- image/png
- audio/video 音频和视频数据
- audio/mpeg
- video/mp4
- application 数据格式不固定,可能是文本也可能是二进制,必须由上层应用程序来解释
- application/json
- application/javascript
- application/pdf
- application/octet-stream 不透明的二进制数据
Encoding type(请求体或响应体的压缩格式)
- gzip:GNU zip 压缩格式,也是互联网上最流行的压缩格式;
- deflate:zlib(deflate)压缩格式,流行程度仅次于 gzip;
- br:一种专门为 HTTP 优化的新压缩算法(Brotli)
Accept
客户端用 Accept 头告诉服务器希望接收什么样的数据
1 | Accept: text/html,application/xml,image/webp,image/png |
多个 MIME type 以’,’做分隔符
Accept-Encoding
客户方支持的压缩格式,例如 gzip,deflate,br,同时支持多个用’,’拼接。
如果请求报文里没有 Accept-Encoding 字段,就表示客户端不支持压缩数据。
1 | Accept-Encoding: gzip, deflate, br |
Content-Type
服务器用 Content-Type 头告诉客户端实际发送了什么样的数据
POST 请求时,客户端告诉服务器 body 内容的格式
1 | Content-Type: text/html |
浏览器看到报文里的类型是“text/html”就知道是 HTML 文件,会调用排版引擎渲染出页面,看到“image/png”就知道是一个 PNG 文件,就会在页面上显示出图像
Content-Encoding
服务器实际的压缩格式。
如果响应报文里没有 Content-Encoding 字段,就表示响应数据没有被压缩。
1 | Content-Encoding: gzip |
语言类型使用的头字段
Accept-Language
标记客户端可以理解的自然语言,多个用’,’隔开
1 | Accept-Language:zh-CN, zh, en |
Content-Language
告诉客户端实体数据使用的实际语言类型
Accept-Charset
1 | Accept-Charset:gbk, utf-8 |
客户端可以理解的字符集。
服务器中在 Content-Type 字段的数据类型后面用’charset=xxx’,来表示
1 | Content-Type:text/html;charset=utf-8 |
内容协商的质量值
Accept、Accept-Encoding、Accept-Language 等请求头字段进行内容协商的时候,还可以使用’q’(quality factor)参数表示权重来设定优先级,最大是 1,最小是 0.01,默认值是 1,如果是 0 就表示拒绝。格式就是在数据类型或语言代码后面加’;q=value’。
1 | Accept:text/html,application/xml;q=0.9,*/*;q=0.8 |
内容协商的结果
Vary
响应头
1 | Vary: Accept-Encoding,User-Agent,Accept |
连接相关的头字段
Connection
请求头和响应头
1 | Connection: keep-alive |
不管客户端是否显式要求长连接,如果服务器支持长连接,它总会在响应报文里放一个“Connection: keep-alive”字段,告诉客户端:“我是支持长连接的,接下来就用这个 TCP 一直收发数据吧“。
1 | Connection:close //这次请求后就可以关闭长连接。 |
Keep-Alive: timeout=value
限定长连接的超时时
Cookie
响应头字段:Set-Cookie,请求头字段:Cookie
cookie 都是 key=value 的形式,多个 cookie,响应头使用多个 Set-Cookie,请求头会把他们用’;‘连接。
1 | //response |
Cookie 属性
- 有效期
- Expires 过期时间,截止日期,是一个绝对的时间点
- Max-Age 相对时间,单位是秒,浏览器用收到报文的时间 点再加上 Max-Age,就可以得到失效的绝对时间
Expires 和 Max-Age 可以同时出现,两者的失效时间可以一致,也可以不一致,但浏览器会优先采用 Max-Age 计算失效期。
如果不配置 Expires 和 Max-Age,Cookie 仅在浏览器运行时有效,一旦浏览器关闭就会失效。
如果 Max-Age 为负数,该 cookie 为临时 cookie ,关闭浏览器即失效,浏览器也不会以任何形式保存该 cookie 。
如果 Max-Age 为 0,表示删除该 cookie 。默认为 -1。
- 作用域
- Domain 域名
- Path 路径
浏览器发送 Cookie 前会从当前 URI 中提取 host 和 path,对比 Cookie 中的设置,如果不满足条件就不会在请求里面发送此 Cookie。
通常 Path 就用一个“/”或者直接省略,表示域名下的任意路径都允许使用 Cookie,让服务器 自己去挑。
安全性
HttpOnly 防止 XSS(跨站脚本攻击)
前端通过 JS 脚本获取 Cookie,会带来安全隐患。这个属性就是告诉浏览器此 Cookie 只能通过浏览器 HTTP 协议传输,禁止其他方式访问。SameSite 防止 CSRF(跨站请求伪造)
- SameSite=Strict,可以严格限定 Cookie 不能随着跳转链接跨站发送,
- SameSite=Lax,则略宽松一点,允许 GET/HEAD 等安全方法,但禁止 POST 跨站发送。
- SameSite=Secure,表示这个 Cookie 仅能用 HTTPS 协议加密传输,明文的 HTTP 协议会禁止发送。但 Cookie 本身不是加密的,浏览器里还是以明文的形式存在。
Cookie 应用
- 身份识别,保存用户的登录信息,实现会话事务
- 广告跟踪
HTTP 协议方法
- POST 传输内容
- GET 获取内容
- PUT 更新
- HEAD 获取报文首部
- DELETE 删除文件
POST 和 GET 请求的区别
- GET 请求的参数在 URL 上,POST 请求放在 request.body 里面
- GET 请求回退无害,POST 请求会再次提交请求
- GET 请求会被浏览器主动缓存,POST 不会,需手动设置
- GET 请求比 POST 更不安全,参数直接暴露在 URL 上,不能传敏感信息
- GET 请求的参数会保存在浏览器历史记录里面,POST 不会
HTTP 状态码
- 1xx:指示请求,表示请求已接收,继续处理
2xx:请求正常接收
- 200 OK
- 204 Not Content:与 200 基本相同但响应头后没有 body 数据
- 206 Partial Content : 客户端发送了一个带有 Range 请求头的 Get 请求,是表明自己只需要 url 上部分的资源,服务器完成了它。 比如:video audio 播放一个很大的视频/音频地址时,一般会返回 206。通常伴随头字段’Content-Range’,表示响应报文里 body 数据的具体范围,供客户端确认,列如:“Content-Range: bytes 0-99/2000”,意思是此次获取的是总计 2000 个字节的前 100 个字节。
3xx:重定向
- 301 Moved Permanently:永久重定向
- 302 Found:临时重定向
301 和 302 都会在响应头里使用 Location 指明后续要跳转的 URI,最终结果浏览器都会重新定向到新的 URI(可以是相对的也可以是绝对的),这个过程客户是无感知的。 - 303 See Other:类似 302,但是要求重定向后的请求改为 GET 方法,访问一个结果页面,避免 POST/PUT 重复操作。
- 307 Temporary Redirect:类似 302,但重定向后请求里的方法和实体不允许变动,含义比 302 更明确。
- 308 Permanent Redirect:类似 307,不允许重定向后的请求变动,但它是 301“永久重定向”的含义。
- 304 Not Modified:用于 If-Modified-Since 等条件请求,表示资源未修改,不跳转(重定向到已缓存的文件)
4xx:客户端错误,请求报文有误,服务器无法处理
- 400 Bad Request:客户端语法错误
- 401 Unauthorized:请求要求用户的身份认证
- 403 Forbidden:表示服务器禁止访问资源
- 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 Request:客户端发送太多请求,通常是由于服务器的限连策略
- 431 Request Header Fields Too Large:请求头某个字段或总体太大
- 5xx:服务端错误
- 500 Internal Server Error:服务端不可预计的错误
- 501 Not Implemented:表示客户端请求的功能还不支持
- 502 Bad Gateway:常是服务器作为网关或者代理时返回的错误码,表示服务器自身工作正常,访问后端服务器时发生了错误。
- 503 Service Unavailable: 服务器当前不能处理客户端的请求 临时过载和当机,所以 503 响应报文里通常还会有一个“Retry-After”字段,指示客户端可以在多久以后再次尝试发送请求。
短连接/无连接/串行连接 short-lived connections
通信过程:请求-应答
底层数据传输基于 TCP/IP,每次发送请求前需要先与服务器建立连接,收到响应报文后立即关闭连接。
TCP 建立连接要有“三次握手”,发送 3 个数据包,需要 1 个 RTT;关闭连接是“四次挥手”,4 个数据包需要 2 个 RTT。
HTTP 的一次简单“请求 - 响应”通常只需要 4 个包,如果不算服务器内部的处理时间,最多是 2 个 RTT。这么算下来,浪费的时间就是3÷5=60%
长连接
“持久连接”(persistent connections)、“连接保活”(keep alive)、“连接复用”(connection reuse)
一定时间内,统一域名下的 HTTP 请求,只要两端都没有提出断开连接,则保持 TCP 连接状态,其他请求可以复用这个连接通道。
HTTP1.1默认所有连接都是持久连接,不需要用什么特殊的头字段指定,只要向服务器发送了第一次请求,后续的请求都会重复利用第一次打开的 TCP 连接。
_优点_:减少了建立连接造成的网络资源和通信时间的浪费。
缺点 1:阻塞模式,下次请求必须等到上次响应返回后才能发起。
缺点 2:TCP 连接长时间不关闭,服务器必须在内存里保存它的状态,这就占用了服务器的资源。如果有大量的空闲长连接只连不发,就会很快耗尽服务器的资源,导致服务器无法为真正有需要的用户提供服务。
关闭长连接
- 客户端加上请求头
Connection:close
关闭长连接。 服务器端通常不会主动关闭连接,但也可以使用一些策略。拿 Nginx 来举例,它有两种方式:
- 使用“keepalive_timeout”指令,设置长连接的超时时间,如果在一段时间内连接上没有任何数据收发就主动断开连接,避免空闲连接占用系统资源。
- 使用“keepalive_requests”指令,设置长连接上可发送的最大请求次数。比如设置成 1000,那么当 Nginx 在这个连接上处理了 1000 个请求后,也会主动断开连接
客户端和服务器都可以在报文里附加通用头字段“Keep-Alive: timeout=value”,限定长连接的超时时间
队头阻塞 Head-of-line blocking
由请求-应答模式导致的,与短连接或长连接无关。
HTTP 规定报文必须是”一发一收“,形成了一个先进先出的串行队列,如果队首的请求因为处理的太慢耽误时间,后续的请求也得一起等待,就承担了不应用的时间成本。
性能优化
并发连接
同时对一个域名发起多个长连接,用数量来解决质量的问题
缺陷:每个客户端都想自己快,建立很多个连接,用户数 × 并发数就会是个天文数字。服务器的资源扛不住,或者被服务器认为是恶意攻击,反而会造成“拒绝服务。
域名分片
客户端使用多个域名指向一台服务器
缓存
服务器的缓存控制
Catch-Control
服务器标记的资源有效使用日期。
max-age=30: 生存时间,单位秒
时间的计算起点是响应报文的创建时刻,包含了在链路传输过程中所有节点所停留的时间。no-store:不允许缓存,用于某些变化非常频繁的数据
- no-cache:可以缓存,但是使用之前必须要跟服务器验证是否过期,是否有最新版本
- must-revalidate:如果缓存不过期就可以继续使用,但是过期了如果想用就必须去服务器验证。
1 | Catch-Control:max-age=10,must-revalidate; |
客户端的缓存控制
Catch-Control
请求头也可以使用。Catch-Control:max-age=0
,表示浏览器不使用缓存。Ctrl+F5
的强制刷新,其实是发起一个Catch-Control:no-cache
浏览器也可以发送“Cache-Control”字段,使用“max-age=0”或“no_cache”刷新数据。
条件请求
浏览器用“Cache-Control”做缓存控制只能是刷新数据,不能很好地利用缓存数据,又因 为缓存会失效,使用前还必须要去服务器验证是否是最新版。
响应头
Last-Modified
文件的最后的修改时间
ETag
实体标签(Entity Tag)的缩写,是资源的一个唯一标识,主要是用来解决修改时间无法准确区分文件变化的问题。
强 ETag 要求资源在字节级别必须完全相符,弱 ETag 在值钱有个‘W/’标记,只要求资源在语义上没有变化,但是内部可能会有部分发生了改变。
请求头
If-Modified-Since
上次响应头返回的 Last-Modified
If-None-Match
上次响应头返回的 ETag
如果缓存有效服务器就会返回 304。
代理服务
服务本身不生产内容,而是处于中间位置转发上下游的请求和响应,具有双重身份。
面向下游用户,表现为服务器,代表服务器响应客户端请求;
面向上游源服务器,又表现为客户端,代表客户端发送请求。
常用反向代理
代理的作用
负载均衡
在面向客户端时屏蔽了源服务器,客户端看到的只是代理服务器,这样代理就可以根据负载均衡算法尽量把外部的流量合理分散到多台源服务器,提供系统的整体资源利用率和性能。健康检查
使用”心跳“等机制监控后端服务器,发现有故障就及时剔除集群,保证服务质量。安全防护
保护被代理的后端服务器,限制 IP 地址或流量,抵御网络攻击和过载。- 加密卸载
对外网使用 SSL/TLS 加密通信认证,而在安全的内网不加密,消除加解密成本。 - 数据过滤
拦截上下行的数据,任意指定策略修改请求或响应 - 内容缓存
暂存、复用服务器响应。
代理相关头字段
Via
Via 是一个通用字段,请求头或响应头里都可以出现。每当报文经过一个代理节点,代理服务器就会把自身的信息(代理主机名/域名)追加到字段的末尾。
但服务器的 IP 地址应该是保密的,关系到企业的内网安全,所以一般不会让客户端知道。
通常服务器需要知道客户端的真实 IP 地址,方便做访问控制、用户画像、统计分析。
X-Forwarded-For
为谁转发,
每经过一个代理节点就会在字典里追加请求放的 IP 地址,所以最左边的 IP 地址就是客户端地址。
X-Real-IP
记录客户端 IP 地址,没有中间的代理信息。
代理协议
通过“X-Forwarded-For”操作代理信息必须要解析 HTTP 报文头,这对于代理来说 成本比较高,原本只需要简单地转发消息就好,而现在却必须要费力解析数据再修改数据, 会降低代理的转发性能,有些情况下是不允许甚至不可能的修改。
”代理协议”有 v1 和 v2 两个版本,v1 和 HTTP 差不多,也是明文,而 v2 是二进制格式。
v1 在 HTTP 报文前增加了一行 ASCII 码文本,相当于又多了一个头。
这一行文本其实非常简单,开头必须是“PROXY”五个大写字母,然后是“TCP4”或 者“TCP6”,表示客户端的 IP 地址类型,再后面是请求方地址、应答方地址、请求方端口号、应答方端口号,最后用一个回车换行(\r\n)结束。
不过代理协议并不支持“X-Forwarded-For”的链式地址形式,所以拿到客户端地址后再 如何处理就需要代理服务器与后端自行约定。
缓存代理服务
缓存代理服务收到源服务器发来的响应数据后,把报文转发给客户端,同时把报文存入自己的 Cache。
这样下一次再有相同请求,代理服务器就可以直接发送 304 或者缓存数据,不必再从原服务器获取,降低客户端的等待时间,节约了源服务器的网络带宽。
缓存控制
服务器缓存控制
Cache-Control: max-age,no-store,no-cache,must-revalidate
上面的属性也可以约束代理。
客户端上的缓存和代理上的缓存可以使用两个新属性:- private:表示缓存只能在客户端保存,是用户私有的,不能放在代理上与别人共享
- public:缓存完全开放,谁都能用,默认值。
缓存失效后重新验证也要分开(即使用条件请求“Last-Modified”和”ETag”), “must-revalidate”是只要过期就必须回源服务器验证。
“proxy-revalidate”只要求代理实务缓存过期后必须验证,客户端不必回源,只验证到代理。缓存的生存时间使用新的“s-maxage”(s 是 share 的意思,注意 maxage 中间没有“-”),只限定在代理上能够存多久,而客户端仍然使用“max_age。
还有一个代理专用的属性“no-transform”。代理有时候会对缓存下来的数据做一些优化,比如把图片生成 png、webp 等几种格式,方便今后的请求处理,而“no-transform”就会禁止这样做。
源服务器在设置完“Cache-Control”后必须要为报文加上“Last-modified”或“ETag”字段。否则,客户端和代理后面就无法使用条件请求来验证缓存是否有效,也就不会有 304 缓存重定向。
下面的流程图是完整的服务器端缓存控制策略,可以同时控制客户端和代理。
客户端缓存控制
max-age,no-store,no-cache 也同样作用域代理和源服务器。
max-stale:如果代理上的缓存过期也可以接受,但不能过期太久,超时也不能用。
min-fresh:缓存必须有效,而且必须在 x 秒后依然有效。
only-if-cached:表示只接受代理缓存的数据,不接受源服务器的响应。如果代理上没有缓存或者缓存过期,就应该给客户端返回一个 504(Gateway Timeout)
HTTPS
安全通信
机密性 Secrecy/Confidentiality
是指对数据的保密,只能由可信的人访问,对其他人是不可见的机密,就是不能让不相关的人看到不该看的东西。完整性 Integrity 一致性
是指数据在传输过程中没有被篡改。身份认证 Authentication
是指确认对方的真实身份,保证消息只能发送给可信的人。不可否认 Non-repudiation/Undeniable
不可抵赖。不能否认已经发生过的行为。
默认端口号 443,
满足四大安全特性:机密性,完整性,不可否认,身份认证m-安全
HTTPS 下层的传输协议不是 TCP/IP 而是 SSL/TLS,HTTPS over SSL/TLS,收发报文不再使用 Socket API,而是调用专门的安全接口
SSL/TLS
SSL,安全套接层(Secure Sockets Layer),在 OSI 模型中处于第 5 层(会话层)。
SSLv3 版本已经很完善,1999 年改名为 TLS(传输层安全,Transport Layer Security),即 TSL1.0 就是 SSLv3.1。
目前应用最广泛的 TLS 是 1.2。
TLS 由记录协议、握手协议、警告协议、变更密码规范协议、扩展协议等几个子协议组成,综合使用了对称加密、非对称加密、身份认证等许多密码学前沿技术。
浏览器和服务器在使用 TLS 建立连接时需要选择一组恰当的加密算法来实现安全通信,这些算法的组合被称为‘密码套件’。
密码套件的命名规范:密钥交换算法+签名算法+对称加密算法+摘要算法
机密性:加密,完整性:接口签名算法,身份认证:数字签名
上图显示环境使用的 TLS 是 1.2,客户端和服务器都支持非常多的密码套件,而最 后协商选定的是 ECDHE-RSA-AES256-GCM-SHA384。
握手时使用 ECDHE 算法进行密钥交换,用 RSA 签名和身份认证,握手后的通信使用 AES 对称算法,密钥长度 256 位,分组模式是 GCM,摘要算法 SHA384 用于消息认证和 产生随机数。
OpenSSL 是著名的开源密码学工具包,是 SSL/TLS 的具体实现。
TLS 协议的组成
记录协议 record Protocol
规定了 TLS 收发数据的基本单位:记录(record),所有的子协议都通过记录协议发出,多个记录数据可以在一个 TCP 包里一次性发出,也并不需要像 TCP 那样返回 ACK。警报协议 Alert Protocol
向对方发出警报信息,有点像 HTTP 协议里的状态码。握手协议 Handshake Protocol
浏览器和服务器会在握手过程中协商 TLS 版本号、随机数、密码套件等信息,然后交换证书和密钥参数,最终双方协商得到的会话密钥,用于后续的混合加密系统。变更密码规范协议 Change Cipher Spec Protocol
通知对方,后续的数据都将使用加密保护,之前的数据都是明文。
ECDHE 握手
Client Hello
1
2
3
4
5
6Handshake 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)客户端向服务器发送客户端 TLS 版本号(Version),支持的密码套件(Cipher),随机数 Random(用于后续生成会话密钥)。
Server Hello
1
2
3
4Handshake Protocol: Server Hello
Version: TLS 1.2 (0x0303)
Random: 0e6320f21bae50842e96…
Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)确认 TLS 版本号,随机数,从客户端 Cipher 里面选择一个用于本次通信的密码套件
服务器把证书发送给服务器(Server Certificate),证明身份。
Server Key Exchange
因为服务器选择 ECDHE 算法,服务器发送椭圆曲线的公钥(Server Params),用来实现密钥交换算法,再加上自己的私钥签名认证。1
2
3
4
5
6
7Handshake 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...Server Hello Done
服务器消息完毕,第一个消息往返结束,两个 TCP 包,共享三个信息 Server Random、Server Random 和 Server Params.- 客户端逐级验证,确认证书的真实性
Client Key Exchange
客户端按照密码套件的要求,也生成一个椭圆曲线的公钥(Client Params),用“Client Key Exchange”消息发给服务器1
2
3Handshake Protocol: Client Key Exchange
EC Diffie-Hellman Client Params
Pubkey: 8c674d0e08dc27b5eaa…Pre-Mater
客户端和服务器用两个公钥(Server Params、Client Params),利用 ECDHE 算出 Pre-Master,也是一个随机数。
现在客户端和服务器手里有了三个随机数:Client Random、Server Random 和 Pre-Master。用这三个作为原始材料,就可以生成用于加密会 话的主密钥,叫“Master Secret”。
主密钥有 48 字节,但它也不是最终用于通信的会话密钥,还会再用 PRF 扩展出更多的密钥,比如客户端发送用的会话密钥(client_write_key)、服务器发送用的会话密钥(server_write_key)等等,避免只用一个密钥带来的安全隐患。Change Cipher Spec
后面都改用对称算法加密通信了,用的 AES- Finished 消息
Finished 消息,之后把之前所有发送的数据做个摘要,再加密一下,让服务器做个验证。 - 服务器也是同样的操作,发“Change Cipher Spec”和“Finished”消息,双方都验证加密解密 OK,握手正式结束,后面就收发被加密的 HTTP 请求和响应了。
与传统的握手有两点不同:
- 使用 ECDHE 实现密钥交换,而不是 RSA,所以会在服务器端发出“Server Key Exchange”消息。
- 因为使用了 ECDHE,客户端可以不用等到服务器发回“Finished”确认握手完毕,立即就发出 HTTP 报文,省去了一个消息往返的时间浪费。这个叫“TLS False Start”,意思就是“抢跑”,和“TCP Fast Open”有点像,都是不等连接完全建立就提前发应用数据,提高传输的效率。
RAS 握手过程
大体的流程没有变,只是“Pre-Master”不再需要用算法生成,而是客户端直接生成随机数,然后用服务器的公钥加密,通过“Client Key Exchange”消息发给服务器。服务器再用私钥解密,这样双方也实现了共享三个随机数,就可以生成主密钥。
双向认证
双向认证的流程也没有太多变化,只是在“Server Hello Done”之后,“Client Key Exchange”之前,客户端要发送“Client Certificate”消息,服务器收到后也把证书链走一遍,验证客户端的身份。
TLS1.3
三个主要改进目标:兼容、安全与性能。
Client Hello
开头的版本号、支持的密码套件和随机数(Client Random)(32 个字节。
注意“Client Hello”里的扩展,“supported_versions”表示这是 TLS1.3,“supported_groups”是支持的曲线,“key_share”是曲线对应的参数。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19Handshake 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: secp256r1Server Hello
给出一个随机数(Server Random)和选定密码套件。
supported_versions”里确认使用的是 TLS1.3,然后在“key_share”扩展带上曲线和对应的公钥参数。
1 | Handshake Protocol: Server Hello |
主密钥
这时只交换了两条消息,客户端和服务器就拿到了四个共享信息:Client Random 和 Server Random、Client Params 和 Server Params,两边就可以各自用 ECDHE 算出“Pre-Master”,再用 HKDF 生成主密钥“Master Secret”,效率比 TLS1.2 提高了一大截。Change Cipher Spec
服务器立刻发出“Change Cipher Spec”消息,比 TLS1.2 提早进入加密通信,后面的证书等就都是加密的了,减少了握手时的明文信息泄露。
这里 TLS1.3 还有一个安全强化措施,多了个“Certificate Verify”消息,用服务器的私钥把前面的曲线、套件、参数等握手数据加了签名,作用和“Finished”消息差不多。但由于是私钥签名,所以强化了身份认证和和防窜改。
HTTP/2 多路复用
每个 HTTP 请求都有有个序列标识符,浏览器并发多个请求,服务器接收到数据后,根据序列标识符重新排序成不同的请求报文,而不会导致数据错乱。同样,服务端也可以并发返回多个响应给浏览器,浏览器收到后根据序列标识重新排序并归入各自的请求的响应报文。并且同一个域名下的所有请求都复用同一个 TCP 连接,极大增加了服务器处理并发的上限。
区别
HTTP1.1 在 HTTP1.0 基础上的改进
缓存处理
0 在 HTTP1.0 中主要使用header
里的If-Modified-Since
,Expires
来做为缓存判断的标准,HTTP1.1 则引入了更多的缓存控制策略例如Entity tag
,If-Unmodified-Since
,If-Match
,If-None-Match
等更多可供选择的缓存头来控制缓存策略。带宽优化及网络连接的使用
HTTP1.0 并且不支持断点续传功能,HTTP1.1 则在请求头引入了 range 头域,它允许只请求资源的某个部分,即返回码是 206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。HTTP1.1 支持只发送header
消息,服务器任务客户端有访问该接口的权限,返回 100,客户端再发送 body,如果服务端认为客户端无访问权限,返回 401,客户端就不需要发送 body 了。错误通知的管理
在 HTTP1.1 中新增了 24 个错误状态响应码,如409(Conflict)
表示请求的资源与资源的当前状态发生冲突;410(Gone)
表示服务器上的某个资源被永久性的删除。Host 头处理
在 HTTP1.0 中认为每台服务器都绑定一个唯一的IP
地址,因此,请求消息中的 URL 并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个 IP 地址。HTTP1.1 的请求消息和响应消息都应支持 Host 头域,且请求消息中如果没有 Host 头域会报告一个错误(400 Bad Request)。长连接
HTTP 1.0 需要使用keep-alive
参数来建立一个长连接,而 HTTP1.1 默认支持长连接
HTTP2.0 在 HTTP1.1 基础上的改进
多路复用
同一个连接并发处理多个请求,而且并发请求的数量是 HTTP1.1 的好几个数量级。HTTP1.1 也可以通过建立多个 TCP 连接来支持处理更多并发请求,但是创建 TCP 连接本身也是有开销的。头部数据压缩
HTTP1.1 中的 HTTP 请求都是由请求行或状态行、请求头或响应头、消息主体三部分组成,一般消息主体都会经过 gzip 压缩,或者本身传输的就是压缩后的二进制文件,但是行和头没经过压缩,以纯文本传输。随着 Web 功能越来越复杂,每个页面产生的请求数也越来越多,导致在头部消耗的流量越来越多,尤其是每次都要传输 UserAgent、Cookie 这类不会频繁变动的内容,完全是一种浪费。
HTTP2.0 使用 HPACK 算法对 header 的数据进行压缩,这样数据体积小了,在网络上传输就会更快。服务端推送
服务端推送是一种在客户端请求之前发送数据的机制。网页使用了许多资源:HTML、样式表、脚本、图片等等。在 HTTP1.1 中这些资源每一个都必须明确地请求。这是一个很慢的过程。浏览器从获取 HTML 开始,然后在它解析和评估页面的时候,增量地获取更多的资源。因为服务器必须等待浏览器做每一个请求,网络经常是空闲的和未充分使用的。
为了改善延迟,HTTP2.0 引入了 server push,它允许服务端推送资源给浏览器,比如一些必要的附加资源等等,在浏览器明确地请求之前,免得客户端再次创建连接发送请求到服务器端获取。这样客户端可以直接从本地加载这些资源,不用再通过网络。二进制格式(Binary Format)
HTTP1.x 的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认 0 和 1 的组合。基于这种考虑 HTTP2.0 的协议解析决定采用二进制格式,实现方便且健壮。