在客户端存储会话状态

作者:Greg MurrayInderjeet Singh

问题描述

Web 应用程序通常需要维护涉及多个请求的会话状态。通常,J2EE[TM] 应用程序中的会话状态可以存储在以下任一层中:

每一层在存储会话状态方面都有其优缺点。应用程序可以在多个层中存储会话状态,并可以选择在不同的层中对会话状态进行分区。因为使用 HttpSession 将状态存储在 Web 层上是众所周知的主题,并且已经在多处论述了其他选择(如使用有态会话 Bean),所以此解决方案着重说明在客户端层上存储会话状态。当应用程序需要将会话状态存储在客户端上而不是(或不仅仅是)使用 HttpSession 保存会话状态时,此解决方案是很有用的。此解决方案的一个主要要求是安全存储状态,此解决方案可以实现这一点。

解决方案

可以使用若干种方法将 Web 应用程序的会话状态存储在客户端层上。一种可行的解决方案是使用 Cookie 在客户端上存储状态。但是,Cookie 在大小上有限制,并且在您希望将会话状态存储在客户端的情况下它不是一种理想的方法。

可以通过使用 HTML 的隐藏表单字段、进行序列化和反序列化的一些代码、编码器和一些其他方法,设计更强健的机制。需要同时对 URL 参数和 Java 对象进行编码,以便可以重新生成与原来完全相同的视图。此解决方案可以提供可重用方法,将 Web 应用程序的会话状态存储在客户端上。让我们简单地论述一下解决方案和示例。

HTML 提供了将状态作为隐藏字段放置在表单中的方法。稍后可以通过后续的 POST 获取此状态。代码示例 1 显示了如何使用隐藏字段在 HTML 表单中存储状态:

<form action="controller.do" method="POST">
<input type="HIDDEN" name="var1" value="value 1">
<input type="SUBMIT" value="Push Me">
</form>

代码示例 1:使用隐藏表单字段存储会话状态

在上面的代码示例中,单击 Push Me 按钮会将表单和数据作为 HTTP POST 提交到已映射到 controller.do 的 Web 组件。请注意,此解决方案不能使用 HTTP GET 方法(而不是 POST),因为 GET 会将所有表单字段编码为 URL 参数,这将创建一个很长的 URL,其长度有可能超过最常用的浏览器和 Web 容器支持的长度。此外,还需要牢记,应该在被视为幂等的请求中使用 HTTP GET 方法,以便可以在本地和通过中间对象缓存这些请求。您可以创建显示为 <a href="someurl"> 标记的链接,但是必须使用与其关联的 JavaScript[TM] 事件传送包含客户端状态的表单。提交代码示例 1 中的表单的链接将显示如下:

 <a href="#" onclick="document.forms[0].submit(); return false;">

Java[TM] 语言提供了一种将对象序列化的机制,以便能够重新创建这些对象,使其状态保留在先前的某个时间状态上。通过与编码方案相结合,序列化的内容将放在发送到客户端的 HTML 页中。在上面的示例中,编码的序列化数据被作为属性 var1 的值放置。如果在页面中存储的数据量很大,则还可以压缩序列化的内容。

呈现表单的 Web 组件(在上面的示例中为映射到 controller.do 的 Servlet)可以使用 HttpServletRequest API 访问隐藏的表单变量。Web 组件可以将状态反序列化,并基于该状态执行某项操作。

可以使用客户端状态减少在 HttpSession 中存储的数据量。它有助于将状态分布在许多 Web 页上,所有这些页都保存在客户端上。这有助于减少对服务器的内存要求,从而提高可伸缩性。但是,并不是所有的状态都适于移到客户端上。在使用客户端状态时,请注意以下事项:

适于在客户端上存储的一些状态包括特定于页面的状态,如对象(JavaServer[TM] Faces 使用具有隐藏表单元素技术的客户端状态作为存储视图状态的选项)。通常保存在 HttpSession 中的特定于用户的对象,诸如购物车、搜索结果、梦想清单之类的对象以及用户信息都适于存储在客户端上。诸如 Web 应用程序配置状态之类的状态、包含各种图像的较大对象或用户之间共享的对象不应该保存在客户端上。

在客户端上存储状态,可能对您来说不是最佳的解决方案。在客户端上存储状态会涉及性能损失和带宽占用等问题。要根据实际的具体情况来考虑使用本文档中所述的策略存储状态。

确保客户端状态的安全

在客户端上存储的任何状态都容易受到各种攻击,例如:

通过在存储客户端状态的所有 Web 页中使用 HTTPS,可以减少传输中的攻击。消除这两种类型攻击的更强有力的方法是,确保除服务器外任何人都无法读取和修改客户端上存储的数据。通过对数据进行加密并为其附加消息验证代码 (MAC),可以实现这一点。通过使用 base64 编码将数据转换为文本。然后,将该文本存储在发送到客户端的 HTML 的隐藏表单字段中。如果需要压缩数据,则应该在执行加密之前进行压缩。这样做是因为加密过程会将数据值随机化,导致不能进一步压缩输出。有关如何执行此操作的设计详细信息,请参见本文档中的确保客户端状态的安全部分。

通过 Servlet 创建定制标记的策略

虽然可以直接将存储会话状态的代码嵌入 JSP 页和 Web 层类中,但是最好将此代码放在定制标记中以便进行重用。如果对 URL 参数和 Java 对象进行编码,并生成使用必需隐藏字段的 HTML 表单,则会在发出后续请求时重新构建这些表单,这种方式会有些繁锁。而可重用的定制标记库可以隐藏对 URL 参数和对话状态进行序列化和编码所需的复杂细节。

有关实现此策略的更多详细信息,请参见客户端状态设计详细信息文档。

参考资料

有关本主题的详细信息,请参阅以下资料:

© Sun Microsystems 2005。Java BluePrints Solutions Catalog 中的所有内容受版权保护,未经 Sun Microsystems 的明确书面许可,不得在其他产品中发布。