在WEB应用编程时,最常用到的是form表单工具。表单是一个包含表单元素的区域。表单元素是允许用户在表单中(比如:文本域、下拉列表、单选框、复选框等等)输入信息的元素。表单使用表单标签(<form>)定义。比如:
<form>
...
input 元素
...
</form>
我们现在来演示一个使用Go语言做Web表单登录的例子
(1)处理表单的输入
我们现在在新建项目的目录里创建一个html,写入以下代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<form action="http://127.0.0.1:9090/login" method="post">
用户名 :<input type="text" name="username">
密码 :<input type="password" name="password">
<input type="submit" value=" 登录 ">
</form>
</body>
</html>
上面表单代码的意思是我们输入用户名和密码点击登录后,客户端就会提交表单到服务器的/login下。
下面是登录后端的代码逻辑
package main
import (
"net/http"
"log"
"fmt"
"strings"
"html/template"
)
func main() {
//指定访问的路由
http.HandleFunc("/login",login)
//设定监听端口
err := http.ListenAndServe(":9090",nil)
if err != nil {
log.Fatal("ListenAndServe: ",err)
}
}
/**
登录后端处理逻辑
*/
func login(rw http.ResponseWriter,request *http.Request){
// 获取请求的方法 request.Method 字符串类型的变量,返回GET, POST,PUT等method信息。
fmt.Printf("method: %v\n",request.Method)
//根据 request.Method 来判断是显示登录界面还是处理登录逻辑
if request.Method == "GET" {
//使用template.ParseFiles 根据指定的文件创建模板示例
t,_ := template.ParseFiles("login.html")
//执行数据融合
t.Execute(rw,nil)
}else {
//请求的是登录数据,那么执行登录的逻辑判断
//解析传过来的参数,默认不会解析,必须显示调用后服务器才会输出参数信息
request.ParseForm()
//这里的request.Form["username"]可以用request.FormValue("username")代替,那么就不需要显示调用 request.ParseForm
fmt.Printf("username: %v\n",request.Form["username"])
fmt.Printf("password: %v\n",request.Form["password"])
}
}
当我们在浏览器里面打开 http://127.0.0.1:9090/login 的时候,出现如下界面
界面图.png第一次登录网址时是显示登录界面,此时 request.Method 是 GET,后台机会使用template.ParseFiles 根据指定的文件创建模板示例(浏览器上的视图显示就是这么来的)。等我们输入用户名与密码再登录后 request.Method 就会成为POST。如果我们需要在服务器上输出传输过来的数据,就必须显示的调用 request.ParseForm(),默认情况下是不会显示调用的。
request.Form 是一个url.Values类型,里面存储的是对应的类似 key=value(key与value都是string类型) 的信息。上面的request.Form["username"]可以用request.FormValue("username")代替,那么就不需要显示调用 request.ParseForm了,因为调用 request.FormValue 时会自动调用 request.ParseForm。但是如果同一个键中包含多个值,使用 request.FormValue 时只会返回第一个值,有关request.FormValue 的源码如下:
/**
FormValue返回查询的命名组件的第一个值。
POST和PUT正文参数优先于URL查询字符串值。
如有必要,FormValue会调用ParseMultipartForm和ParseForm,并忽略这些函数返回的任何错误。
如果key不存在,FormValue将返回空字符串。
要访问同一个键的多个值,请调用ParseForm,然后直接检查Request.Form。
*/
func (r *Request) FormValue(key string) string {
if r.Form == nil {
r.ParseMultipartForm(defaultMaxMemory)
}
if vs := r.Form[key]; len(vs) > 0 {
return vs[0] //返回第一值
}
return ""
}
(2)验证表单的输入
在不使用正则表达式的情况下,如何判断以下情况:
①判断传输过来的字符串为空字符串或为空(无法判断字符串中间是否为空)
//使用 request.FormValue 获取传输的数据
username := request.FormValue("username")
password := request.FormValue("password")
//使用len(strings.TrimSpace(username)) 判断用户名与密码是否为空字符串
fmt.Printf("username: %v, len(strings.TrimSpace(username))= %v\n",username, len(strings.TrimSpace(username)))
fmt.Printf("password: %v,len(strings.TrimSpace(username))= %v\n", password,len(strings.TrimSpace(password)))
升级版:将字符串所有的空格消除后判断字符串是否为空字符串
//使用 strings.Join + strings.Fields + strings.TrimSpace
username := strings.Join(strings.Fields(strings.TrimSpace(request.FormValue("username"))),"")
password := strings.Join(strings.Fields(strings.TrimSpace(request.FormValue("password"))),"")
fmt.Println("username: ",username," password: ",password)
当然也可以使用下面这些方法
strings.Replace(username," ","",-1) //注意 第一个为“ ”中间有个空格,后面的“”是没有空格的,-1代表将前面的字符全部替换后面的字符
(3)判断传输过来的数据都是数字
//先经过去空字符串操作处理,再使用strconv.Atoi
getint1,err1:=strconv.Atoi(username)
getint2,err2:=strconv.Atoi(password)
fmt.Println("username: ",username," password: ",password)
if err1 != nil || err2 != nil{
fmt.Println("数字无法转换:",username,password)
}else if getint1 > 100 || getint2 > 100{
fmt.Println("数字太大:",username,password)
}else {
fmt.Println("数字:",username,password)
}
(4)过滤标签,预防脚本攻击
为了防止用户在客户端上进行脚本攻击(比如在客户端上插入JavaScript、VBScript、 ActiveX或Flash等方式欺骗服务器),我们可以使用Go的html/template包会自动过滤html标签,自动进行数据转义。比如这三个函数:
//以下三个是返回纯文本数据的转义HTML等价物。
func HTMLEscape(w io.Writer, b []byte) //把b进行转义之后写到w
func HTMLEscapeString(s string) string //转义s之后返回结果字符串
func HTMLEscaper(args ...interface{}) string //支持多个参数一起转义,返回结果字符串
比如我们在用户名上输入<script>alert()</script>,在调用上面的函数后,就会自动进行转义操作
username := strings.Join(strings.Fields(strings.TrimSpace(request.FormValue("username"))),"")
password := strings.Join(strings.Fields(strings.TrimSpace(request.FormValue("password"))),"")
fmt.Println("username:", template.HTMLEscapeString(username)) // 输出到服务器端
fmt.Println("password:", template.HTMLEscapeString(password))
template.HTMLEscape(rw, []byte(username)) // 输出到客户端
客户端会输出以下结果:
<script>alert()</script>
(5)防止多次递交表单
为了防止用户在同一个表单多次提交,我们可以是在模版里面增加了一个隐藏字段 token ,这个值我们通过MD5(时间戳)来获取惟一值,然后我们把这个值存储到
服务器端,以方便表单提交时比对判定。
客户端模板上添加隐藏字段token
<input type="hidden" name="token" value="{{.}}">
后端的代码处理逻辑
if request.Method == "GET" {
crutime := time.Now().Unix() //获取经过的秒数
h := md5.New() //获取新的hash值
io.WriteString(h,strconv.FormatInt(crutime,10)) //写入io
token := fmt.Sprintf("%x",h.Sum(nil)) //格式化
//使用template.ParseFiles 根据指定的文件创建模板示例
t, _ := template.ParseFiles("login.html")
//执行数据融合
t.Execute(rw, token)
}
按照上面的代码执行后,在客户端的源代码界面上的token值就会由唯一的初始值,且会根据刷新实时更新,这样就保证了每次显示form表单的时候都是唯一的,用户递交的表单保持了唯一性。
Imagewdx.png(6)处理文件上传
在客户端进行文件上传操作,我们需要在表单中添加form的 enctype 属性, enctype 属性有如下三种情况:
//表示在发送前编码所有字符(默认)
application/x-www-form-urlencoded
//不对字符编码,在使用包含文件上传控件的表单时,必须使用该值。
multipart/form-data
//空格转换为 "+" 加号,但不对特殊字符编码。
text/plain
根据 enctype 属性,修改的客户端代码如下所示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>上传文件</title>
</head>
<body>
<form enctype="multipart/form-data" action="http://127.0.0.1:9090/upload" method="post">
<input type="file" name="uploadfile" />
<input type="hidden" name="token" value="{{.}}"/>
<input type="submit" value="upload" />
</form>
</body>
</html>
服务器端上传文件代码如下所示:
func upload(rw http.ResponseWriter, request *http.Request) {
//获取请求方式:post还是get
fmt.Println("method: ", request.Method)
if request.Method == "GET" {
//获取经过的秒数
cruTime := time.Now().Unix()
//获取新的hash值,防止页面重复提交
h := md5.New()
//将cruTime进行10进制转换,并写入io
io.WriteString(h, strconv.FormatInt(cruTime, 10))
//Sum将当前哈希附加到b并返回结果切片。
token := fmt.Sprintf("%x", h.Sum(nil))
//使用template.ParseFiles 根据指定的文件创建模板示例
t, _ := template.ParseFiles("login.html")
//执行数据融合展现到客户端页面
t.Execute(rw, token)
} else {
//解析整个请求体,并将其文件部分的总maxMemory字节存储在内存中,其余部分存储在临时文件的磁盘上。
request.ParseMultipartForm(32 << 20)
//通过 request.FormFile 获取客户端的文件句柄
file, handler, err := request.FormFile("uploadfile")
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
fmt.Fprintf(rw, "%v", handler.Header)
//根据指定的位置打开指定的文件
f, err := os.OpenFile("./test/"+handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
fmt.Println(err)
return
}
defer f.Close()
//使用 io.Copy 来存储文件
io.Copy(f, file)
}
}
注意
①ParseMultipartForm(maxMemory int64) 这个函数,调用之后,上传的文件存储在 maxMemory 大小的内存里面,如果文件大小超过了maxMemory ,那么剩下的部分将存储在系统的临时文件中。
②调用request.FormFile("uploadfile")函数,获取文件句柄,然后对文件进行存储等处理。
参考书籍:《Go Web编程》
网友评论