Form表单做为web2.0时代的重要角色,也是我们与web网站进行数据交互的重要渠道,但是大家在web网站开发过程中,都会遇到一个问题,那就是如何避免表单重复提交,我们可不确定用户可在提交了一个表单后,是否有足够的耐心等待我们的程序加载完成,如果此时用户不耐烦的在前台重复刷新页面,那么就会造成数据重复提交、信息不准确,因此我们在程序设计时一定要规避这样的问题,接下来介绍一下在Django框架开发中如何避免此问题。
首先说明一下Http协议中两种非常常见的方法GET和POST。
1、GET方法往往被视为向服务器索取信息的一种手段,但是通过使用"?"和"&"符号也可以将不同的表单项提交给服务器,例如"/test?id=1"中我们就将id=1提交给了后台,有时在一些特殊情况中,我们甚至不需要在html中写form表单,只要我们保证url正确就可以了,但是使用GET方式提交也有一个问题需要注意,IE对于URL长度的限制是2083个字节,其他浏览器或是web server都有不同的限制,所以在提交较长的内容时,GET方法就有些不合适了。
2、POST方法相比GET方法在数据提交上更安全一些,因为GET方法将参数统一放到了URL中,随后在HTTP包头中发向服务器,但是提交的参数也就无疑被暴露了,在浏览器中可以直观的看到提交的具体内容,在web server中也可以通过访问日志随便查看提交的内容,对于有安全性的数据一定是采用POST方式来提交的,同时对于密码等敏感内容也一定要做加密处理,采用POST方法对于数据内容理论上也没有长度限制,这样就可以提交更多的数据内容了。
使用POST方法提交表单往往可以增加信息传输的安全性,如果表单项过多也避免了较长的url,因此在web开发中我更喜欢用POST方法,回到之前的问题,就是页面提交过后,此时如果刷新页面,那么浏览器会提示重新提交表单,此时如果我们点击继续后,那么浏览器就会将之前提交过的内容再次发送至服务器,如果我们在后台没有做限制处理,那么数据就被重复提交了,试想一下如果A用户要转账给B用户,A用户输入转账金额2000元,在表单提交后,后台根据A用户发送的请求在数据库里更新A账户信息,如果此时A用户重复刷新页面,那么后台岂不是要连续的从A用户里扣除2000元增加到B用户中么,虽然现实生活中不可能发生这样的事情,但是这样却很好的说明了表单重复提交的危险性,因此对于POST方法提交表单,我们可以用下面的方式来避免重复提交。
1、URL重定向
这种方式较为简单,我们可以在用户提交的表单后,在提交页面做一个读秒的倒计时提示,这也是我们在大多数网站上经常见到的,在倒计时结束后,页面被重新定向到上一级页面,或是我们希望被跳转的页面,这样在一定程度上就避免了重复刷新带来的风险,或是在页面提交后直接进行URL重定向,在Django的视图中我们可以使用HttpResponseRedirect来重定向页面。
2、Cookie验证
上面的方式我们通过页面来进行控制,也就是说提交过后页面url放生了变化,此时用户再次刷新时就不会请求之前提交的页面,除了URL重定向,我们也可以通过cookie中key-value来做进一步限制,首先进入提交页前我们在cookie中写入一个值,在提交过后重置该值,这样后台通过获取cookie中的值就可以做判断了,此时如果用户再次刷新页面时,就可以返回提示说明信息已经提交过,不需要重复提交,在django框架中的代码如下。
# 表单视图 def page(request): response = render_to_response(‘post.html‘) # 在客户端Cookie中添加Post表单token,避免用户重复提交表单 response.set_cookie("postToken",value=‘allow‘) return response #提交视图 def post(request): # 检测Token值,判断用户提交动作是否合法 if request.COOKIES["postToken"] !=‘allow‘: # 不存在合法Token,该表单为重复提交 return HttpResponse("you can‘t post it again.") ‘‘‘ 代码逻辑处理段 ‘‘‘ response = render_to_response(‘newPage.html‘) # 表单POST提交成功,重置客户端Cookie中存在的Token值,避免重复提交 response.set_cookie("postToken",value=‘disable‘) return response
3、Hidden属性的隐藏域
上一种方式我们在用户的浏览器中写入了指定的token值,我们也可以在服务器的session会话中写入特定的信息,然后将内容渲染到html页面中表单的隐藏域,用户从页面中是看不到这个隐藏域的,一般有特殊信息提交时会用到,比如这里的重复提交验证,我们就可以用表单的Hidden这个属性,我们将服务器生成的值写入到表单,随用户操作一起提交回服务器,那么如果该值与服务器端匹配,程序继续执行,否则认为重复提交,这里要注意与cookie操作中一样,我们在提交完后,服务器一定要清空session中的值。
# 表单视图 def page(request): # 在服务端session中添加key认证,避免用户重复提交表单 token = allow # 可以采用随机数 request.session[‘postToken‘] = token # 将Token值返回给前台模板 return render_to_response(‘post.html‘,‘postToken‘:token) #提交视图 def post(request): # 检测session中Token值,判断用户提交动作是否合法 Token = request.session.get(postToken,default=None) # 获取用户表单提交的Token值 userToken = request.POST[‘postToken‘] if userToken !=Token: # 不存在合法Token,该表单为重复提交 return HttpResponse("you can‘t post it again.") ‘‘‘ 代码逻辑处理段 ‘‘‘ # 表单POST提交成功,重置服务端中存在的Token值,避免重复提交 del request.session[‘postToken‘] return render_to_response(‘newPage.html‘)
4、验证码
我们可以在表单页中加入验证码,每次提交时服务端来做验证,但是这样的方式又影响了提交操作的便捷度,有得有失。
对于GET的提交方式较为简单,在提交后重新定义url地址就可以了,因为url中没有了参数,也就避免了重复提交,但是为了避免用户重新访问之前的url地址,我们也可以结合上面的方法来对GET方法进行限制。
写在最后
对于表单重复提交的情况,我们发现往往最容易出现在表单页与提交页URL相同、或是页面提交后依然停留在提交页URL中,在Django框架中,我认为表单页与提交页应该尽量使用不同的URL,不同的视图,这样我们可以更好的避免该问题,例如在提交后我们将URL重定向到表单页,甚至可以在前台使用Ajax异步来提交数据,这样提交动作不会影响到页面变化,用户再次刷新也只能刷新当前的表单页了。相信在web程序开发中一定还有更多种方法来规避表单重复提交的问题,这里所展示的也不一定是最完整准确的,但在web程序开发中我们一定要考虑到类似这样的异常,这才是最重要的。
原创文章首发自阿布的博客,本文地址:http://www.abuve.com/article/17/