表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)。
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) 尽可能返回一个存在的会话否则返回空。
我们还是只解决了手边一半的问题。我们希望能够跳过会话“新建”状态并自动的转换到会话“使用中”状态。我们能够通过重定向浏览器到处理服务端小程序自动的实现这些。表3把服务端小程序会话逻辑和重定向用户端与有效会话到处理服务端小程序的能力结合在一起。
表3:RedirectServlet.Java
01: package multiplesubmits;
02:
03: import java.io.*;
04: import java.util.Date;
05: import javax.servlet.*;
06: import javax.servlet.http.*;
07:
08: public class RedirectServlet extends HttpServlet{
09: public void doGet (HttpServletRequest req, HttpServletResponse res)
10: throws ServletException, IOException {
11: HttpSession session = req.getSession(false);
12: System.out.println("");
13: System.out.println("-------------------------------------");
14: System.out.println("SessionServlet::doGet");
15: System.out.println("Session requested ID in Request:" +
16: req.getRequestedSessionId());
17: if ( null == req.getRequestedSessionId() ) {
18: System.out.println("No session ID, first call,
creating new session and forwarding");
19: session = req.getSession(true);
20: System.out.println("Generated session ID in Request: " +
21: session.getId());
22: String encodedURL = res.encodeURL("/RedirectServlet");
23: System.out.println("res.encodeURL(\"/RedirectServlet\");="
+encodedURL);
24: res.sendRedirect(encodedURL);
25: //
26: // RequestDispatcher rd = getServletContext().getRequestDispatcher(encodedURL);
27: // rd.forward(req,res);
28: //
29: return;
30: }
31: else {
32: System.out.println("Session id = " +
req.getRequestedSessionId() );
33: System.out.println("No redirect required");
34: }
35:
36: HandleRequest(req,res);
37: System.out.println("SessionServlet::doGet returning");
38: System.out.println("------------------------------------");
39: return;
40: }
41:
42: void HandleRequest(HttpServletRequest req, HttpServletResponse res)
43: throws IOException {
44: System.out.println("SessionServlet::HandleRequest called");
45: res.setContentType("text/Html");
46: PrintWriter out = res.getWriter();
47: Date date = new Date();
48: out.println("<html>");
49: out.println("<head><title>Ticket Confirmation</title></head>");
50: out.println("<body>");
51: out.println("<h1>The Current Date And Time Is:</h1><br>");
52: out.println("<h3>" + date.toString() + "</h3>");
53: out.println("</body>");
54: out.println("</html>");
55: System.out.println("SessionServlet::HandleRequest returning");
56: return;
57: }
58: }
这如何解决我们的问题的呢?测试上面这段代码显示出在11行我们尝试获得一个会话的句柄。
在17行我们通过检测为空的会话ID或检测有效会话ID来确定存在一个有效的会话。如果不存在会话就执行18-29行程序。通过下述方法我们处理多重提交的问题,在19行首先建立一个会话,在22行使用URL编码添加新会话ID,并且在24行重定向我们的服务端小程序到新的URL编码。
不熟悉重写URL的读者可参考15行到23行。一个HttpServlet对象可以重写URL。这个过程将一个会话ID插入到URL。底层的应用服务器能够自动的用编码URL提供给服务端小程序或jsp一个存在的会话。由于这依赖于应用服务器,为了使上面的例子可以运行,你可能需要设置环境使URL能够重写!
总结
在这篇文章中,我们讨论了多重提交问题的许多解决方案。每一个方案都有优点和缺陷。在处理问题时,要清晰的理解和权衡解决方案多方面的优点和缺陷。我们最后的例子有利于解决客户端额外重复的访问和浏览的问题。JavaScript脚本的方法是最好的,但是需要客户端支持才能够运行。和其他任何问题一样,会有一大堆解决方案,每个方案都会有其自己的优缺点。掌握每个方案的优缺点,有利于我们为解决问题作出最好的选择。
matrix开源技术经onjava授权翻译并发布.
如果你对此文章有任何看法或建议,请到Matrix论坛发表您的意见.
注明: 如果对matrix的翻译文章系列感兴趣,请点击oreilly和javaworld文章翻译计划查看详细情况
您也可以点击-fpwang查看翻译作者的详细信息 (出处:http://www.knowsky.com)