golang学习笔记之四 - web服务器与表单处理

2023-09-05 16:11:09 浏览数 (2)

学习笔记 golang

Web服务器

我们看到上面的代码,要编写一个Web服务器很简单,只要调用http包的两个函数就可以了;如果你以前是PHP程序员,那你也许就会问,我们的nginx、apache服务器不需要吗?Go就是不需要这些,因为他直接就监听tcp端口了,做了nginx做的事情,然后sayhelloName这个其实就是我们写的逻辑函数了,跟php里面的控制层(controller)函数类似

代码语言:javascript复制
func httpTest(w http.ResponseWriter, r *http.Request) {
    /*
    fmt.Println(w)
    */

    /*
    fmt.Println(r)
    */

    // http://localhost:89/index?name=shyzhen&password=123456
    r.ParseForm()                           // 解析参数,默认是不会解析的
    fmt.Println(r.Form)                     // URL携带的参数 (必须执行上一步解析参数,不然是空map[]) `map[name:[shyzhen] password:[123456]]`
    fmt.Println("URL:", r.URL)              // 不包括host的url, `/index?name=shyzhen&password=123456`
    fmt.Println("URL.paht:", r.URL.Path)    // 当前路由  `/index`
    fmt.Println("URL.Scheme", r.URL.Scheme)
    fmt.Println(r.Form["name"][0])          // shyzhen

    fmt.Println("----------------")

    for k, v := range r.Form {
        fmt.Println(k, v)
    }

    fmt.Fprintln(w, "hello world")          // 这个写入到w的是输出到客户端的
}

func main() {
    http.HandleFunc("/index", httpTest)

    err := http.ListenAndServe(":9527", nil)
    if err != nil {
        log.Fatal("listenandserve:", err)
    }
}

通过server.go源码可以看到go语言在每个请求都使用了goroutines,保证每个请求独立,相互不会阻塞。

代码语言:javascript复制
func (srv *Server) Serve(l net.Listener) error {
        ...
        tempDelay = 0
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve(ctx)
        ...
}

web服务器案例

  • login.gtpl
代码语言:javascript复制
<html>
<head>
    <title></title>
</head>
<body>
<form action="/login" method="post">
    用户名:<input type="text" name="username">
    密码:  <input type="password" name="password">
    <input type="submit" value="登录">
</form>
</body>
</html>
  • login.go
代码语言:javascript复制
func hello(w http.ResponseWriter, r *http.Request)  {
    fmt.Fprintln(w, "首页hello")
}

func login(w http.ResponseWriter, r *http.Request)  {

    fmt.Println(r.Method)
    if r.Method == "GET" {                                    // 根据访问方法类型渲染不同界面和逻辑
        //fmt.Fprintln(w, "登录界面")                         // 引入模板的话,不能有输出

        t, _ := template.ParseFiles("./view/login.gtpl")      // ParseFiles引入模板文件,返回模板和错误信息
        err := t.Execute(w, nil)                              // 渲染模板
        log.Println("err:", err)                              // 在服务端打印日志

        //log.Println("err:", t.Execute(w, nil))              // 简洁写法
    } else {
        r.ParseForm()      // 解析参数,默认是不会解析的
        fmt.Println(r.Form["username"])
        fmt.Println(r.Form["password"])

        // 生成url参数
        / *
        v := url.Values{}
        v.Set("name", "Ava")
        v.Add("friend", "Jess")
        v.Add("friend", "Sarah")
        v.Add("friend", "Zoe")

        fmt.Println("v:", v)
        fmt.Println("get name :", v.Get("name"))
        fmt.Println("get friend:", v.Get("friend"))
        fmt.Println("friend", v["friend"])
        fmt.Println("v.Encode()", v.Encode())
        */
    }
}

func main()  {
    http.HandleFunc("/", hello)
    http.HandleFunc("/login", login)

    err := http.ListenAndServe(":9527", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

Validator

常见的验证规则:是否必填(len())、数字、长度、中英文、电子邮件、手机号码等:

代码语言:javascript复制
<html>
<head>
    <title></title>
</head>
<body>
<form action="/login" method="post">
    用户名:<input type="text" name="username">
    密码:  <input type="password" name="password">
    验证码:<input type="text" name="verifycode">
    爱好  :<input type="checkbox" name="like" value="1">1
    <input type="checkbox" name="like" value="2">2
    <input type="checkbox" name="like" value="3">3
    <input type="submit" value="登录">
</form>
</body>
</html>
代码语言:javascript复制
func login(w http.ResponseWriter, r *http.Request) {
    r.ParseForm()
    if r.Method == "GET" {
        t, _ := template.ParseFiles("./view/validator.gtpl")

        log.Println(t.Execute(w, nil))
    } else {
        r.ParseForm()
        //username := r.Form["username"][0]
        //password := r.Form["password"][0]
        //verifycode := r.Form["verifycode"][0]
        like := r.Form["like"]                     // checkBox必须用这种方式取值  (通过r.Form.Get()只能获取单个的值,如果是map的值,必须通过该方式来获取)

        username := r.Form.Get("username")
        password := r.Form.Get("password")
        verifycode := r.Form.Get("verifycode")
        //like := r.Form.Get("like")

        fmt.Println(username, password, verifycode, like)

        // 输入验证
        if len(username) == 0 {
            fmt.Println("用户名不能为空")
        }
        if len(password) < 6 {
            fmt.Println("密码必须大于等于6位字符")
        }
        if !isNumber(verifycode) {
            fmt.Println("验证码只能是数字")
        }
    }
}

// 数字
func isNumber(number1 string) bool {
    if m, _ := regexp.MatchString("^[0-9] $", number1); !m {
        return false
    }
    return true
}

// 中文
func isChinese(string1 string) bool {
    if m, _ := regexp.MatchString("^\p{Han} $", string1); !m {
        return false
    }
    return true
}

// 英文
func isEnglish(string1 string) bool {
    if m, _ := regexp.MatchString("^[a-zA-Z] $", string1); !m {
        return false
    }
    return true
}

// email
func isEmail(string1 string) bool {
    if m, _ := regexp.MatchString(`^([w._]{2,10})@(w{1,}).([a-z]{2,4})$`, string1); !m {
        return false
    }

    return true
}

// 手机号码
func isPhone(string1 string) bool {
    if m, _ := regexp.MatchString(`^(1[3|4|5|8][0-9]d{4,8})$`, string1); !m {
        return false
    }
    return true
}

// 身份证
func isIDCard(string1 string) bool {
    // 验证15位身份证,15位的是全部数字
    //if m, _ := regexp.MatchString(`^(d{15})$`, string1); !m {
    //    return false
    //}

    // 验证18位身份证,18位前17位为数字,最后一位是校验位,可能为数字或字符X。
    if m, _ := regexp.MatchString(`^(d{17})([0-9]|X)$`, string1); !m {
        return false
    }
    return true
}

func main() {
    http.HandleFunc("/", login)

    err := http.ListenAndServe(":9527", nil)
    if err != nil {
        log.Fatal("listen err" ,err)
    }
}
  • 下拉列表 select 如果我们想要判断表单里面<select>元素生成的下拉菜单中是否有被选中的项目。有些时候黑客可能会伪造这个下拉菜单不存在的值发送给你,那么如何判断这个值是否是我们预设的值呢?

我们的select可能是这样的一些元素

代码语言:javascript复制
<select name="fruit">
<option value="apple">apple</option>
<option value="pear">pear</option>
<option value="banana">banana</option>
</select>

那么我们可以这样验证,遍历所有的option值,查看是否有相等的

代码语言:javascript复制
slice:=[]string{"apple","pear","banana"}

v := r.Form.Get("fruit")
for _, item := range slice {
    if item == v {
        return true
    }
}

return false
  • 单选框 Radio 单选框的判断与select相似,要对传递过来的值进行鉴权
代码语言:javascript复制
<input type="radio" name="gender" value="1">男
<input type="radio" name="gender" value="2">女
代码语言:javascript复制
slice:=[]string{"1","2"}

for _, v := range slice {
    if v == r.Form.Get("gender") {
        return true
    }
}
return false
  • 复选框 CheckBox:
代码语言:javascript复制
<input type="checkbox" name="interest" value="football">足球
<input type="checkbox" name="interest" value="basketball">篮球
<input type="checkbox" name="interest" value="tennis">网球

对于复选框我们的验证和单选有点不一样,因为接收到的数据是一个slice,在上面的demo中可以看到,接收值的时候要用r.Form["name"]方式来接收

代码语言:javascript复制
slice:=[]string{"football","basketball","tennis"}
a:=Slice_diff(r.Form["interest"],slice)
if a == nil{
    return true
}

return false

防 XSS

攻击者通常会在有漏洞的程序中插入JavaScript、VBScript、 ActiveX或Flash以欺骗用户。一旦得手,他们可以盗取用户帐户信息,修改用户设置,盗取/污染cookie和植入恶意广告等。对XSS最佳的防护应该结合以下两种方法:一是验证过滤所有输入数据,有效检测攻击;另一个是对所有输出数据进行适当的处理,以防止任何已成功注入的脚本在浏览器端运行:

代码语言:javascript复制
fmt.Println(username)                               // <script>alert('you have been pwned')</script>
fmt.Println(template.HTMLEscapeString(username))    // 输出到服务器端&lt;script&gt;alert(&#39;you have been pwned&#39;)&lt;/script&gt;!
template.HTMLEscape(w, []byte(username))            // 输出到客户端 &lt;script&gt;alert(&#39;you have been pwned&#39;)&lt;/script&gt;!

文件上传

0 人点赞