REST 的 “客户机-无状态-服务器” 约束禁止在服务器上保存会话状态。符合这一约束进行设计可以提高系统的可见性、可靠性和可伸缩性。但是融入式服务器端 Web 应用程序希望能够为单个用户提供大量个性化内容,因此必须在两种设计之间作出选择。第一种设计要在每个客户机请求中都发送大量状态信息,因此每个请求都完整地保留了上下文的内容,服务器是无状态的。第二种解决方案表面上来看比较简单,应用程序开发人员和中间件供应商都比较倾向于这种方法,它只是简单地发送一个用户标识,并在服务器端为这个标识关联一个 “用户会话”(如图 3 所示)。第二种设计直接违背了客户机-无状态-服务器约束。尽管它确实可以实现我们想要的用户功能(具体来说就是指个性化),但却对这个架构进行了极大的改动。
图 3. 融入式服务器端 Web 应用程序,其中包含了大量服务器端会话状态
Java Servlet 的 HttpSession API 正是一个此类变动的例子。HttpSession 让我们可以在状态和特定用户之间建立关联。这个 API 看起来对于开发新手非常简单。实际上,它似乎可以将任何对象保存到 HttpSession 中,并且不需要自己实现任何特定的查找逻辑就可以将这些对象取出来。但是当我们开始在 HttpSession 中放入更多对象时,就会开始注意到我们的应用服务器要占用的内存和处理资源越来越多。很快我们就确定自己需要将应用程序部署到集群环境中来应对日益增加的资源需求。然后就会认识到,要让 HttpSession 在集群环境中工作,每个对象都必须实现 Java 的 Serializable 接口,以使会话数据能够在集群环境中的服务器间传递。然后必须确定应用服务器在关机/重启过程中是否要继续维护会话数据。很快您就会质疑,违背客户机-无状态-服务器约束是否真的是一个好主意。(实际上,很多开发人员都不了解这个约束。)
使分布式缓存变为不可能
融入式服务器端 Web 应用程序的第二个严重后果在于:它实际上不能利用 REST 的第一类支持进行资源缓存。引用 Fielding 的话来说,“添加缓存约束的优点是可以部分或完全避免某些交互操作,从而可以通过减少一系列交互的平均延迟来提高效率、可伸缩性以及用户可以感受到的性能。不过这样做的代价是如果缓存中的陈旧数据与通过将请求直接发送给服务器而获得的数据有很大区别,那么缓存的可靠性就降低了。”
我们可以将融入式 Web 应用程序近似地看作一个活动实体,它会根据用户提供的新输入内容、其他人输入的新内容以及新的后台数据而不断发生变化。由于服务器必须根据多个用户与应用程序的交互来生成每个页面,因此我们实际上无法两次生成相同的文档。因此,Web 浏览器或代理服务器无法缓存服务器资源。
有几种解决方案可以用来处理资源无法缓存的问题。一种就是创建细粒度的资源在服务器端的缓存,这样服务器就可以通过预先组合好的部分来构建一个粗粒度的页面,而不是通过基本元素(HTML 和数据)从头开始一步步地构建这种页面了。但是问题依然存在:每个请求都会导致大量的服务器处理,这会损害系统的可伸缩性,还可能会对用户感受到的响应性造成负面影响。
无法提供可缓存资源的另外一个结果是:动态程度相当高的 Web 应用程序必须显式地禁止搜索引擎和其他类型的 “机器人”作出请求,因为处理这类请求的成本都非常昂贵;而在符合 REST 准则的应用程序中,只需一次性地将某个资源提供给那一类 “机器人”,然后对它们的后续访问发送一条简单的 “Not-modified” 消息即可。
但是为什么要在一个负载过重的服务器上集中这么多的资源消耗呢?从理论上来说,我们什么时候可以将处理和内存需求分布到客户机呢?简单的答案是给定传统 Web 浏览器约束,这是不可行的(请参看 不使用 Ajax 的客户端处理)。但是 Ajax 架构风格使开发人员可以将处理和状态需求分布到客户机。请继续阅读,学习为什么选择使用 Ajax 风格的融入式应用程序可以继续遵循 REST 准则,并充分利用它的优势。
Ajax 和 REST正如我们前面看到的一样,传统的服务器端 Web 应用程序将数据的标识和服务器上的动态数据元素合并在了一起,并将所构成的完整 HTML 文档返回给浏览器。Ajax 应用程序在其主要 UI 和浏览器中的主要逻辑方面有所不同;基于浏览器的应用程序代码可以在必要时获取新的服务器数据,并将这些数据织入当前页面(请参看 参考资料 中 Jesse James Garrett 有关 Ajax 的启蒙文章)。呈现和数据绑定的位置看起来可能是一个实现细节,但是这种区别会导致完全不同的架构风格。
利用有状态 Web 客户机的优点
人们通常将 Ajax 应用程序描述成无需在每次点击时彻底地刷新整页的 Web 页面。尽管这个描述非常确切,但是根本的动机在于彻底刷新整页会令用户不耐烦,从而无法获得愉快、融入式的用户体验。从架构的角度来看,整个页面全部刷新的设计甚至非常危险,这种设计使您无法选择在客户机存储应用程序状态,这可能会导致妨碍应用程序充分利用 Web 最强大的架构设计点的设计决策。
Ajax 让我们不需要进行完全刷新就可以与服务器进行交互,这一事实使有状态客户机再次成为可用选择。这一点对于动态融入式 Web 应用程序架构的可能性有深远的影响:由于应用程序资源和数据资源的绑定转换到了客户端,因此这些应用程序都可以享受这两个世界中最好的东西 —— 融入式 Web 应用程序中动态、个性化的用户体验,以及遵守 REST 准则的应用程序中简单、可伸缩的架构。
缓存 Ajax 引擎
设想一下,将 Amazon.com 彻底重新实现为一个 Ajax 应用程序 —— 一个 Web 页面可以从服务器上动态获取所有的数据。(出于商业原因,Amazon 可能并不希望这样做,不过那是其他文章讨论的话题了。)由于现在有很多 UI 和应用程序逻辑都可以在客户机而不是在服务器上运行,根据 Garrett 的说法,最初加载页面时需要下载 Amazon 的 Ajax “引擎”。这个引擎包含大量应用程序逻辑(以 JavaScript 代码实现),另外还有此后将使用从服务器上异步获取的数据填充的 UI 框架(见图 4):
图 4. 融入式 Ajax 应用程序