在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
1. 1).GOPATH设置 先设置自己的GOPATH,可以在本机中运行$PATH进行查看: userdeMacBook-Pro:~ user$ $GOPATH -bash: /Users/user/go: is a directory 在这可见我的GOPATH是/Users/user/go,并在该目录下生成如下作用的三个子目录:
2).应用目录结构 然后之后如果想要自己新建应用或者一个代码包都是在src目录下新建一个文件夹,文件夹一般是代码包名称,比如$GOPATH/src/mymath/sqrt.go,在这里,包名就是mymath,然后其代码中的包名写成package mymath,比如: package mymath func Sqrt(x float64) float64{ z := 0.0 for i := 0; i < 1000; i++ { z -= ( z * z - x ) / ( 2 * x ) } return z } 当然也允许多级目录,例如在src下面新建了目录$GOPATH/src/github.com/astaxie/beedb,在这里包路径就是github.com/astaxie/beedb,包名称为最后一个目录beedb
3).编译应用 假设上面我们建好了自己的mymath应用包,之后的编译安装方法有两种:
编译安装好后,我们就能够到$GOPATH/pkg/${GOOS}_${GOARCH}目录下看见mymath.a这个应用包 ${GOOS}_${GOARCH}是平台名,如mac系统是darwin_amd64,linux是linux_amd64 userdeMacBook-Pro:src user$ cd mymath/ userdeMacBook-Pro:mymath user$ ls sqrt.go userdeMacBook-Pro:mymath user$ go install userdeMacBook-Pro:mymath user$ cd .. userdeMacBook-Pro:src user$ cd .. userdeMacBook-Pro:go user$ cd pkg userdeMacBook-Pro:pkg user$ cd darwin_amd64/ userdeMacBook-Pro:darwin_amd64 user$ ls golang.org mymath.a userdeMacBook-Pro:darwin_amd64 user$
4).调用应用 然后就是对该应用进行调用 比如我们再新建一个应用包mathapp,创建一个main.go源码: package main import( "mymath" "fmt" ) func main() { fmt.Printf("Hello, world. Sqrt(2) = %v \n", mymath.Sqrt(2)) } 然后进入该应用目录,运行go build来编译程序: userdeMacBook-Pro:src user$ cd mathapp/ userdeMacBook-Pro:mathapp user$ ls main.go userdeMacBook-Pro:mathapp user$ go build userdeMacBook-Pro:mathapp user$ ls main.go mathapp userdeMacBook-Pro:mathapp user$ 然后运行该可执行文件,./mathapp,得到返回结果为: userdeMacBook-Pro:mathapp user$ ./mathapp Hello, world. Sqrt(2) = 1.414213562373095 ⚠️ package <pkgName> :用于指明当前文件属于哪个包 package main : 说明该文件是一个可独立执行的文件,它在编译后会产生可执行文件 除了main包外,其他包都会生成*.a文件(也就是包文件),并放在$GOPATH/pkg/${GOOS}_${GOARCH}目录下 每一个可独立执行的go程序中,必定都包含一个package main,在这个main包中必定包含一个入口函数main(),该函数即没有参数,也没有返回值 5).获取远程包 如果你想要获取的是一个远程包,可以使用go get获取,其支持多数的开源社区(如github、googlecode、bitbucket、Launchpad),运行语句为: go get github.com/astaxie/beedb
go get -u参数可以自动更新包,并且在使用go get时会自动获取该包依赖的其他第三方包 userdeMBP:~ user$ go get github.com/astaxie/beedb userdeMBP:~ user$ cd go/src userdeMBP:src user$ ls mymath golang.org mathapp github.com userdeMBP:src user$ cd github.com/ userdeMBP:github.com user$ ls WeMeetAgain astaxie btcsuite conformal userdeMBP:github.com user$ cd astaxie/ userdeMBP:astaxie user$ ls beedb userdeMBP:astaxie user$ cd ../../.. userdeMBP:go user$ cd pkg userdeMBP:pkg user$ ls darwin_amd64 userdeMBP:pkg user$ cd darwin_amd64/ userdeMBP:darwin_amd64 user$ ls github.com golang.org mymath.a userdeMBP:darwin_amd64 user$ cd github.com/astaxie/ userdeMBP:astaxie user$ ls beedb.a 通过这个命令可以获取相应的源码,对应的开源平台采用不同的源码控制工具,如github采用git,googlecode采用hg。因此想要使用哪个平台的代码就要对应安装相应的源码控制工具 上面的代码在本地的代码结构为:
go get 本质上可以分成两步:
使用方法就是: import github.com/astaxie/beedb
2.相关http内容可见go标准库的学习-net/http
3.表单学习——form 1)如何处理表单的输入 举例:
package main import( "fmt" "net/http" "log" ) func index(w http.ResponseWriter, r *http.Request){ r.ParseForm() //解析URL传递的参数,对于POST则解析响应包的主体(request body),如果不调用它则无法获取表单的数据 fmt.Println(r.Form) fmt.Println(r.PostForm) fmt.Println("path", r.URL.Path) fmt.Println("scheme", r.URL.Scheme) fmt.Println(r.Form["url_long"]) //如果使用的是方法FormValue()方法(它只会返回同名参数slice中的第一个,不存在则返回空字符串),则可以不用调用上面的ParseForm()方法 for k, v := range r.Form{ fmt.Println("key :", k) fmt.Println("value :", v) } html := `<html> <head> <title></title> </head> <body> <form action="http://localhost:9090/login" method="post"> username: <input type="text" name="username"> password: <input type="text" name="password"> <input type="submit" value="login"> </form> </body> </html>` fmt.Fprintf(w, html) //将html写到w中,w中的内容将会输出到客户端中 } func login(w http.ResponseWriter, r *http.Request){ fmt.Println("method", r.Method) //获得请求的方法 r.ParseForm() fmt.Println(r.Form) fmt.Println(r.PostForm) fmt.Println("path", r.URL.Path) fmt.Println("scheme", r.URL.Scheme) fmt.Println(r.Form["url_long"]) if r.Method == "POST"{ fmt.Println("username : ", r.Form["username"]) fmt.Println("password : ", r.Form["password"]) } } func main() { http.HandleFunc("/", index) //设置访问的路由 http.HandleFunc("/login", login) //设置访问的路由 err := http.ListenAndServe(":9090", nil) //设置监听的端口 if err != nil{ log.Fatal("ListenAndServe : ", err) } }
调用http://localhost:9090/后,浏览器返回:
终端返回: userdeMBP:go-learning user$ go run test.go map[] map[] path / scheme [] 浏览器访问http://localhost:9090/login后终端变成: userdeMBP:go-learning user$ go run test.go map[] map[] path / scheme [] method POST map[username:[hello] password:[world]] map[username:[hello] password:[world]] path /login scheme [] username : [hello] password : [world] r.Form里面包含所有请求的参数,比如URL中query-string、POST的数据、PUT的数据 当你URL的query-string字段和POST的字段冲突时,该值会被保存成一个slice存储在一起 比如把index函数中html值中的action改成http://localhost:9090/login?username=allen,如下: <form action="http://localhost:9090/login?username=allen" method="post"> 此时的终端为: method POST map[password:[world] username:[hello allen]] map[password:[world] username:[hello]] path /login scheme [] username : [hello allen] password : [world] 可见r.PostForm中不会存放URL中query-string的数据
2)对表单的输入进行验证 因为不能够信任任何用户的输入,因此我们需要对用户的输入进行有效性验证 主要有两方面的数据验证:
1》必填字段 确保从表单元素中能够得到一个值,如上面例子中的username字段,使用len()获取字符串长度: if len(r.Form["username"][0]) == 0{ //如果为0则说明该表单元素中没有值,即为空时要做出什么处理 }
2》数字 确保从表单获取的是数字,比如年龄 getInt, err := strconv.Atoi(r.Form.Get("age")) if err != nil { //这就说明数字转化出错了,即输入的可能不是数字,这里进行错误的操作 } //如果确定是数字则继续进行下面的操作 if getInt > 100{ //判断年龄的大小范围的问题 } 还有另一种方法就是使用正则表达式: if m, _ := regexp.MatchString("^[0-9]+$", r.Form.Get("age")); !m{ //如果没有匹配项,则!m为true,说明输入的不是数字 return false }
3》中文 保证从表单中获取的是中文,使用正则表达式 if m, _ := regexp.MatchString("^[\\x{4e00}-\\x{9fa5}]+$", r.Form.Get("realname")); !m{ //如果没有匹配项,则!m为true,说明输入的不是中文 return false }
4》英文 保证从表单中获取的是英文,使用正则表达式 if m, _ := regexp.MatchString("^[a-zA-Z]+$", r.Form.Get("englishname")); !m{ //如果没有匹配项,则!m为true,说明输入的不是英文 return false }
5》电子邮件 查看用户输入的电子邮件是否正确 if m, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,}).([a-z]{2,4})$`, r.Form.Get("email")); !m{ //如果没有匹配项,则!m为true,说明输入邮箱格式不对 return false }
6》手机号码 if m, _ := regexp.MatchString(`^(1[3|4|5|8][0-9]\d{4,8})$`, r.Form.Get("mobile")); !m{ //如果没有匹配项,则!m为true,说明输入电话号码格式不对 return false }
7》下拉菜单 判断表单的<select>元素生成的下拉菜单中被选中项目是否正确,因为有时黑客会伪造下拉菜单中不存在的值发送给你,比如下拉菜单为: <select name"fruit"> <option value="apple">apple</option> <option value="pear">pear</option> <option value="banana">banana</option> </select> 验证方法为: slice := []string{"apple", "pear", "banana"} for _, v := range slice{ if v == r.Form.Get("fruit"){ return true } } return false
8》单选按钮 单选按钮<radio>中只有男=1,女=2两个选项,如何防止传入的值为3等错误值,单选按钮为: <input type="radio" name="gender" value="1">男 <input type="radio" name="gender" value="2">女 验证方法: slice := []int {1,2} for _, v := range slice{ if v == r.Form.Get("gender"){ return true } } return false
9》复选框 选定用户选中的都是你提供的值,不同之处在于接受到的数据是一个slice <input type="checkbox" name="interest" value="football">足球 <input type="checkbox" name="interest" value="basketball">篮球 <input type="checkbox" name="interest" value="tennis">网球 验证: slice := []string{"football", "basketball", "tennis"} a := Slice_diff(r.Form["interest"], slice) if a == nil{//说明接收到的数据中的值都来自slice return true } return false
10》时间和日期 使用time处理包
11》身份证号 //验证15位身份证,15位都是数字 if m, _ := regexp.MatchString(`^(\d{15})$`, r.Form.Get("usercard")); !m{ //如果没有匹配项,则!m为true,说明输入身份证格式不对 return false } //验证18位身份证,前17位都是数字,最后一位是校验码,可能是数字和X if m, _ := regexp.MatchString(`^(\d{17})([0-9]|X)$`, r.Form.Get("usercard")); !m{ //如果没有匹配项,则!m为true,说明输入身份证格式不对 return false }
3)预防跨站脚本 因为现在的网站含有大量动态内容以提高用户体验,动态站点会受到名为“跨站脚本攻击”(即XSS)的威胁,静态站点则不受影响 攻击者会在有漏洞的程序中插入攻击的JavaScript、VBScript、ActiveX或Flash来欺骗用户在这上面进行操作来盗取用户的账户信息、修改用户设置、盗取/污染cookie和植入恶意广告等。 两种防护方法:
该适当的处理使用的是html/template中的函数进行转义: HTMLEscapefunc HTMLEscape(w io.Writer, b []byte)
函数向w中写入b的HTML转义等价表示。 HTMLEscapeStringfunc HTMLEscapeString(s string) string 返回s的HTML转义等价表示字符串。 HTMLEscaperfunc HTMLEscaper(args ...interface{}) string 函数返回其所有参数文本表示的HTML转义等价表示字符串。 Template类型是text/template包的Template类型的特化版本,用于生成安全的HTML文本片段。 Newfunc New(name string) *Template
创建一个名为name的模板。 Parsefunc (t *Template) Parse(src string) (*Template, error)
Parse方法将字符串text解析为模板。嵌套定义的模板会关联到最顶层的t。Parse可以多次调用,但只有第一次调用可以包含空格、注释和模板定义之外的文本。如果后面的调用在解析后仍剩余文本会引发错误、返回nil且丢弃剩余文本;如果解析得到的模板已有相关联的同名模板,会覆盖掉原模板。 ExecuteTemplatefunc (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error ExecuteTemplate方法类似Execute,但是使用名为name的t关联的模板产生输出。 因为HTTP是一种无状态的协议,那么要如何判别是否为同一个用户。一般是使用cookie(cookie是存储在客户端的信息,能够每次通过header和服务器进行交互) 更详细的内容可见go标准库的学习-text/template 举例: package main import( "fmt" "net/http" "log" "html/template" ) func index(w http.ResponseWriter, r *http.Request){ r.ParseForm() //解析URL传递的参数,对于POST则解析响应包的主体(request body),如果不调用它则无法获取表单的数据 fmt.Println(r.Form) fmt.Println(r.PostForm) fmt.Println("path", r.URL.Path) fmt.Println("scheme", r.URL.Scheme) fmt.Println(r.Form["url_long"]) //如果使用的是方法FormValue()方法(它只会返回同名参数slice中的第一个,不存在则返回空字符串),则可以不用调用上面的ParseForm()方法 for k, v := range r.Form{ fmt.Println("key :", k) fmt.Println("value :", v) } fmt.Fprintf(w, "hello world") //将html写到w中,w中的内容将会输出到客户端中 } func login(w http.ResponseWriter, r *http.Request){ fmt.Println("method", r.Method) //获得请求的方法 r.ParseForm() if r.Method == "GET"{ // html := `<html> <head> <title></title> </head> <body> <form action="http://localhost:9090/login" method="post"> username: <input type="text" name="username"> password: <input type="text" name="password"> <input type="submit" value="login"> </form> </body> </html>` t := template.Must(template.New("test").Parse(html)) t.Execute(w, nil) }else{ fmt.Println("username : ", template.HTMLEscapeString(r.Form.Get("username")))//在终端即客户端输出 fmt.Println("password : ", template.HTMLEscapeString(r.Form.Get("password")))//把r.Form.Get("password")转义之后返回字符串 template.HTMLEscape(w, []byte(r.Form.Get("username"))) //在客户端输出,把r.Form.Get("username")转义后写到w } } func main() { http.HandleFunc("/", index) //设置访问的路由 http.HandleFunc("/login", login) //设置访问的路由 err := http.ListenAndServe(":9090", nil) //设置监听的端口 if err != nil{ log.Fatal("ListenAndServe : ", err) } } 访问http://localhost:9090/ 访问http://localhost:9090/login 如果仅传入字符串: 服务端返回: method POST username : hello password : allen map[] map[] path /favicon.ico scheme [] 客户端:
当时如果username输入的是<script>alert()</script> 客户端返回: 可见html/template包默认帮你过滤了html标签
如果你想要内容不被转义,方法有: 1》使用text/template import ( "text/template" "os" ) ... t, err := template.New("test").Parse(`{{define "T"}} Hello, {{.}}!{{end}}`) err := template.ExecuteTemplate(os.Stdout, "T", "<script>alert('you have benn pwned')</script>") 2》使用html/template,和template.HTML import ( "html/template" "os" ) ... t, err := template.New("test").Parse(`{{define "T"}} Hello, {{.}}!{{end}}`) err := template.ExecuteTemplate(os.Stdout, "T", template.HTML("<script>alert('you have benn pwned')</script>"))
4)防止多次递交表单 解决办法是在表单中添加一个带有唯一值的隐藏字段。在验证表单时,先检查带有该唯一值的表单是否已经提交过,如果是,则拒绝再次提交;如果不是,则处理表单进行逻辑处理。 如果使用的是Ajax模式递交表单的话,当表单递交后,通过javascript来禁用表单的递交按钮 比如我们能够使用MD5(时间戳)来获取唯一值,如time.Now().Unix() 举例: package main import( "fmt" "net/http" "log" "text/template" "crypto/md5" "time" "io" "strconv" ) func index(w http.ResponseWriter, r *http.Request){ r.ParseForm() //解析URL传递的参数,对于POST则解析响应包的主体(request body),如果不调用它则无法获取表单的数据 fmt.Println(r.Form) fmt.Println(r.PostForm) fmt.Println("path", r.URL.Path) fmt.Println("scheme", r.URL.Scheme) fmt.Println(r.Form["url_long"]) //如果使用的是方法FormValue()方法(它只会返回同名参数slice中的第一个,不存在则返回空字符串),则可以不用调用上面的ParseForm()方法 for k, v := range r.Form{ fmt.Println("key :", k) fmt.Println("value :", v) } fmt.Fprintf(w, "hello world") //将html写到w中,w中的内容将会输出到客户端中 } func login(w http.ResponseWriter, r *http.Request){ fmt.Println("method", r.Method) //获得请求的方法 if r.Method == "GET"{ // html := `<html> <head> <title></title> </head> <body> <form action="http://localhost:9090/login" method="post"> username: <input type="text" name="username"> password: <input type="text" name="password"> <input type="hidden" name="token" value="{{.}}"> <input type="submit" value="login"> </form> </body> </html>` crutime := time.Now().Unix() h := md5.New() io.WriteString(h, strconv.FormatInt(crutime, 10)) token := fmt.Sprintf("%x", h.Sum(nil)) t := template.Must(template.New("test").Parse(html)) t.Execute(w, token) }else{ r.ParseForm() token := r.Form.Get("token") if token != ""{ //验证token的合法性 }else{ //如果不存在token,则报错 log.Fatal("not token") } 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"))) //在客户端输出 } } func main() { http.HandleFunc("/", index) //设置访问的路由 http.HandleFunc("/login", login) //设置访问的路由 err := http.ListenAndServe(":9090", nil) //设置监听的端口 if err != nil{ log.Fatal("ListenAndServe : ", err) } } 浏览器中访问http://localhost:9090/login 可见得到的token时间戳为:"7cf962884609e3810259654d1e766754" 该方案可以防止非恶意的攻击,并能使恶意用户暂时不知所措。但是它不能够排除所有的欺骗性的动机,对此类情况还需要更加复杂的工作
5)处理文件上传——大文件 要使得表单能够上传文件,首先就是要添加form的encrype属性,该属性有三种情况:
举例: 通过表单上传文件,在服务器端处理文件 package main import( "fmt" "net/http" "log" "text/template" "crypto/md5" "time" "io" "strconv" "os" ) func index(w http.ResponseWriter, r |
请发表评论