• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    迪恩网络公众号

Goweb编程学习笔记——未完待续

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

1.

1).GOPATH设置

先设置自己的GOPATH,可以在本机中运行$PATH进行查看:

userdeMacBook-Pro:~ user$ $GOPATH
-bash: /Users/user/go: is a directory

在这可见我的GOPATH是/Users/user/go,并在该目录下生成如下作用的三个子目录:

  • src:存放源代码(比如.go .c .h .s等)
  • pkg:编译后生成的文件(比如.a)
  • bin:编译后生成的可执行文件(为了方便可将此目录加入到$PATH中,本机已添加)

 

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应用包,之后的编译安装方法有两种:

  • 一是进入对应的应用包目录,即mymath目录,然后运行go install
  • 二是在任意目录下执行go install 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 本质上可以分成两步:

  • 通过源码工具clone代码到src下面
  • 然后自动执行go install

 使用方法就是:

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)对表单的输入进行验证

因为不能够信任任何用户的输入,因此我们需要对用户的输入进行有效性验证

主要有两方面的数据验证:

  • 页面端的js验证(使用插件库,比如ValidationJS插件)
  • 服务器端的验证,这里讲的就是这种

1》必填字段

确保从表单元素中能够得到一个值,如上面例子中的username字段,使用len()获取字符串长度:

if len(r.Form["username"][0]) == 0{
    //如果为0则说明该表单元素中没有值,即为空时要做出什么处理
}
  • 当r.Form中表单元素的类型是空文本框、空文本区域以及文件上传,表单元素为空值
  • 如果类型是未选中的复选框和单选按钮,那么就不会在r.Form中产生相应的条目,用这种方法来验证会报错。所以需要使用r.Form.Get()来获取这类表单元素的值,这样当该字段不存在时会返回。但是这种方法只能获取单个值,如果是map的值,还是要使用上面的方法

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和植入恶意广告等。

两种防护方法:

  • 验证所有输入数据,即上面2)进行的操作
  • 对所有输出数据进行适当的处理,一防止任何已经注入的脚本在浏览器端运行,这里讲的是这种

该适当的处理使用的是html/template中的函数进行转义:

 HTMLEscape

func HTMLEscape(w io.Writer, b []byte)

函数向w中写入b的HTML转义等价表示。

 HTMLEscapeString

func HTMLEscapeString(s string) string

返回s的HTML转义等价表示字符串。

 HTMLEscaper

func HTMLEscaper(args ...interface{}) string

函数返回其所有参数文本表示的HTML转义等价表示字符串。

Template类型是text/template包的Template类型的特化版本,用于生成安全的HTML文本片段

 New

func New(name string) *Template

创建一个名为name的模板。

 Parse

func (t *Template) Parse(src string) (*Template, error)

Parse方法将字符串text解析为模板。嵌套定义的模板会关联到最顶层的t。Parse可以多次调用,但只有第一次调用可以包含空格、注释和模板定义之外的文本。如果后面的调用在解析后仍剩余文本会引发错误、返回nil且丢弃剩余文本;如果解析得到的模板已有相关联的同名模板,会覆盖掉原模板。

 ExecuteTemplate

func (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属性,该属性有三种情况:

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

举例:

 通过表单上传文件,在服务器端处理文件

package main 
import(
    "fmt"
    "net/http"
    "log"
    "text/template"
    "crypto/md5"
    "time"
    "io"
    "strconv"
    "os"
)

func index(w http.ResponseWriter, r 

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
Go语言_反射篇发布时间:2022-07-10
下一篇:
Web前端和后端之区分 - 路途遥远,Go~发布时间:2022-07-10
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap