1. 介绍
现代 Web 应用程序几乎都会以某种形式使用会话(Session)。会话通常用于在客户端和服务器之间保持状态,从而提升用户体验,比如用户登录状态、个性化推荐、购物车等功能。
与此同时,很多系统都基于 RESTful 原则构建。在本文中,我们将探讨会话的本质,以及它是否与 RESTful 的无状态原则相冲突。
2. 什么是会话?
“会话”这个词在编程领域有多种含义。在底层,它可以指控制两个设备之间连接的 TCP 连接。
在应用层面,会话是跟踪客户端多次交互状态的一种方式。虽然现在很多应用使用 HTTP 协议,但 HTTP 本身是无状态的。因此,应用程序必须自己实现某种形式的会话管理。
会话跟踪通常包括在客户端首次发起请求时为其分配一个唯一标识符(session ID),之后客户端在每次请求中都携带这个 ID。服务器使用这个 ID 来查找和保存该客户端的状态信息。
2.1. 会话的典型用途
在现代 Web 应用中,很难完全避免使用会话,因为它为提升用户体验提供了很多支持:
- 记住用户的登录状态
- 跨设备保存购物车内容
- 在不同搜索中保留用户的筛选条件
这些功能都要求服务器保存客户端之前交互的某些状态信息。由于客户端可能更换设备或浏览器,因此唯一稳定的状态存储位置是服务器端。
2.2. Cookie 与会话的关系
需要注意的是,会话不等于 Cookie。Cookie 只是客户端与服务器之间交换会话信息的一种机制。除了 Cookie,我们还可以使用自定义 HTTP 头、查询参数等方式来实现类似功能。
3. REST 与状态
REST(Representational State Transfer)是一种架构风格,由 Roy Fielding 在其博士论文中提出,用于指导构建大规模、可扩展的客户端-服务器系统。
需要注意的是,REST 中的“状态”这个词有两层含义:
- 客户端与服务器之间交互的状态(session state)
- 资源的状态(resource state)
当我们讨论会话时,通常指的是前者,即客户端与服务器之间的交互状态。
而资源的状态始终由服务器维护。客户端可以请求资源的不同表示,也可以提交对资源的修改,但最终资源状态的维护者是服务器。
3.1. 无状态约束(Stateless Constraint)
RESTful 系统的一个重要约束是:每个请求必须包含服务器完成该请求所需的所有信息。换句话说,服务器不能依赖任何先前请求的上下文来处理当前请求。
Fielding 对 REST 中的会话状态是这样描述的:
每个客户端到服务器的请求必须包含理解该请求所需的所有信息,不能依赖服务器中存储的任何上下文。因此,会话状态必须完全由客户端维护。
可以看到,Fielding 在这里明确区分了客户端状态和服务器状态。服务器可以且应该管理资源的状态,比如用户地址、订单历史等。这些都是资源相关的状态,服务器负责维护。
而客户端可以管理自己的状态,比如用户名、密码、页面浏览历史等。实现无状态的关键在于:每个请求都必须包含完成请求所需的所有数据。
3.2. 无状态的重要性
Fielding 在定义 REST 时加入无状态约束是有其深意的,其中最主要的原因是:无状态系统更容易扩展。
设想一个部署在多个服务器上的应用,我们通过负载均衡器将用户请求分发到不同实例上。如果每个实例都是无状态的,那么任何客户端都可以连接到任意实例。
这使得负载均衡器的工作变得简单,它可以自由选择可用实例处理请求。我们也可以随时添加新实例,无需担心状态迁移。
但如果每个实例都在本地维护客户端的会话状态,负载均衡器就不能随意转发请求了。它必须确保同一个客户端始终连接到同一个实例。这会带来两个问题:
- 负载均衡器需要理解应用逻辑,增加了复杂度
- 或者我们需要引入共享状态机制(如分布式缓存)来同步会话数据,这又增加了耦合和潜在故障点
除了扩展性,无状态还带来了其他好处:
✅ 更好的可观测性:每个请求都可以独立监控和分析
✅ 更高的容错性:当某个实例宕机时,只需重启或新增实例即可,无需担心状态丢失
4. 会话是否违反了 REST 原则?
现在我们来回答标题的问题:会话是否违反了 REST 原则?
回顾一下两个关键点:
- 会话状态是存储在服务器上的
- REST 原则要求状态只能由客户端维护
从这个角度看,会话确实违反了 REST 的无状态约束。
但这并不一定是坏事。软件开发中很多决策都是权衡的结果。通过牺牲无状态性,我们可以实现更丰富的用户体验:
- 在手机上加购商品,用电脑完成支付
- 登录一次,即可获得个性化页面展示
这些功能的实现,都依赖于服务端维护的会话状态。
5. 总结
我们已经了解了会话的本质,并明确了它确实违反了 REST 的无状态原则。
虽然这使得我们的系统从严格意义上讲不再是“RESTful”,但这并不意味着这是一个错误的决定。每个应用都有不同的需求,REST 的约束也不是放之四海而皆准的铁律。
通过适度放弃 REST 的某些约束,我们可以构建出更贴近用户需求、更灵活的应用系统。关键在于理解这些权衡,并在设计中做出合理的选择。