演示重复提交的错误:
相关文件:
struts.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN""http://struts.apache.org/dtds/struts-2.0.dtd"><struts><package name="strutsqs" extends="struts-default"><action name="Login" class="com.gq.LoginAction"><result name="error">/error.jsp</result><result name="success">/success.jsp</result></action></package>
</struts>
login.jsp
<%@ page language="java" import="java.util.*" pageEncoding="ISO-8859-1"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head><base href="<%=basePath%>"><title>My JSP 'login.jsp' starting page</title><meta http-equiv="pragma" content="no-cache"><meta http-equiv="cache-control" content="no-cache"><meta http-equiv="expires" content="0"> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"><meta http-equiv="description" content="This is my page"><!--<link rel="stylesheet" type="text/css" href="styles.css">--></head><body><form action="Login.action" method="post"><table width="207" border="1" height="82">
<tbody><tr>
<td> UserName:</td>
<td> <input type="text" name="username"></td></tr>
<tr>
<td> Password:</td>
<td> <input type="password" name="password"></td></tr>
<tr>
<td> <input type="submit" value="Login"></td>
<td> <input type="reset" value="Reset" name="reset"></td></tr>
</tbody></table></form></body>
</html>
LoginAction.java
public class LoginAction {private String username;private String password;public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}@SuppressWarnings("unchecked")public String execute() throws Exception{// Just for test of token.System.out.println( "name:" + getUsername() + "\tpassword:" + getPassword());consumeTimeForToken();if( getUsername().equals("gqltt") && getPassword().equals("123")){addToSessionScope("user", getUsername());return "success";}return "error";}@SuppressWarnings("unchecked")void addToSessionScope( String key, String value ){ActionContext.getContext().getSession().put(key, value);}//消耗时间,提供重复提交的机会void consumeTimeForToken(){int result = 0;for( int i = 0 ; i<30000000 ; i++ ){result += (int)i/551.22;result /= 3.1458;}System.out.println("result=" + result);}// Just used for unit test!String findCompanyByName( ){Map<String, String> records = new HashMap<String, String>();records.put("gqltt", "SNJP");records.put("Liyanhong", "baidu");records.put("Bill", "Microsoft");return records.get( getUsername() );}
}
提交页面:
输出结果:
解决办法:
1、在提交页面的 form 中添加 <s:token/>
2、在Struts2 的配置文件中启用 TokenInterceptor拦截器或 TokenSessionStoreInterceptor拦截器
注意:
1、必须配置默认的拦截器(basicStack),否则取不到数据,抛出 NullPointerException
2、必须指定重复提交后的错误页面(invalid.token)
优点:可以防止客户重复提交,大大地降低了服务器的负荷。
缺点:对用户来说,可能会很不方便,一不小心点击了提交按钮,进入到了invalid.token页面,就再也回不去了,上述的操作就再也看不见了。(的确很恶心,即使倒退回登录页面,再次正常登录,还是会进入 invalid.token 页面!除非关掉网页再打开。)
参考:http://chengyue2007.iteye.com/category/73492?show_full=true
等待画面:
struts.xml 添加配置:
<action name="LongLived" class="com.gq.LongLivedAction"><interceptor-ref name="completeStack"/> <interceptor-ref name="execAndWait"/> <result name="wait">/wait.jsp</result> <result name="error">/error.jsp</result><result name="success">/success.jsp</result>
</action>
LongLiveAction.java
public class LongLivedAction {private String username;private String password;public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}@SuppressWarnings("unchecked")public String execute() throws Exception{addToSessionScope("user", getUsername());// Just for test of token.System.out.println( "name:" + getUsername() + "\tpassword:" + getPassword());consumeTimeForToken();if( getUsername().equals("gqltt") && getPassword().equals("123")){//addToSessionScope("user", getUsername());return "success";}return "error";}@SuppressWarnings("unchecked")void addToSessionScope( String key, String value ){Map session = ActionContext.getContext().getSession();if( session == null ){System.out.println("Error: session is null...");return ;}session.put(key, value);//ActionContext.getContext().getSession().put(key, value);}//消耗时间void consumeTimeForToken(){try {Thread.sleep( 4*1000 );} catch (InterruptedException e) {// do nothing...}System.out.println("After 4 seconds...");}
}
wait.jsp
注意:不要忘了加入 <%@ taglib prefix="s" uri="/struts-tags"%>
<%@ page language="java" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head><base href="<%=basePath%>"><title>Please Wait</title><meta http-equiv="refresh" content="3;url=<s:url includeParams='all'/> "/> </head><body>Please Wait...</body>
</html>
缺点:将参数写在 URL 中,明文化了(密码都看得到了!!!)
问题:取不到 session,Map session = ActionContext.getContext().getSession(); 操作一直返回 Null。为什么?
参考:http://webservices.ctocio.com.cn/java/470/9189470.shtml
下面是另一种,等待画面的配置,可以解决 session 为 Null 的问题:
struts.xml 添加配置:
<action name="Wait" class="com.gq.WaitAction"> <interceptor-ref name="defaultStack"/><interceptor-ref name="execAndWait"> <param name="excludeMethods">input</param> <!-- 等待时间,执行时间没有超过此值,将不显示等待画面 (毫秒) <param name="delay">1000</param>--> <!-- 间隔检查时间,检查后台进程有没有执行完毕,如果完成了它就立刻返回,不用等到等待,用户不会看到等待画面 <param name="delaySleepInterval">50</param>--> </interceptor-ref> <result name="wait">/wait.jsp</result> <result name="error">/error.jsp</result><result name="success">/success.jsp</result></action>
WaitAction.java
public class WaitAction extends ActionSupport implements SessionAware {private static final long serialVersionUID = -5724238080200557097L;private Map session;public void setSession(Map session) {this.session = session;}public Map getSession(){return session;}private String username;private String password;public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}@SuppressWarnings("unchecked")public String execute() throws Exception{System.out.println("In Wait.action");addToSessionScope("user", getUsername());// Just for test of token.System.out.println( "name:" + getUsername() + "\tpassword:" + getPassword());consumeTimeForToken();if( getUsername().equals("gqltt") && getPassword().equals("123")){//addToSessionScope("user", getUsername());return "success";}return "error";}@SuppressWarnings("unchecked")void addToSessionScope( String key, String value ){getSession().put(key, value);}//消耗时间void consumeTimeForToken(){try {Thread.sleep( 4*1000 );} catch (InterruptedException e) {// do nothing...}System.out.println("After 4 seconds...");}
}
注意:Action 要实现 SessionAware接口(验证过——这样的添加属性到 session 是OK 的!)
因为这个action将会以单独的线程执行,所以你不能用ActionContext,因为它是ThreadLocal.这也就是说如果你要访问session数据,你必须实现 SessionAware结构而不是调用ActionContext.getSesion() 。
wait.jsp(带刷新失败时候的超链接)
<%@page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>
<%@taglib prefix="s"uri="/struts-tags"%>
<!DOCTYPEHTMLPUBLIC"-//W3C//DTDHTML4.01Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<meta http-equiv="refresh" content="1;url=<s:url includeParams="none" />"/>
<title>
</title>
</head>
<body>
<h1>数据处理中,请稍等......</h1>
如果没有自动跳转请<a href="<s:url includeParams="all" />">点这里</a>.
</body>
</html>
其中的includeParams参数取值为:
none,不把参数加入到url参数中
all,是把get和post中的参数加入到url参数中
get,是只把get中的参数加入到url参数中