演示重复提交的错误:
相关文件:
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参数中