最主要和最有效解决多重提交问题的方法就是防止这种情况发生。下面列出的HTML程序ConcertTickets.html是一个简单的表单,用于获取由用户输入的音乐会名字并提交给服务端小程序(servlet)订票。当网站相应迅速时处理执行的很好。但是,如果网站处于性能很低的状态并且提交的任务处理不够快,用户认为失败后会重新提交。处理过程在表1下的图2中显示。
表1:ConcertTickets.html
01: <html>
02: <head><title>Online Concert Tickets</title></head>
03:
04: <center><h1>Order Tickets</h1></center>
05:
06: <form name="Order" action="./SimpleOrder" method="GET">
07: <table border="2" width="50%" align="center" bgcolor="CCCCCC">
08: <tr><td align="right" width="40%">Concert: </td>
09: <td width="60%"><input type="text" name="Concert" value=""></td></tr>
10:
11: <tr><td colspan="2" align="center">
12: <input type="submit" name="btnSubmit"
13: value="Do Submit"></td></tr>
14: </table>
15: </form>
16: </body>
17: </html>
图2:重复的任务提交
防止多重提交
最简单的解决多重提交问题的方法是防止这种情况发生。下面是我们表1中表单程序的一个修改,加入了少量的javascript脚本。内嵌的javascript脚本记录以前提交按钮被点击的次数。在用户再次提交时,将会弹出报警窗口并且表单不会再被提交。我们能够通过给提交按钮加入onClick事件属性来缩短普通提交处理过程的周期。每次提交按钮被点击时,onClick的代码就会执行。在我们这个例子中,会引起javascript脚本函数checksubmitcount()被调用。但仅仅调用一个函数并不会真正起到作用。如果我们只是加入onClick,每次提交按钮被点击时我们会收到弹出的警报,而任务提交也会立即发生。用户会被警告她操作错误,但是请求还是会被提交。这样做仅仅对用户端有了一定的改善。而在服务端结果是一样的:多重提交。
表2:Concert2.html
01: <html>
. . .<!?与表1 :ConcertTickets.html程序02~11相同 //-->
12: <input type="button" name="btnSubmit"
13: value="Do Submit"
14: onClick="checksubmitcount();"></td></tr>
15: </table>
16: </form>
17:
18: <script language="javascript">
19: <!--
20: var submitcount = 0;
21: function checksubmitcount()
22: {
23: submitcount++;
24: if (1 == submitcount )
25: {
26: document.Order.submit();
27: }
28: else
29: {
30: if ( 2 == submitcount)
31: alert("You have already submitted this form");
32: else
33: alert("You have submitted this form"
34: + submitcount.toString()
35: + " times already");
36: }
37: }
38: //-->
39: </script>
40: </body>
41: </html>
我们能通过更进一步和更精细的改变我们网页的工作方式来解决问题。敏锐的读者可能会注意到对表单添加的改变。程序第12行定义的按钮类型原先为submit,现在改成了button。而网页界面是完全相同的。可是,与表单相关的默认动作(程序第6行,调用服务端小程序)不再是自动执行的了。我们现在能够通过程序控制表单向服务器端的提交,我们的问题也得到了解决,不是么?
表2当然是一种改善,但我们还是需要一些其他方法。仍然有许多情况会导 致错误。如果用户按下了浏览器的后退键或者刷新了整个网页会怎么样?如果用户的浏览器关闭了javascript的功能或者浏览器不能处理会如何呢?我们还是可以处理这个问题的,但是为了取代防止多重提交,我们需要在后端通过表单处理的服务端小程序处理它们。
为了理解如何解决多重提交问题,我们必须首先理解服务端小程序会话(sessions)机制。每个人都知道,HTTP协议的固有性质中并不对状态(客户端请求信息的历史记录)进行记录。为了处理状态,我们需要一些方法使浏览器能够将当前请求与其他大量请求联系起来。会话(session)程序提供给我们一个解决这个问题的方案。HttpServlet中的方法doGet()和doPost()使用了两个指定的参数:HttpServletRequest和HttpServletResponse。服务端小程序请求参数使我们能够访问会话(session)。会话为访问和存储状态提供了机制。
什么才是会话(session)呢?会话(session)包括很多内容:
状态集??由web服务器管理并且由特定用户所有请求所共享的详细表示描述。
存储空间??通过HttpSession接口至少将HttpServlets所需状态数据和定义存储起来。
在我们具体了解如何使用服务器端方案解决我们的问题之前,我们还需要了解服务端小程序会话(session)的生命周期。与EJB及其他服务端实体一样,会话在生存期中通过一个定义的状态集运行。下图显示了会话的生命周期。Servlet可在三种特定的状态中转换:不存在(does not exist),新建(new),非新建(not new/或使用中in-use)。
图3:服务端小程序会话(session)生命周期
[ 相关贴图 ]
a) 会话在开始时处于不存在状态。会话从这一状态开始或者由于许多原因而返回到此状态。最主要的原因就是用户以前没有访问过这些状态或者是由于用户脱离(超时)站点或退出使会话被设置为无效。
b) 当会话被建立时便会从不存在状态进入新建状态。新建与非新建状态的区分是非常重要的,因为HTTP协议不记录状态信息。根据servlet详细说明书描述,在客户端返回会话给服务端之前会话不能够进入非新建状态(即从预期会话转变为当前会话)。这样在客户端不知道或者还没有决定加入会话时会话处于新建状态。
c) 当会话通过cookie或是重写URL()返回到服务器时,会话就变为使用中或非新建状态。
d) 通过各种get与set方法继续使用会话会使其维持在使用中状态。
e) 当会话由于长时间没有被使用而超时或显式的被设为无效则会发生图中所示的5以及6所标识的转移。不同应用服务器用不同方式处理超时。BEA公司的WebLogic使应用部署者能够通过与web应用一起打包的特殊部署描述脚本(weblogic.xml)设置会话超时的时限。
现在我们了解了会话的生命周期,那么如何获得一个会话并有效的使用它呢?接口HttpServletRequest提供了两个关于会话的方法:
public HttpSession getSession()返回一个新的会话或一个已存在的会话。
如果提供一个有效的会话ID(可能是通过cookie)则返回一个存在的会话。返回新的会话可能会有许多原因:用户最初的会话(无法提供有效会话ID);会话超过有效时间(提供了会话ID);一个无效的会话(提供了会话ID);或者是明确指出会话无效(提供了会话ID)。
public HttpSession getSession(boolean)可能返回新会话、存在的会话或者空。getSession(true)尽可能返回一个存在的会话。否则创建一个新会话。getSession(false) 尽可能返回一个存在的会话否则返回空。
(本文来源于图老师网站,更多请访问http://m.tulaoshi.com/bianchengyuyan/)我们还是只解决了手边一半的问题。我们希望能够跳过会话新建状态并自动的转换到会话使用中状态。我们能够通过重定向浏览器到处理服务端小程序自动的实现这些。表3把服务端小程序会话逻辑和重定向用户端与有效会话到处理服务端小程序的能力结合在一起。