十一、HTTP的特点
1. HTTP 是灵活可扩展的,可以任意添加头字段实现任意功能;
2. HTTP 是可靠传输协议,基于 TCP/IP 协议“尽量”保证数据的送达;
3. HTTP 是应用层协议,比 FTP、SSH 等更通用功能更多,能够传输任意数据;
4. HTTP 使用了请求 - 应答模式,客户端主动发起请求,服务器被动回复请求;
5. HTTP 本质上是无状态的,每个请求都是互相独立、毫无关联的,协议不要求客户端或服务器记录请求相关的信息。
十二、HTTP的优点和缺点
1. HTTP 最大的优点是简单、灵活和易于扩展;
2. HTTP 拥有成熟的软硬件环境,应用的非常广泛,是互联网的基础设施;
3. HTTP 是无状态的,可以轻松实现集群化,扩展性能,但有时也需要用 Cookie 技术来实
现“有状态”;
4. HTTP 是明文传输,数据完全肉眼可见,能够方便地研究分析,但也容易被窃听;
5. HTTP 是不安全的,无法验证通信双方的身份,也不能判断报文是否被窜改;
6. HTTP 的性能不算差,但不完全适应现在的互联网,还有很大的提升空间
十三、HTPP的实体数据
1、数据类型与编码
MIME type 和 Encoding type 解决了计算机理解 body 数据的问题
在 TCP/IP 协议栈里,传输数据基本上都是“header+body”的格式。
TCP、UDP 因为是传输层的协议,只要把数据发送到对方就算是完成了任务。
HTTP 协议是应用层的协议,数据到达之后,还必须要告诉上层应用数据类型
MIME type:“多用途互联网邮件扩展”,用来标记 body 的数据类型
MIME 把数据分成了八大类,每个大类下再细分出多个子类,形式是“type/subtype”的字符串。
1. text:即文本格式的可读数据,我们最熟悉的应该就是text/html 了,表示超文本文档,此外还有纯文本text/plain、样式表 text/css 等。
2. image:即图像文件,有 image/gif、image/jpeg、image/png 等。
3. audio/video:音频和视频数据,例如 audio/mpeg、video/mp4 等。
4. application:数据格式不固定,可能是文本也可能是二进制,必须由上层应用程序来解释。常见的有application/json,application/javascript、application/pdf 等,另外,如果实在是不知道数据是什么类型,像刚才说的“黑盒”,就会是application/octet-stream,即不透明的二进制数据。
HTTP 在传输时为了节约带宽,有时候还会压缩数据,还需要有一个“Encoding type”,告诉数据是用的什么编码格式,对方才能正确解压缩,还原出原始的数据。
Encoding type:
1. gzip:GNU zip 压缩格式,也是互联网上最流行的压缩格式;
2. deflate:zlib(deflate)压缩格式,流行程度仅次于gzip;
3. br:一种专门为 HTTP 优化的新压缩算法(Brotli)
数据类型内容协商的头字段:Accept(数据类型)、Accept-Encoding(压缩方式,没有表示客户端不支持压缩数据)、Content-Type(数据类型,也可用在请求报文,POST方法时必须要用)、Content-Encoding(压缩方式,没有表示数据没有被压缩)
Accept 请求头字段和 Content实体头字段
2、语言类型与编码
语言与编码内容协商的头字段:Accept-Language、ContentLanguage、Accept-Charset、Content-Type字段的数据类型后面用“charset=xxx”
3、内容协商的质量值
“q”参数表示权重来设定优先级,[0.01,1],默认值是 1,0表示拒绝。具体的形式是在数据类型或语言代码后面加一个“;”,然后是“q=value”.“;”的意义是小于“,”的
eg.Accept: text/html,application/xml;q=0.9,*/*;q=0.8
Vary 字段表示服务器依据了 Accept-Encoding、User-Agent 和 Accept 这三个头字段,然后决定了发回的响应报文,请求头变化时,Vary 也会随着响应报文一起变化。
十四、HTTP传输大文件的方法
1、数据压缩
压缩HTML等文本文件是传输大文件最基本的方法
2、分块传输
服务器化整为零,分块传输:响应报文里用头字段“Transfer-Encoding: chunked”来表示,意思是报文
里的 body 部分不是一次性发过来的,而是分成了许多的块(chunk)逐个发送
流式数据:body 数据的长度是未知的,无法在头字段“Content-Length”里给出确切的长度,只能用 chunked 方式分块发送。
Transfer-Encoding: chunked”和“ContentLength”这两个字段是互斥的,也就是说响应报文里这两个字段不能同时出现,一个响应报文的传输要么是长度已知,要么是长度未知(chunked)
分块传输的编码规则:16进制长度头 + 数据块
1. 每个分块包含两个部分,长度头和数据块;
2. 长度头是以 CRLF(回车换行,即\r\n)结尾的一行明文,用 16 进制数字表示长度;
3. 数据块紧跟在长度头后,最后也用 CRLF 结尾,但数据不包含 CRLF;
4. 最后用一个长度为 0 的块表示结束,即“0\r\n\r\n”。
3、范围请求
范围请求(range requests):允许客户端在请求头里使用Range头字段来表示只获取文件的一部分,客户端的“化整为零”
服务器支持范围请求:必须在响应头里使用字段“Accept-Ranges: bytes”;不支持“Accept-Ranges: none”或者没有此字段
范围请求格式:Range:bytes=x-y(x、y表示偏移量,从0开始;10-表示10到文件结尾,-1表示0到文件结尾倒数第二个)
服务器接收范围请求后:
1、检查范围,范围出界,返回416
2、范围正确,根据Range计算偏移量读取文件,返回206 PartialContent
3、添加一个响应头字段Content-Range:bytes x-y/length
4、发送数据
4、多段数据
一次请求多个范围
请求头字段:Range:bytes=x-y,x-y
响应头字段Content-Type:multipart/byteranges(表示报文的 body 是由多段字节序列组成的);boundary=xxx(给出段之间的分隔标记)
Content-Range:bytes x-y/length
课后问题:
1、分块传输中数据里含有回车换行(\r\n)不影响分块处理,因为分块前有数据长度说明
2、如果对一个被 gzip 的文件执行范围请求,那么这个范围是应用于原文件还是压缩后的文件呢?
Range是针对原文件的
十五、HTTP的连接管理
1、短连接
短连接:HTTP协议底层的数据传输基于 TCP/IP,每次发送请求前需要先与服务器建立连接,收到响应报文后会立即关闭连接,连接过程很短暂。
缺点:事件都被浪费在建立好关闭连接上,传输效率太低。严重制约了服务器的服务能力,导致它无法处理更多的请求
2、长连接
长连接:用到了成本均摊的思路,一次建立连接中多次请求响应
长连接上发送的请求越多,利用率也就越高
3、连接相关的头字段
HTTP/1.1中的连接默认启用长连接
请求头里明确地要求使用长连接机制的字段:Connection: keep alive
服务器支持长连接,它总会在响应报文里放一个“Connection: keep alive”字段
长连接也需要在恰当的时间关闭,请求头里加上Connection: close,响应报文里也加上这个字段,发送之后就调用 Socket API 关闭 TCP连接。服务器端通常不会主动关闭连接
服务器可以使用一些策略主动关闭连接:
1. 使用“keepalive_timeout”指令,设置长连接的超时时间,如果在一段时间内连接上没有任何数据收发就主动断开连接,避免空闲连接占用系统资源。
2. 使用“keepalive_requests”指令,设置长连接上可发送的最大请求次数。比如设置成 1000,那么当 Nginx 在这个连接上处理了 1000 个请求后,也会主动断开连接。
客户端和服务器都可以在报文里附加通用头字段“Keep-Alive: timeout=value”,限定长连接的超时时间
4、队头阻塞
队头阻塞”是由 HTTP 基本的“请求 - 应答”模型所导致的。形成了一个先进先出的“串行”队列。排在最前面的请求被最优先处理。
队首的请求因为处理的太慢耽误了时间,那么队列里后面的所有请求也不得不跟着一起等待,结果就是其他的请求承担了不应有的时间成本
5、性能优化
a、并发连接:同时对一个域名发起多个长连接,用数量来解决质量的问题
b、域名分片:多开几个域名,域名都指向同一台服务器,长连接的数量又增加了
十六、HTTP的重定向和跳转
主动跳转:由浏览器的使用者主动发起
被动跳转:由服务器来发起
重定向可以把一个URI指向另一个URI,也可以把多个URI指向一个URI,用途很多
1、过程
过程:重定向”实际上发送了两次HTTP 请求,第一个请求返回了 302,然后第二个请求就被重定向到了“/index.html”。
重定向是服务器发起的跳转,要求客户端改用新的URI重新发送请求,通常自动进行,用户无感知。
第一次请求的响应头的头字段“Location: /index.html”指示了要跳转的URI,可以用相对或绝对的形式。
Location”字段属于响应字段,只有配合 301/302 状态码才有意义,标记了服务器要求重定向的 URI。
浏览器收到 301/302 报文,会检查响应头里有没有“Location”。如果有,就从字段值里提取出 URI,发出新的 HTTP 请求,相当于自动替我们点击了这个链接。
重定向时只是在站内跳转,用相对 URI。要跳转到站外,就必须用绝对 URI
2、状态码
301:“永久重定向”
302:“临时重定向”
303 See Other:类似 302,要求重定向后的请求改为GET 方法,访问一个结果页面,避免POST/PUT 重复操作;
307 Temporary Redirect:类似 302,但重定向后请求里的方法和实体不允许变动,含义比 302 更明确;
308 Permanent Redirect:类似 307,不允许重定向后的请求变动,但它是 301“永久重定向”的含义
3、应用场景
a、资源不可用
b、避免重复
大幅度的改变:永久
原来的 URI 在将来的某个时间点还会恢复正常:临时
4、相关问题
性能损耗:一个跳转会有两次请求 - 应答,比正常的访问多了一次。站内重定向还好说,可以长连接复用,站外重定向就要开两个连接
循环跳转:A=>B=>C=>A
十七、HTTP的Cookie机制
1、Cookie
Cookie 并不属于 HTTP 标准(RFC6265,而不是RFC2616/7230),所以语法上与其他字段不太一致,使用的分隔符是“;”
HTTP 协议有个特点是无状态,不同请求间协议内容无相关性,即本次请求与上次请求没有内容
的依赖关系,本次响应也只针对本次请求的数据,至于服务器应用程序为用户保存的状态
是属于应用层,与协议是无关的。
Cookie :一种让HTTP 协议请求与请求之间建立起联系,并且服务器需要知道这个请求来自哪个用户的技术
2、Cookie工作过程
响应头字段Set-Cookie(key=value)和请求头字段Cookie
Cookie 本质上就是一份存储在用户本地的文件(用于存储少量的不敏感的信息),里面包含了每次请求中都需要传递的信息。
3、Cookie属性
a、设置 Cookie 的生存周期
Expires:绝对时间点,截止日期(deadline)。
Max-Age:相对时间,单位是秒,浏览器用收到报文的时间点再加上 Max-Age,就可以得到失效的绝对时间(优先采用)
b、设置 Cookie 的作用域
Domain:指定 Cookie 所属的域名
Path:指定 Cookie 所属的路径
浏览器在发送 Cookie 前会从 URI 中提取出 host 和 path 部分,对比 Cookie 的属性。如果不满足条件,就不会在请求头里发送 Cookie
c、Cookie 的安全性
HttpOnly:Cookie 只能通过浏览器 HTTP 协议传输,禁止其他方式访问,浏览器的 JS 引擎就会禁用 document.cookie 等一切相关的 API
SameSite:可以防范“跨站请求伪造”(XSRF)攻击,设置成“SameSite=Strict”可以严格限定 Cookie 不能随着跳转链接跨站发送,而“SameSite=Lax”则略宽松一点,允许 GET/HEAD 等安全方法,但禁止 POST 跨站发送。
Secure: Cookie 仅能用 HTTPS 协议加密传输,明文的HTTP 协议会禁止发送。但 Cookie 本身不是加密的,浏览器里还是以明文的形式存在
4、Cookie应用
a、身份识别,实现有状态的会话事务
b、广告跟踪
5、Session和Cookie
Cookie 以明文的方式存储在本地,而 Cookie 中往往带有用户信息,这样就造成了非常大的安全隐患。
Session
翻译过来就是会话,会话
是指客户端与服务端进行通讯的过程,比如用户在浏览器中点击一个超链接访问 Web 资源,到关闭该标签页就是一个会话过程。
工作过程:
- 客户端第一次请求,提交用户名密码等信息进行登录认证,服务器根据客户端提交的信息进行鉴权,鉴权成功后创建 Session 对象,用来保存相关数据,比如用户角色、登录时间等;
- 服务器响应时将此 Session 的唯一标识信息 SessionID 返回给客户端,客户端接收到后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名;
- 客户端之后的每一次请求,浏览器都会自动将当前域名下的 Cookie 信息发送给服务器;
- 服务器解析 Cookie,获取到 SessionID,查找与之对应的 Session 对象,如果 Session 对象存在说明用户已经登录,返回请求数据;
- 如果 Session 对象不存在或已过期,展示错误信息,并返回登陆页面。
Cookie 是存储在客户端(Client),而 Session 保存在服务端(Server)。
SessionID 是 Cookie 和 Session 中间的一道桥梁,需要借助 Cookie 的传递才有意义。
Cookie | Session | |
---|---|---|
存储方式 | Cookie 存储在客户端,方便与 JS 交互,方便获取用户信息。 | Session 存储在服务端,高效、安全,不依赖浏览器环境。 |
存储类型 | Cookie 只能存储字符串。 | Session 可以存储任意数据类型。 |
存储大小 | Cookie 大小有限制(4KB),同一域名下的数量也有限制(20 个) | Session 没有类似的限制。 |
生命周期 | Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能。 | Session 一般失效时间较短,客户端关闭(默认情况下)或者 Session 超时都会失效。 |
安全性 | Cookie 是本地存储,不是很安全,别人可以分析存放在本地的 Cookie 并进行欺骗,存在 CSRF 风险。 | |
特点 | Cookie 是客户端存储用户信息的一种机制,用来记录用户的一些信息,也是实现 Session 的一种方式。 | Session 是在服务端存储的一个数据结构,用来跟踪用户的状态,这个数据可以保存在内存、文件、数据库中。 |
总结 | 1. Cookie 遵循同源策略,不能跨域访问,除非特别部署。 2. SessionID 是客户端的唯一标识,存储在 Cookie 中,它是维持一个会话的核心。 3. Cookie 和 Session 都是为了在无状态的 HTTP 协议之上维护会话状态,使得服务端可以知道当前是和哪个客户在“打交道”。 |
其实,Cookie 和 Session 无法放在一起对比,因为它们维度不同,放在一起比较,容易误导别人造成混乱。
如果硬要进行比较,可把 Session 这一虚拟概念,分成两个实例,服务端 Session 和 客户端 Session,由服务端 Session 和 Cookie 进行比较(见上表)。
- Cookie 是一个 HTTP 头部,而 Session 是一个虚拟概念。
- Cookie 可以用于实现 Session,也可以不。反过来说,Session 也未必需要用 Cookie 实现。
- Session 指的是客户端与服务端维持的一个有状态会话,和 Cookie 不是两种互斥的实现。
- Session 不能说这是纯服务端的事情,跟客户端也有关,也需要 Cookie 的帮助。
- 客户端 Session 即 SessionID,存储在 Cookie 中,所以也是 Cookie。
- Cookie 按存储类型可分为会话 Cookie 和 持久化 Cookie,前者即 SessionID。
十八、HTTP的缓存控制
缓存(Cache)优化系统性能,HTTP 传输的每一个环节基本上都会有缓存
1、服务器的缓存控制
流程:
1. 浏览器发现缓存无数据,于是发送请求,向服务器获取资源;
2. 服务器响应请求,返回资源,同时标记资源的有效期;
3. 浏览器缓存资源,等待下次重用。
服务器标记资源有效期使用的头字段是“Cache-Control”,里面的值“maxage=30”就是资源的有效时间,时间的计算起点是响应报文的创建时刻。
no_store:不允许缓存,用于某些变化非常频繁的数据,例如秒杀页面;
no_cache:可以缓存,但在使用之前必须要去服务器验证是否过期,是否有最新的版本;
must-revalidate:如果缓存不过期就可以继续使用,但过期了如果还想用就必须去服务器验证
2、客户端的缓存控制
请求 - 应答的双方都可以用Cache-Control字段进行缓存控制,互相协商缓存的使用策略
“刷新”:浏览器会在请求头里加一个“Cache-Control: maxage=0”。max-age=0 的意思就是“我要一个最最新鲜的西瓜”,而本地缓存里的数据至少保存了几秒钟,所以浏览器就不会使用缓存,而是向服务器发请求。服务器看到 max-age=0,也就会用一个最新生成的报文回应浏览器
Ctrl+F5 的“强制刷新”:Cache-Control: no-cache”,含义和“max-age=0”基本一样。效果相同
“前进”“后退”“跳转”这些重定向动作中浏览器不会“夹带私货”,只用最基本的请求头,没有“Cache-Control”,所以就会检查缓存,直接利用之前的资源,不再进行网络通信
3、条件请求
HTTP 协议就定义了一系列“If”开头的“条件请求”字段,专门用来检查验证资源是否过期,把两个请求才能完成的工作合并在一个请求里做。
“if-Modified-Since”和“If-None-Match”。需要第一次的响应报文提供“Last-modified”(文件的最后修改时间)和“ETag”(精确地识别资源的变动情况),然后第二次请求时就可以带上缓存里的原值,验证资源是否是最新的。如果资源没有变,服务器就回应一个“304 Not Modified”,表示缓存依然有效,浏览器就可以更新一下有效期,然后放心大胆地使用缓存了。
强 ETag 要求资源在字节级别必须完全相符,弱 ETag 在值前有个“W/”标记,只要求资源在语义上没有变化,但内部可能会有部分发生了改变
课后问题:
1. Cache 和 Cookie 都是服务器发给客户端并存储的数据,你能比较一下两者的异同吗?
Cache 和 Cookie 的相同点是:都会保存到浏览器中,并可以设置过期时间。
不同点:
1. Cookie 会随请求报文发送到服务器,而 Cache 不会,但可能会携带 if-Modified-Since(保存资源的最后修改时间)和 If-None-Match(保存资源唯一标识) 字段来验证资源是否过期。
2. Cookie 在浏览器可以通过脚本获取(如果 cookie 没有设置 HttpOnly),Cache 则无法在浏览器中获取(出于安全原因)。
3. Cookie 通过响应报文的 Set-Cookie 字段获得,Cache 缓存的是完整报文。
4. 用途不同。Cookie 常用于身份识别,Cache 则是由浏览器管理,用于节省带宽和加快响应度。
5. Cookie 的 max-age 是从浏览器拿到响应报文时开始计算的,而 Cache 的 max-age 是从响应报文的生成时间(Date 头字段)开始计算。
2. 即使有“Last-modified”和“ETag”,强制刷新(Ctrl+F5)也能够从服务器获取最新数据(返回 200 而不是 304),请你在实验环境里试一下,观察请求头和响应头,解释原因。
强制刷新,请求头里有Pragma: no-cache和Cache-Control: no-cache,没有IfModified-Since/If-None-Match,这个Pragma: no-cache的意思是禁用缓存
十九、HTTP的代理服务
1、代理服务(反向代理)
HTTP 代理就是客户端和服务器通信链路中的一个中间环节,为两端提供“代理服务”;
代理处于中间层,为 HTTP 处理增加了更多的灵活性,可以实现负载均衡、安全防护、数据过滤等功能;
负载均衡:代理服务器就可以掌握请求分发的“大权”,决定由后面的哪台服务器来响应请求。负载均衡算法(轮询、一致性哈希),尽量把外部的流量合理地分散到多台源服务器,提高系统的整体资源利用率和性能。
2、代理相关头字段
a、字段“Via”标明代理的身份
通用字段,报文经过一个代理节点,就会追加代理主机名或者域名。Via: proxy1, proxy2
b、X-Forwarded-For
报文经过一个代理节点,就会追加请求方的 IP 地址
c、X-Real-IP:记录客户端 IP地址
d、X-Forwarded-Host:记录客户端请求的原始域名
e、X-Forwarded-Proto:记录客户端请求的原始协议名
3、代理协议
代理协议”可以在不改动原始报文的情况下传递客户端的真实 IP
代理协议在 HTTP 报文前增加了一行 ASCII 码文本,相当于又多了一个头。
格式:开头必须是“PROXY”五个大写字母,然后是“TCP4”或者“TCP6”,表示客户端的 IP 地址类型,再后面是请求方地址、应答方地址、请求方端口号、应答方端口号,最后用一个回车换行(\r\n)结束
服务器看到这样的报文,只要解析第一行就可以拿到客户端地址,不需要再去理会后面的HTTP 数据,省了很多事情
二十、HTTP的缓存代理
1、缓存代理服务
缓存代理是增加了缓存功能的代理服务,缓存源服务器的数据,分发给下游的客户端;
2、源服务器的缓存控制
a、Cache-Control”属性:max-age、no_store、no_cache 和 must-revalidate
b、“private”和“public”:区分客户端缓存和代理缓存
c、“must-revalidate”:只要过期就必须回源服务器验证,“proxy-revalidate”只要求代理的缓存过期后必须验证,客户端不必回源,只验证到代理这个环节就行了
d、s-maxage”:缓存的生存时间
e、no-transform:禁止代理对缓存数据修改
3、客户端的缓存控制
max-stale:如果代理上的缓存过期了也可以接受,但不能过期太多,超过 x 秒
也会不要。
min-fresh:缓存必须有效,而且必须在 x 秒后依然有效。
only-if-cached:表示只接受代理缓存的数据,不接受源服务器的响应。如果代理上没有缓存或者缓存过期,就应该给客户端返回一个504(Gateway Timeout)