zoukankan      html  css  js  c++  java
  • goweb-表单

    表单

    简单的处理一个登陆界面

    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) //获取请求的方法
    	if r.Method == "GET" {
    		t, _ := template.ParseFiles("login.gtpl")
    		log.Println(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)
    	}
    }
    

    request.Form是一个url.Values类型,里面存储的是对应的类似key=value的信息,下面展示了可以对form数据进行的一些操作:

    v := url.Values{}
    v.Set("name", "Ava")
    v.Add("friend", "Jess")
    v.Add("friend", "Sarah")
    v.Add("friend", "Zoe")
    // v.Encode() == "name=Ava&friend=Jess&friend=Sarah&friend=Zoe"
    fmt.Println(v.Get("name"))
    fmt.Println(v.Get("friend"))
    fmt.Println(v["friend"])
    
    

    Request本身也提供了FormValue()函数来获取用户提交的参数。如r.Form["username"]也可写成r.FormValue("username")。调用r.FormValue时会自动调用r.ParseForm,所以不必提前调用。r.FormValue只会返回同名参数中的第一个,若参数不存在则返回空字符串。

    服务端表单验证

    这一部分讲了验证数字,中文,名字,号码等许多需要验证的东西,主要有:通过正则验证,通过逻辑结构验证并调用相关的包

    预防跨站脚本

    现在的网站包含大量的动态内容以提高用户体验,比过去要复杂得多。所谓动态内容,就是根据用户环境和需要,Web应用程序能够输出相应的内容。动态站点会受到一种名为“跨站脚本攻击”(Cross Site Scripting, 安全专家们通常将其缩写成 XSS)的威胁,而静态站点则完全不受其影响。

    攻击者通常会在有漏洞的程序中插入JavaScript、VBScript、 ActiveX或Flash以欺骗用户。一旦得手,他们可以盗取用户帐户信息,修改用户设置,盗取/污染cookie和植入恶意广告等。

    对XSS最佳的防护应该结合以下两种方法:一是验证所有输入数据,有效检测攻击(这个我们前面小节已经有过介绍);另一个是对所有输出数据进行适当的处理,以防止任何已成功注入的脚本在浏览器端运行。

    主要通过转义来实现这个,用到text/template和html/template

    防止多次递交表单

    不知道你是否曾经看到过一个论坛或者博客,在一个帖子或者文章后面出现多条重复的记录,这些大多数是因为用户重复递交了留言的表单引起的。由于种种原因,用户经常会重复递交表单。通常这只是鼠标的误操作,如双击了递交按钮,也可能是为了编辑或者再次核对填写过的信息,点击了浏览器的后退按钮,然后又再次点击了递交按钮而不是浏览器的前进按钮。当然,也可能是故意的——比如,在某项在线调查或者博彩活动中重复投票。那我们如何有效的防止用户多次递交相同的表单呢?

    解决方案是在表单中添加一个带有唯一值的隐藏字段。在验证表单时,先检查带有该唯一值的表单是否已经递交过了。如果是,拒绝再次递交;如果不是,则处理表单进行逻辑处理。另外,如果是采用了Ajax模式递交表单的话,当表单递交后,通过javascript来禁用表单的递交按钮。

    
    func login(w http.ResponseWriter, r *http.Request) {
    	fmt.Println("method:", r.Method) //获取请求的方法
    	if r.Method == "GET" {
    		crutime := time.Now().Unix()
    		h := md5.New()
    		io.WriteString(h, strconv.FormatInt(crutime, 10))
    		token := fmt.Sprintf("%x", h.Sum(nil))
    
    		t, _ := template.ParseFiles("login.gtpl")
    		t.Execute(w, token)
    	} else {
    		//请求的是登陆数据,那么执行登陆的逻辑判断
    		r.ParseForm()
    		token := r.Form.Get("token")
    		if token != "" {
    			//验证token的合法性
    		} else {
    			//不存在token报错
    		}
    		fmt.Println("username length:", len(r.Form["username"][0]))
    		fmt.Println("username:", template.HTMLEscapeString(r.Form.Get("username"))) //输出到服务器端
    		fmt.Println("password:", template.HTMLEscapeString(r.Form.Get("password")))
    		template.HTMLEscape(w, []byte(r.Form.Get("username"))) //输出到客户端
    	}
    }
    

    利用唯一性来判断,但并不能防止恶意的攻击

    处理文件上传

    你想处理一个由用户上传的文件,比如你正在建设一个类似Instagram的网站,你需要存储用户拍摄的照片。这种需求该如何实现呢?

    要使表单能够上传文件,首先第一步就是要添加form的enctype属性,enctype属性有如下三种情况:

    • application/x-www-form-urlencoded 表示在发送前编码所有字符(默认)
    • multipart/form-data 不对字符编码。在使用包含文件上传控件的表单时,必须使用该值。
    • text/plain 空格转换为 "+" 加号,但不对特殊字符编码。

    上传文件主要三步处理:

    表单中增加enctype="multipart/form-data"
    服务端调用r.ParseMultipartForm,把上传的文件存储在内存和临时文件中
    使用r.FormFile获取文件句柄,然后对文件进行存储等处理
    

    Go支持模拟客户端表单功能支持文件上传,详细用法请看如下示例:

    package main
    
    import (
    	"bytes"
    	"fmt"
    	"io"
    	"io/ioutil"
    	"mime/multipart"
    	"net/http"
    	"os"
    )
    
    func postFile(filename string, targetUrl string) error {
    	bodyBuf := &bytes.Buffer{}
    	bodyWriter := multipart.NewWriter(bodyBuf)
    
    	//关键的一步操作
    	fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename)
    	if err != nil {
    		fmt.Println("error writing to buffer")
    		return err
    	}
    
    	//打开文件句柄操作
    	fh, err := os.Open(filename)
    	if err != nil {
    		fmt.Println("error opening file")
    		return err
    	}
    	defer fh.Close()
    	
    	//iocopy
    	_, err = io.Copy(fileWriter, fh)
    	if err != nil {
    		return err
    	}
    
    	contentType := bodyWriter.FormDataContentType()
    	bodyWriter.Close()
    
    	resp, err := http.Post(targetUrl, contentType, bodyBuf)
    	if err != nil {
    		return err
    	}
    	defer resp.Body.Close()
    	resp_body, err := ioutil.ReadAll(resp.Body)
    	if err != nil {
    		return err
    	}
    	fmt.Println(resp.Status)
    	fmt.Println(string(resp_body))
    	return nil
    }
    
    // sample usage
    func main() {
    	target_url := "http://localhost:9090/upload"
    	filename := "./astaxie.pdf"
    	postFile(filename, target_url)
    }
    

    这一章里面我们学习了Go如何处理表单信息,我们通过用户登录、上传文件的例子展示了Go处理form表单信息及上传文件的手段。但是在处理表单过程中我们需要验证用户输入的信息,考虑到网站安全的重要性,数据过滤就显得相当重要了,因此后面的章节中专门写了一个小节来讲解了不同方面的数据过滤,顺带讲一下Go对字符串的正则处理。

    客户端和服务器端是如何进行数据上的交互,客户端将数据传递给服务器系统,服务器接受数据又把处理结果反馈给客户端。

    链接

  • 相关阅读:
    Linux下的MySQL主从同步
    人不能同时在两个地方做猪(Scrum Team)
    memcache安装
    Java开发中的Memcache原理及实现
    linux mv
    nginx
    idea 热部署
    vue watch
    vue入门
    基于vue-cli快速构建
  • 原文地址:https://www.cnblogs.com/ygjzs/p/12191343.html
Copyright © 2011-2022 走看看