美文网首页
Go语言 Web开发(4)表单

Go语言 Web开发(4)表单

作者: 小杰的快乐时光 | 来源:发表于2018-08-23 05:39 被阅读0次

    在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)) // 输出到客户端
    

    客户端会输出以下结果:

    &lt;script&gt;alert()&lt;/script&gt;
    

    (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编程》

    相关文章

      网友评论

          本文标题:Go语言 Web开发(4)表单

          本文链接:https://www.haomeiwen.com/subject/vyouiftx.html