曹 琨,孔祥盛
(新鄉(xiāng)學(xué)院計算機(jī)與信息工程學(xué)院,河南新鄉(xiāng)453003)
瀏覽器用戶在使用基于Struts的Web應(yīng)用系統(tǒng)時,由于網(wǎng)絡(luò)延遲使瀏覽器用戶認(rèn)為Web應(yīng)用系統(tǒng)響應(yīng)失敗,導(dǎo)致用戶重復(fù)提交表單數(shù)據(jù)。由于表單數(shù)據(jù)的重復(fù)提交,導(dǎo)致網(wǎng)絡(luò)和服務(wù)器資源的浪費(fèi),并有可能導(dǎo)致數(shù)據(jù)庫數(shù)據(jù)冗余,嚴(yán)重時可導(dǎo)致服務(wù)器死機(jī)現(xiàn)象,如何避免表單數(shù)據(jù)重復(fù)提交顯得格外重要[1]。
基于Struts的Web應(yīng)用系統(tǒng)避免表單重復(fù)提交有以下四種解決方案:
①使用Javascript解決表單重復(fù)提交問題;②使用Cookie解決表單重復(fù)提交問題;③使用Session解決表單重復(fù)提交問題;④使用Struts同步令牌Token解決表單重復(fù)提交問題。
第①種方案為瀏覽器端的解決方案,該方案可以防止用戶重復(fù)點(diǎn)擊提交按鈕產(chǎn)生的表單重復(fù)提交問題,該方案受瀏覽器端制約;第②、③、④種方案為服務(wù)器端的解決方案,這三種方案可以防止用戶刷新頁面產(chǎn)生的表單重復(fù)提交問題[2]。
通過使用提交按鈕的onClicks事件檢測用戶的提交狀態(tài)可以解決表單重復(fù)提交問題[3]。實現(xiàn)方法是:當(dāng)用戶點(diǎn)擊提交按鈕后,使用按鈕的onClick事件控制按鈕,將按鈕變?yōu)榛疑顟B(tài)(圖1),用戶不能再次點(diǎn)擊提交按鈕,從而解決用戶重復(fù)點(diǎn)擊提交按鈕時造成的表單重復(fù)提交問題,實現(xiàn)代碼如下:
<form method="post"name="thisForm"action="test.do" >
<input name="text"type="text"/>
<input name="submitButton"value="提交"type="submit"
onClick="document.thisForm.submitButton.
value= '正在提交,請等待...';
document.thisForm.submitButton.disabled=true;document.thisForm.submit();"/>
</form>
圖1 使用onClick事件解決表單重復(fù)提交問題
另外還可以使用表單的onSubmit事件檢測用戶的提交狀態(tài)解決表單重復(fù)提交問題[3]。實現(xiàn)方法是:使用Javascript記錄表單的提交次數(shù)(默認(rèn)為1),當(dāng)表單提交次數(shù)大于1時,將彈出圖2所示的對話框,提示用戶已經(jīng)提交表單,從而解決用戶重復(fù)點(diǎn)擊提交按鈕造成的表單重復(fù)提交問題,實現(xiàn)代碼如下:
<script language="javascript">
<!--
var submitcount=1;
function submitOnce(form){
if(submitcount==1){
submitcount++;
return true;
}else{
alert("正在操作,請不要重復(fù)提交表單數(shù)據(jù)!");
return false;
}
}
//-->
</script>
<form method="post"name="thisForm"action="test.do"onSubmit="return submitOnce(this)" >
<input name="text"type="text"/>
<input name="submitButton"value="提交"type="submit"/>
</form>
圖2 使用onSubmit事件解決表單重復(fù)提交問題
由于Javascript腳本程序運(yùn)行在瀏覽器端,使用該方法可以防止用戶重復(fù)點(diǎn)擊提交按鈕導(dǎo)致的表單重復(fù)提交問題,但是由于用戶可以屏蔽瀏覽器Javascript腳本程序的運(yùn)行,因此該方法僅適用于瀏覽器開啟了Javascript功能。當(dāng)瀏覽器屏蔽了Javascript腳本程序運(yùn)行時,可以選擇下面三種服務(wù)器端的解決方案。
在Struts的Action中使用Cookie記錄表單提交的狀態(tài)可以解決表單重復(fù)提交問題[3],實現(xiàn)方法是:當(dāng)用戶第一次點(diǎn)擊提交按鈕后觸發(fā)Struts中的Action代碼運(yùn)行,該Action為瀏覽器用戶設(shè)置一個Cookie,然后運(yùn)行業(yè)務(wù)邏輯代碼,最后取消該Cookie。在運(yùn)行業(yè)務(wù)邏輯代碼的過程中,當(dāng)用戶刷新頁面時,判斷Cookie是否已經(jīng)存在,若存在,Action將頁面重定向到失敗頁面,從而解決用戶刷新頁面時造成的表單重復(fù)提交問題,實現(xiàn)代碼如下:
Cookie cookies[]=request.getCookies();
for(int i=0;i< cookies.length;i++){
Cookie cookie=cookies[i];
if("submitOnce".equals(cookie.getName())){
return mapping.findForward("failure");
}
}
Cookie cookie=new Cookie("submitOnce","submitOnce");
cookie.setMaxAge(60);
response.addCookie(cookie);
//業(yè)務(wù)邏輯代碼
cookie.setMaxAge(0);
由于Cookie信息保存在瀏覽器端,用戶可以阻止瀏覽器使用Cookie,因此該方法僅適用于瀏覽器開啟Cookie。當(dāng)瀏覽器禁用Cookie時,可以選擇下面兩種服務(wù)器端的解決方案。
在Struts的Action中使用Session記錄表單提交的狀態(tài)可以解決表單重復(fù)提交問題[3],實現(xiàn)步驟如下:
1)包含有form表單的頁面通過應(yīng)用服務(wù)器程序動態(tài)生成,服務(wù)器程序為每次產(chǎn)生的form表單都分配一個唯一隨機(jī)標(biāo)識號random,并在Session和form表單的一個隱藏域中進(jìn)行保存。
2)當(dāng)用戶提交form表單時,負(fù)責(zé)接收這一請求的Struts的Action程序比較form表單隱藏域中的標(biāo)識號與存儲在Session中的標(biāo)識號是否相同,當(dāng)遇到下列情形時,Action程序?qū)⒄J(rèn)為表單重復(fù)提交,Action程序?qū)㈨撁嬷囟ㄏ虻绞№撁?
當(dāng)前用戶Session不存在表單標(biāo)識;用戶提交的表單數(shù)據(jù)沒有標(biāo)識號;存儲在當(dāng)前用戶的Session中的標(biāo)識號與表單數(shù)據(jù)中的標(biāo)識號不同。
實現(xiàn)代碼如下:
1)產(chǎn)生random并把random放入session對象和隱藏域中。
<%
int random=(int)(10000*Math.random());
session.setAttribute("random",random);
%>
<form method="post"name="thisForm"action="test.jsp" >
<input name="text"type="text"/>
<input type="hidden"name="random"value="<%=random%>">
<input name="submitButton"value="提交"type="submit"/>
</form>
2)判斷是否重復(fù)提交的Struts的Action的代碼實現(xiàn)。
Session session=request.getSession();
if(request.getParameter("random")==null||session.getAttribute("random")==null){return mapping.findForward("failure");
}else{
int randomSession = ((Integer)session.getAttribute("random")).intValue();
int randomRequest=Integer.parseInt(request.get-Parameter("random"));
if(randomSession!=randomRequest){
return mapping.findForward("failure");
}else{
//業(yè)務(wù)邏輯代碼
}
}
由于Session信息保存在服務(wù)器端,瀏覽器用戶無法干預(yù)Session的使用,因此該方法適用于任何場景。
Struts的Action提供了另一個防止表單重復(fù)提交的方法,即同步令牌(Token)機(jī)制[4]。在 Struts中,通過TokenProcessor類來創(chuàng)建和處理令牌,Struts根據(jù)用戶會話ID和當(dāng)前系統(tǒng)時間生成一個唯一的同步令牌(Token)。同步令牌機(jī)制的基本原理是[5]:應(yīng)用服務(wù)器在處理到達(dá)的請求之前,會將請求中包含的令牌值與保存在當(dāng)前用戶會話Session中的令牌值進(jìn)行比較,看是否匹配。在處理完該請求后,且在響應(yīng)發(fā)送給瀏覽器端之前,會產(chǎn)生一個新的令牌,該令牌除傳給瀏覽器端以外,也會將用戶會話Session中保存的舊的令牌進(jìn)行替換。這樣如果用戶回退到剛才的提交頁面并再次提交的話,瀏覽器端傳過來的令牌就和服務(wù)器端的令牌不一致,從而有效地避免表單重復(fù)提交問題的發(fā)生。
在Struts中使用同步令牌(Token)解決表單重復(fù)提交問題的步驟如下:
1)在轉(zhuǎn)發(fā)jsp頁面之前,創(chuàng)建一個新的同步令牌,方法如下:
saveToken(request);
在轉(zhuǎn)發(fā) jsp頁面時,<htm l:form>會自動根據(jù)session中標(biāo)識ID生成一個表單隱藏域同步令牌數(shù)據(jù),防止重復(fù)提交表單,生成的表單隱藏域格式如下:
<input type=”hidden”name=”org.apache.struts.taglib.html.TOKEN”
value=”6aa35341f25184fd996c4c918255c3ae”>,其中的value是調(diào)用TokenProcessor類中的generateToken()方法獲得的。
2)在jsp頁面的表單提交處理Action中加入同步令牌判斷方法,代碼如下:
if(isTokenValid(request,true)){
//業(yè)務(wù)邏輯代碼
}else{
//表單重復(fù)提交
saveToken(request);
return mapping.findForward(“failure”);
}
resetToken(request);//刪除session中的令牌
由于使用同步令牌解決表單重復(fù)提交問題的原理是基于Session會話的原理,因此該方法適用于所有Struts系統(tǒng)。
本文介紹了基于Struts的Web系統(tǒng)中表單重復(fù)提交的四種解決方案,并詳細(xì)分析了每種方案的設(shè)計方法及應(yīng)用場景,目前這四種解決方案已經(jīng)運(yùn)用到很多基于Struts的Web系統(tǒng)中。
[1]朱國輝,周琪云,朱文生.Web應(yīng)用中重復(fù)提交的探討[J].計算機(jī)與現(xiàn)代化,2006,(7):75 -77.
[2]阮景奎,宋國柱.重復(fù)表單提交實現(xiàn)[J].湖北汽車工業(yè)學(xué)院學(xué)報,2007,(9):33 -35.
[3]Elliot White III,Jonathan Eisenhamer.PHP 5 in Practice[M].Sams,2006.
[4]廖儀奎.Java Web開發(fā)之Struts編程基礎(chǔ)與實例精講[M],北京:中國電力出版社,2006.
[5]楊兵.令牌技術(shù)在Web項目中的研究與應(yīng)用[J].計算機(jī)與現(xiàn)代化,2005,(7):110 -112.