17.蛤蟆笔记go语言——表单
表单是我们平常编写Web应用常用的工具,通过表单我们可以方便的让客户端和服务器进行数据的交互。对于以前开发过Web的用户来说表单都非常熟悉,但是对于C/C++程序员来说,这可能是一个有些陌生的东西.
表单是一个包含表单元素的区域。表单元素是允许用户在表单中(比如:文本域、下拉列表、单选框、复选框等等)输入信息的元素。表单使用表单标签(<form>)定义。
在Request里面的有专门的form处理,可以很方便的整合到Web开发里面来.由于不能信任任何用户的输入,所以我们需要对这些输入进行有效性验证
表单还有一个很大的功能就是能够上传文件
表单输入
新建表单login.gtpl如下:
<html>
<head>
<title></title>
</head>
<body>
<formaction="http://127.0.0.1:9090/login" method="post">
用户名:<input type="text"name="username">
密码:<inputtype="password" name="password">
<inputtype="submit" value="登陆">
</form>
</body>
</html>
放到当前项目的文件夹下,注意使用UTF8 无BOM格式编码。
http包里面有一个很简单的方式就可以获取,怎么处理login页面的form数据?
处理上节中的代码如下:
package main
import (
"fmt"
"html/template"
"log"
"net/http"
"strings"
)
func sayhelloName(w http.ResponseWriter, r*http.Request) {
r.ParseForm()//解析url传递的参数,对于POST则解析响应包的主体(request body)
//注意:如果没有调用ParseForm方法,下面无法获取表单的数据
fmt.Println(r.Form)//这些信息是输出到服务器端的打印信息
fmt.Println("path",r.URL.Path)
fmt.Println("scheme",r.URL.Scheme)
fmt.Println(r.Form["url_long"])
for k,v := range r.Form {
fmt.Println("key:",k)
fmt.Println("val:",strings.Join(v, ""))
}
fmt.Fprintf(w,"Hello astaxie!") //这个写入到w的是输出到客户端的
}
func login(w http.ResponseWriter, r *http.Request) {
fmt.Println("method:",r.Method) //获取请求的方法
ifr.Method == "GET" {
t,_ := template.ParseFiles("login.gtpl")
t.Execute(w,nil)
} else{
//请求的是登陆数据,那么执行登陆的逻辑判断
fmt.Println("username:",r.Form["username"])
fmt.Println("password:",r.Form["password"])
}
}
func main() {
http.HandleFunc("/",sayhelloName) //设置访问的路由
http.HandleFunc("/login",login) //设置访问的路由
err :=http.ListenAndServe(":9090", nil) //设置监听的端口
if err!= nil {
log.Fatal("ListenAndServe:", err)
}
}
通过代码可以看出获取请求方法是通过r.Method来完成的,这是个字符串类型的变量,返回GET, POST, PUT等method信息。login函数中我们根据r.Method来判断是显示登录界面还是处理登录逻辑。当GET方式请求时显示登录界面,其他方式请求时则处理登录逻辑,如查询数据库、验证登录信息等。在浏览器里面打开http://127.0.0.1:9090/login进行测试。
Handler里面是不会自动解析form的,必须显式的调用r.ParseForm()。
r.Form里面包含了所有请求的参数,比如URL中query-string、POST的数据、PUT的数据。
验证表单输入
开发Web的一个原则就是,不能信任用户输入的任何信息,所以验证和过滤用户的输入信息就变得非常重要,经常会在微博、新闻中听到某某网站被入侵了,存在什么漏洞,这些大多是是因为网站对于用户输入的信息没有做严格的验证引起的,所以为了编写出安全可靠的Web程序,验证表单输入的意义重大。
平常编写Web应用主要有两方面的数据验证,一个是在页面端的js验证(目前在这方面有很多的插件库,比如ValidationJS插件),一个是在服务器端的验证。
必填字段
想要确保从一个表单元素中得到一个值,例如前面小节里面的用户名,我们如何处理呢?Go有一个内置函数len可以获取字符串的长度,这样我们就可以通过len来获取数据的长度
数字
想要确保一个表单输入框中获取的只能是数字,例如,你想通过表单获取某个人的具体年龄是50岁还是10岁,而不是像“一把年纪了”或“年轻着呢”这种描述如果我们是判断正整数,那么我们先转化成int类型,然后进行处理
对于性能要求很高的用户来说,这是一个老生常谈的问题了,他们认为应该尽量避免使用正则表达式,因为使用正则表达式的速度会比较慢。但是在目前机器性能那么强劲的情况下,对于这种简单的正则表达式效率和类型转换函数是没有什么差别的。如果你对正则表达式很熟悉,而且你在其它语言中也在使用它,那么在Go里面使用正则表达式将是一个便利的方式。Go实现的正则是RE2,所有的字符都是UTF-8编码的。
中文
有时候我们想通过表单元素获取一个用户的中文名字,但是又为了保证获取的是正确的中文,我们需要进行验证,而不是用户随便的一些输入。对于中文我们目前有效的验证只有正则方式来验证。
英文
期望通过表单元素获取一个英文值,例如我们想知道一个用户的英文名,应该是astaxie,而不是asta谢。可以很简单的通过正则验证数据:
电子邮件地址
想知道用户输入的一个Email地址是否正确,通过如下这个方式可以验证
if m, _ :=regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,}).([a-z]{2,4})$`,
r.Form.Get("email")); !m {
fmt.Println("no")
}else{
fmt.Println("yes")
}
等等其他。
预防跨站脚本
现在的网站包含大量的动态内容以提高用户体验,比过去要复杂得多。所谓动态内容,就是根据用户环境和需要,Web应用程序能够输出相应的内容。动态站点会受到一种名为“跨站脚本攻击”(Cross Site Scripting, 安全专家们通常将其缩写成 XSS)的威胁,而静态站点
则完全不受其影响。
攻击者通常会在有漏洞的程序中插入JavaScript、 VBScript、 ActiveX或Flash以欺骗用户。一旦得手,他们可以盗取用户帐户信息,修改用户设置,盗取/污染cookie和植入恶意广告等。
对XSS最佳的防护应该结合以下两种方法:一是验证所有输入数据,有效检测攻击;另一个是对所有输出数据进行适当的处理,以防止任何已成功注入的脚本在浏览器端运行。
那么Go里面是怎么做这个有效防护的呢?Go的html/template里面带有下面几个函数可
以帮你转义。
• func HTMLEscape(w io.Writer, b []byte) //把b进行转义之后写到w
• func HTMLEscapeString(s string) string //转义s之后返回结果字符串
• func HTMLEscaper(args ...interface{}) string //支持多个参数一起转义,返回结果
防止多次递交表单
是否曾经看到过一个论坛或者博客,在一个帖子或者文章后面出现多条重复的记录,这些大多数是因为用户重复递交了留言的表单引起的。由于种种原因,用户经常会重复递交表单。通常这只是鼠标的误操作,如双击了递交按钮,也可能是为了编辑或者再次核对填写过的信息,点击了浏览器的后退按钮,然后又再次点击了递交按钮而不是浏览器的前进按钮。当然,也可能是故意的——比如,在某项在线调查或者博彩活动中重复投票。那我们如何有效的防止用户多次递交相同的表单呢?
解决方案是在表单中添加一个带有唯一值的隐藏字段。在验证表单时,先检查带有该惟一值的表单是否已经递交过了。如果是,拒绝再次递交;如果不是,则处理表单进行逻辑处理。另外,如果是采用了Ajax模式递交表单的话,当表单递交后,通过javascript来禁用表单的递交按钮。
处理文件上传
要使表单能够上传文件,首先第一步就是要添加form的enctype属性,enctype属性有如
下三种情况:
l application/x-www-form-urlencoded 表示在发送前编码所有字符(默认)
l multipart/form-data 不对字符编码。在使用包含文件上传控件的表单时,必须使用该值。
l text/plain 空格转换为"+" 加号,但不对特殊字符编码。
有疑问加站长微信联系(非本文作者)