Application Layer: 考虑怎样在网络应用程序和用户之间通信
应用架构
Email
HTTP
依赖于 TCP 和 UDP 协议
Hyper Text Transfer Protocol specifies communication of browser and server
HTTP 是不加密的
它以明文形式发送数据。即传输的所有内容,包括 URL、头信息、以及内容本身,可以被网络上任何可以访问数据传输路径的节点轻易读取或修改。例如,网络中间人(网络提供商、路由器、代理服务器)可以看到你的请求内容和响应内容。
HTTP 版本
HTTP 1.0
早期 Web 的基础协议,使用短连接 (Non-persistent HTTP),每次交互都需要新建立连接,无法有效复用连接,导致额外的延迟和资源消耗。→ 每进行一次 HTTP 操作,就建立一次连接,任务结束就中断连接。
没有标准的压缩方法规定
- at most one object sent over one TCP connection, then closed
- download multiple objects required multiple conections
- RTT (round-trip time): time for a small packet to travel from client to server and back
- one RTT to initiate TCP connection
- one RTT for HTTP request (for Web pages from Web server) and firstt few bytes of HTTP responsee to return
HTTP 1.1
引入长连接(Persistent HTTP),允许在一个连接上发送多个请求和响应,减少了建立和关闭连接的开销。
- 引入 PUT / DELETE 方法
- 引入 Cache-Control / ETag / Last-Modified 头部进行缓存(见下文 📦)
常用的压缩方法是 gzip 和 deflate
HTTP 2.0
- 二进制分帧层,将所有传输的信息分割为更小的消息和帧
- 文本形式传输,易于阅读和调试,但通常会较大
- 二进制格式传输,减少了带宽消耗和传输时间,有更快的解析速度
- 支持多路复用,允许在单一连接上并行地发送多个请求和响应,减少延迟
- 即使用一个 TCP 连接来处理多个 HTTP 请求和响应,这意味着不同的模块(如页面上的不同资源:HTML、CSS、JavaScript、图片等)可以通过同一个连接同时传输,而不需要为每个模块建立单独的连接。
- Webpack 按顺序发出这些块。浏览器可以在等待下载最新文件的同时开始执行缓存中的文件。当使用代码分割进行按需加载时,Webpack 会并行请求。
- 引入服务器推送功能,服务器可以主动向客户端发送资源,不必等待请求
- 可用于将包拆分为多个较小的块存入缓存,然后在请求 HTML 页面时将不同的 chunk 主动发送到客户端。
- 流量控制
- 首部压缩
- 静态字典 (Static Table):包含了一组预定义的常见 HTTP 头部键值对,在压缩和解压缩过程中用于对常见头部进行编码和解码,从而减少传输的开销。
- 动态字典 (Dynamic Table):动态字典是在 HTTP/2 通信过程中动态构建的,它存储了最近使用过的 HTTP 头部键值对。动态字典可以根据通信过程中的实际需要进行更新和调整,以适应不同的场景和请求。
HPACK 压缩方法
HTTP 3.0
使用基于 UDP 的 QUIC (Quick Udp Internet Connection) 协议作为传输层协议 → 解决了头部阻塞问题,允许请求和响应的头部信息在数据流中独立传输。
- 相较于 TCP,QUIC 具有更低的连接建立延迟和更好的拥塞控制
Header
通用头部
Content-Type
:请求体的媒体类型,如text/html
,application/json
Content-Length
:请求体或响应体的大小(字节)
Cache-Control
:指定请求和响应遵循的缓存机制
Connection
:控制是否保持网络连接开启或关闭
Date
:消息发送的日期和时间
请求头部
Accept
:客户端能够接收的内容类型
Accept-Language
:客户端能够理解的自然语言列表
User-Agent
:发起请求的客户端应用信息
Host
:请求发送的目标服务器地址
Authorization
:认证信息,如 Token
Cookie
:服务器发送到客户端的 Cookie
Referer
:引发请求的原始资源地址
响应头部
Set-Cookie
:服务器发送到客户端的 cookie
ETag
:资源的特定版本标识,用于缓存控制
Expires
:资源过期的日期时间
Last-Modified
:资源最后修改的日期时间
Server
:服务器软件信息。
WWW-Authenticate
:客户端适用的认证方案和参数
实体头部
Content-Encoding
:实体数据应用的编码方式,如gzip
Content-Language
:实体数据的自然语言
Content-Location
:请求资源可替代的备用位置
缓存机制
强缓存
Strong Cache,
使用场景 → 不经常更新的静态资源 (图片/ CSS / JS) 文件。同时可以配合修改版本号(app.v1.js
) 来触发新的下载。
通过设置 HTTP 响应头实现,主要包括以下两个响应头:
Cache-Control
public
:表示响应可以被任何缓存(包括浏览器缓存和 CDN)缓存。private
:表示响应只能被客户端(浏览器)缓存。- 对于 GET 请求的响应,默认是 public 的,而对于其他类型的请求(如 POST),则是 private 的。
max-age
:指定资源在缓存中的最大有效时间,以秒为单位。- 静态资源更新:如果资源在服务器上更新了,但
max-age
尚未过期,用户可能仍然得到旧版本的资源,因为浏览器不会去服务器检查新版本。 - 缓存管理:过度依赖
max-age
可能导致缓存存储迅速增长,需要更细致的缓存策略来避免不必要的缓存占用。 no-cache
:表示缓存需要向服务器验证资源是否过期,但仍然可以缓存。no-store
:表示不缓存响应的任何部分,始终从服务器获取最新的资源。
存在问题
Expires
:指定资源的过期时间
当客户端发送请求时,如果发现响应中包含了强缓存相关的响应头,并且缓存未过期,则客户端可以直接使用缓存的资源,而无需向服务器发起请求。
协商缓存
Conditional Cache / Negotiated Cache
通过与服务器进行通信来验证资源是否仍然有效 → 主要针对频繁变动的资源,例如 API 请求结果(动态内容与用户个性化设置),这些内容可能经常发生变化,但不一定每次都需要重新下载。
首先设置
Cache-Control: no-cache
,然后配合以下两种响应头实现Last-Modified
:服务器在响应中发送资源的最后修改时间。- 优点
- 使用的是时间戳,精度通常可以达到秒级,服务器可以比较容易地获取资源的最后修改时间。
- 是 HTTP/1.0 规范中定义的标准字段,与 HTTP/1.0 兼容的服务器和客户端可以支持它。
- 缺点
- 时间戳的精度有限,当两次修改在1秒内时,无法检测到变化。
- 如果服务器的时间与客户端的时间不同步,可能会导致问题。
ETag
:服务器在响应中发送资源的唯一标识符(通常是哈希值)。- 优点
- 服务器生成的唯一标识符,通常基于资源内容的哈希值或其他算法生成。客户端在请求资源时,可以将上次获取到的
ETag
值发送给服务器,服务器收到后会检查资源是否与该ETag
值匹配。如果资源没有发生变化,服务器将返回状态码 304(Not Modified),告诉客户端可以继续使用缓存的资源,否则服务器将返回新的资源内容。 - 可以更精确地判断资源是否发生了变化。
- HTTP/1.1 规范中定义的标准字段,更现代的应用通常会优先使用它。
- 缺点
- 生成和比较 ETag 会消耗一些服务器资源,尤其对于大文件或高流量网站。
- 一些代理服务器或 CDN 可能不支持 ETag,可能会导致不稳定的缓存效果。
当客户端发送请求时,如果发现响应中包含了协商缓存相关的响应头,客户端会将上一次请求中的
-
If-Modified-Since
(对应Last-Modified
)
- 或
If-None-Match
(对应ETag
)
请求头发送给服务器,服务器根据这些值判断资源是否发生了变化。如果资源没有变化,则返回 304 Not Modified 响应,客户端继续使用缓存的资源。
优先级上 ETag 更高,因为它可以更精确地判断资源是否变化。
请求方法
GET
使用 URL 查询字符串来传输数据→ 请求用于检索数据
- GET 请求的结果可以在一段时间内被缓存起来(包括浏览器/客户端/代理服务器等),便于未来的相同请求可以快速地获取响应
POST
将数据包含在 HTTP 请求的消息体中 → 请求涉及数据修改时,如提交表单、上传文件
- 默认情况下不会被浏览器缓存,每次发送 POST 请求都会获取新的响应
哪个刷新会丢失数据?
刷新页面时,GET 请求会重新发送相同的请求,而 POST 请求会弹出一个确认框,询问用户是否要重新提交表单数据。这意味着刷新页面时,POST 请求可能会导致数据的重复提交,可能会丢失数据。
PUT / DELETE
- PUT 用于创建或更新资源,DELETE 于删除资源。
- 它们的引入主要用于 RESTful 架构,增强资源操作清晰度,提升 API 设计质量。
状态码
2xx
- 200:OK
201:Created
通常在 POST 请求中使用,当服务器成功创建了一个新资源时。
202:Accepted
请求已接受,但尚未处理。
- 例如,异步处理任务。
203:Non-Authoritative Information
请求成功,但返回的信息可能不是来自原始服务器。
- 通常在代理服务器使用时会出现这种情况。
204:No Content
请求成功,但没有返回内容。
- 通常用于 DELETE 请求,表示删除操作成功但没有返回任何数据。
3XX
301:Moved Permanently
- 用户访问
example.com/old-page
时,服务器响应并重定向example.com/new-page
后,未来的请求会直接访问new-page
。 - 即 old 链接已经没有存在的意义了,它所有的价值都会导向给 new。对于搜索引擎来说 new 链接现在就是 old 链接了。所以 old 链接的权重会直接传给 new 链接。这就是常说的 301 对 SEO 有好处的原因。
- 301 是谷歌官方认为将网站地址由 HTTP 迁移到 HTTPS 的最佳方法。
302:Moved Temporarily
- 最常见的一种重定向方法,但同时也是现实与标准相矛盾的典型案例。在HTTP/1.1 中修改为 Found。
- 未来的请求仍然会访问
old-page
- 即 一般情况下都会实现为 GET 的重定向,但是不能确保 POST 会重定向为 POST。
- 302 似乎包含了 303 的情况 → 这是由历史原因导致的
虽然规范中不允许客户端在重定向时改变请求的方法,但是很多现存的浏览器使用 GET 方式访问在 Location中规定的 URI,而无视原先请求的方法。因此状态码 303 和 307 被添加了进来,用以明确服务器期待客户端进行何种反应。
303:See Other
- 即无论原请求是 GET 还是 POST,客户端收到服务端的响应后,必须使用 GET 方法重定向到新地址。
- 常用于将 POST 请求重定向到 GET 请求,比如上传了一份个人信息,服务器将你导向一个 “上传成功” 页面。
- 禁止响应被缓存。
304:Not Modified
[协商]缓存重定向,资源未修改,可继续使用之前的内容
307:Temporary Redirect
- 与 302 类似,未来的请求仍然会访问
old-page
- 但是明确要求客户端使用相同的 HTTP 方法进行重定向,即原本使用 POST,重定向依旧是 POST。
308:Permanent Redirect
- 与 301 类似,未来的请求直接会访问
new-page
- 但是明确要求客户端使用相同的 HTTP 方法进行重定向…
4XX
- 400:服务器无法理解请求的格式
- 401:(Unauthorized) 请求未授权,需要身份验证
- 403:(Forbidden) 服务器理解请求,但拒绝执行。
- 客户端虽然提供了有效的身份验证凭据,但由于权限限制而失败
5XX
- 500:内部服务器错误
- 504:网关超时,未能及时从上游服务器接收到响应时
HTTPS
Hyper Text Transfer Protocol Secure
HTTPS 是 HTTP 协议通过 (Secure Socket Layer / Transport Layer Security) 加密后的版本。
- SSL 是 TLS 的前身,可以理解为应用层与传输层之间的独立层。
随便打开一个线上网站,然后利用 Chrome 的开发者工具,可以看到发送了很多 HTTPS 请求,也能看到请求的内容,这难道加密了吗?
- 当使用浏览器发送 HTTPS 请求时,浏览器首先在内部构建请求信息,包括 URL、头信息和可能的请求体。这时信息是明文的,它还没离开浏览器。
- 在发送到网络之前,浏览器使用与服务器协商的加密算法和密钥加密这些数据。然后,这些加密后的数据被发送到网络。
加密方式
(非) 对称加密
- 握手阶段: 在 SSL/TLS 握手阶段,非对称加密用于安全地交换对称密钥。客户端生成的 Pre-Master Secret 会使用服务器的公钥加密后发送给服务器,服务器使用自己的私钥解密,从而确保了对称密钥的安全传输。
- 身份验证: 非对称加密也用于服务器的身份验证。服务器会发送自己的数字证书(包含公钥),客户端通过 CA 认证的方式验证证书的合法性,以此确认服务器的身份。
- 数据传输阶段: 在握手阶段结束后,客户端和服务器生成了一个共享的对称密钥,用于加密后续所有的通信数据。这种方式可以确保通信效率,因为对称加密的计算复杂度较低,适合快速加密和解密大量数据。
数字证书
包含一个公钥和与之相关联的私钥,这种非对称加密方法使得只有拥有私钥的人能解密,安全性更高但速度较慢。
- 证书如何 “防篡改 / 防调包” ?
- 证书由权威的 CA(证书颁发机构)签名,包含了服务器的公钥、颁发者信息、有效期等。证书一旦被篡改,签名验证就会失败,浏览器会发出警告。
- SSL / TLS 提供的握手过程,确保中间人无法在不被发现的情况下替换证书。
连接过程
相当于 TLS 的 四次握手
- 客户端发起连接:浏览器(客户端)向服务器发起 HTTPS 请求,给出协议版本号、一个客户端生成的随机数(Random secret),以及客户端支持的加密方法
- 服务器响应:服务端确认双方使用的加密方法,并给出数字证书、以及一个服务器生成的随机数(Premaster secret)
- 证书验证:客户端验证数字证书的合法性(是否来自受信任的 CA,是否未过期,并确认与服务器域名匹配)
- 密钥交换:客户端生成一个新的随机数,并使用数字证书中的公钥,加密这个随机数,发给服务端
- 服务器解密密钥:服务器使用自己的私钥来解密客户端发送的加密密钥(随机数)
- 安全通信:双方使用协商好的对称密钥(Session key,由前面的三个随机数生成)在加密通道中传输数据
DNS
依赖于 TCP 和 UDP 协议
Domain name system is a directory service that provides a mapping between the name of a host on the network and its IP address.
- 计算机既可以被赋予 IP 地址,也可以被赋予主机名和域名。
- 用户通常使用主机名或域名来访问对方的计算机,而不是直接通过 IP 地址访问。
DNS 域名解析,输入和输出是什么?
- 输入:用户输入的域名(如
www.example.com
)
- 输出:与该域名对应的 IP 地址
解析过程
首先操作系统会检查本地缓存中是否有这个域名对应的 IP 地址,如果有,直接使用。如果本地缓存没有找到,系统会查看本地的
hosts
文件,看看是否有相关记录。如果以上均没有找到,系统会向本地配置的 DNS 服务器(通常是 ISP 提供的)发送 DNS 查询请求。
递归查询
→ 大多数客户端(如个人电脑、智能手机等)
客户端将查询请求发送给一个 DNS 服务器,如果这个 DNS 服务器不直接拥有请求的记录,它将代表客户端向其他 DNS 服务器查询,直到找到答案。
迭代查询
→ 大多数公共 DNS 服务器和 ISP 的DNS服务器
客户端向 DNS 服务器发送查询请求,如果服务器没有答案,它不会代表客户端进行查询,而是返回一个对客户端有帮助的 “最佳” DNS 服务器地址。
记录类型
URL
Uniform Resource locator serves as system for resources on web
当在浏览器中输入一个 URL 后,会发生什么?
- 解析 URL:游览器根据 URL 中的协议、域名、资源路径来确定要请求的资源
- 域名解析:为了找到服务器的 IP 地址,浏览器需要进行 DNS 查询。
- 如果域名的 IP 地址未缓存在浏览器或操作系统的 DNS 缓存中,浏览器会向配置的 DNS 服务器发起查询。
- 这个查询可能是递归的,也可能是迭代的,直到找到域名对应的IP地址。
- 建立 TCP 连接:浏览器使用 HTTP (端口 80) 或 HTTPS (端口 443) 协议与服务器建立 TCP 连接。
- 如果使用 HTTPS,会涉及 SSL/TLS 握手建立安全连接 → 展开(HTTPS连接过程)
- 发送请求:浏览器构建一个HTTP(或HTTPS)请求。请求包括 HTTP 方法(例如 GET、POST)、请求头(例如 User-Agent、Accept)、请求体(对于 POST 请求)等信息。
- 服务器处理请求并响应:根据请求的内容和路由,执行相应的处理,例如查询数据库、处理业务逻辑等,并生成一个响应消息。这个响应包括了状态码(如 200 表示成功),可能的响应头信息,以及请求的资源内容。
- 浏览器渲染页面:构建 DOM 树
CDN
代理
Client → Proxy → Server
为什么要代理?直接请求源服务器不行吗?
- 隐私和安全性
- 性能和负载均衡
相当于中间商,本来 A 和 B 可以直接连接,但是此时添加了一个 C 在中间。
A 跟 B 不直接连接,而是通过 C 作为中介进行连接。
最常见的例子就是二手房东,租房子时签约的人不是房子的真正房东,而是房东委托的中介,房东不想管事,所以才会通过中介(代理)进行处理。
正向代理
Forward Proxy,为了隐藏客户端身份
顺着请求方向进行的代理,即代理服务器它是由你配置,去请求目标服务器地址。
- 访问原来无法访问的资源,如 Google → VPN
- 可以做缓存,加速访问资源
反向代理
Reverse Proxy,为了隐藏服务端身份
比如访问百度,其代理服务器对外的域名为
https://www.baidu.com
。具体内部的服务器节点我们不知道。现实中通过访问百度的代理服务器后,代理服务器将请求转发到它们 多的服务器节点中,然后进行搜索后将结果返回。- 负载均衡 → Nginx
CDN 实际上是一种特殊的反向代理,它通过将网站的静态资源缓存在全球多个服务器节点上,加速资源加载,相关流程如下:
- DNS 查询:解析会返回 CDN 的节点 IP 地址,而不是源服务器的 IP 地址。
- 请求路由:如果请求的资源已经在 CDN 节点的缓存中,会直接返回资源。
- 缓存资源 / 缓存更新:根据设定的缓存策略(例如过期时间)来管理。
用了CDN 最后包的总体积不也是一样大吗?为什么这样可以优化?
- 减少网络延迟:用户可以从离自己「地理位置最近」的服务器获取资源
- 提高稳定性:如果一个服务器出现故障,会将请求重定向到其他可用的服务器
- 节省带宽成本:使用「缓存机制」,减少对源服务器的请求次数
Cookie
客户端存储服务端数据的一种机制,它是客户端发送给服务器 HTTP 请求上的附加信息。
常用属性
Domain:指定 Cookie 可以发送到的域名。
- 例如,
Domain=example.com
允许 Cookie 被发送到example.com
及其子域sub.example.com
。
- 如果不指定 Domain,浏览器默认使用创建 Cookie 时所在的域,即只能在同源 (Same-Origin) 的请求中发送。
Path:指定 Cookie 可以发送到的 URL 路径。
- 例如,
Path=/test
只请求example.com/test
或其子路径example.com/test/sub
。
- 默认值为
/
,即 在整个域名下都有效。
- Expires
- Max-Age
- Secure:仅在 HTTPS 连接中发送。
- HttpOnly:Cookie 不能被 JavaScript 访问。
SameSite :用于控制跨站点请求时是否发送 Cookie。
Strict
:浏览器仅在目标站点的 URL 与请求页面的 URL 完全一致(即同站点)时发送 Cookie,否则不发送。这有助于防止 CSRF。
Lax
- 当通过链接从外部站点访问目标站点时,发送
- 当从当前浏览器选项卡中导航到目标站点时,发送
- 当通过 POST 请求进行的(例如表单提交),不发送
None
各种存储手段的大小限制?
- Cookie:4KB
- LocalStorage / SessionStorage:5MB
- IndexedDB:大小限制不统一,部分现代浏览器为每个域提供约 50MB 至无上限的存储空间,这取决于用户的硬盘空间和浏览器的具体实现
携带 Cookie 前提?
默认情况只有当请求与设置 Cookie 的网站处于同一个源(协议、域名、端口全部相同)时,浏览器才会自动携带 Cookies
Cookie 在哪种场景下比 localStorage 更好?
- Cookies 通常用于存储身份验证令牌和进行会话管理,即保持用户登录状态
- localStorage 数据没有到期时间,适合长期存储,适合存储大量不需要频繁发送到服务器的数据
Cookie 怎么防止被篡改?
使用 HTTPS 协议 / 设置 HttpOnly 标志 / 设置 Secure 标志
使用加密算法 / 使用签名 / 限制 Cookie 的域和路径 / 定期更新 Cookie
Session
广义上来说,Session 是服务端存储用户状态的一种机制,不管是用什么存储(各种内存、缓存、数据库),都属于会话。
狭义上来说,传统的 Session 实现基本是 Cookie-based Session。
Token
本质就是一段字符串,作为验证用户身份的“令牌”。至于怎么生成,具体算法由你选择。
- 从这个角度, SessionId 也是 Token。只不过前后端分离 / APP 兴起,换了种说法。
- 不要混淆了 “Session” 与 “Session 的实现” 。
JWT
Json Web Token ,通过把用户信息,用密钥加密防止篡改,然后放在请求头里。用户请求的时候再解密,于是服务器就不需要保存用户的信息了。JWT 是无状态的,同时有明显缺点:
- 无法撤销:一旦被签发,在到期时间之前都是有效的。
- → 黑名单机制
- 及时刷新:总不能用户操作一半,突然过期了。
- → acessToken + refreshToken
虽然有不同手段能暂时解决问题,但是此时又发现,项目架构又回到了传统 Session 模型中,对 JWT 来讲属于自断招牌。那么属于 JWT 的更合适使用场景是什么?
- 不应该用于维护用户登陆状态(Authentication)
- 如果作为访问凭证,那么它面临的问题和普遍意义上的 Session 完全一样,反而增加了复杂度。
- 可以用于短时间内的授权(Authorization)
- 临时文件分享链接
- 第三方应用接入