表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) 盡可能返回一個存在的會話否則返回空。
我們還是只解決了手邊一半的問題。我們希望能夠跳過會話“新建”狀態并自動的轉換到會話“使用中”狀態。我們能夠通過重定向瀏覽器到處理服務端小程序自動的實現這些。表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查看翻譯作者的詳細信息
為了理解如何解決多重提交問題,我們必須首先理解服務端小程序會話(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) 盡可能返回一個存在的會話否則返回空。
我們還是只解決了手邊一半的問題。我們希望能夠跳過會話“新建”狀態并自動的轉換到會話“使用中”狀態。我們能夠通過重定向瀏覽器到處理服務端小程序自動的實現這些。表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查看翻譯作者的詳細信息