2019独角兽企业重金招聘Python工程师标准>>>
作为资深Crud Boy,写web算是老本行了,学习了Go的基本语法后,用web练手应该是最佳选择了吧!
官网上给出了一个web的教程,一步步教你如何写出一个简单的web程序。我基本上根据这个教程,又一次熟悉了一下Go的语法,并实现了一个非常非常简单的WEB程序
定义数据结构
比较简单,一个Page结构体,只有标题和内容两个字段
type Page struct {
Title string
Body []byte
}
模拟实现数据持久化。
这部分也是简单的一个模拟,将Body部分的数据写入一个txt文件,仅此而已。
func (p *Page) save() error {
filename := p.Title + ".txt"
return ioutil.WriteFile(filename, p.Body, 0600)
}
实现请求的处理
这部分是这个教程的重点,从最开始简单的响应开始,一步步优化代码,熟悉了异常处理、验证、闭包等语法。
func viewHandler(w http.ResponseWriter, r *http.Request,title string) {
p, err := loadPage(title)
if err != nil {
http.Redirect(w, r, "/edit/"+title, http.StatusFound)
return
}
renderTemplate(w, "view", p)
}
func saveHandler(w http.ResponseWriter, r *http.Request,title string) {
body := r.FormValue("body")
p := Page{Title: title, Body: []byte(body)}
err := p.save()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/view/"+title, http.StatusFound)
}
func editHandler(w http.ResponseWriter, r *http.Request,title string) {
p, err := loadPage(title)
if err != nil {
p = &Page{Title: title}
}
renderTemplate(w, "edit", p)
}
这是最后优化后的三个处理函数。
其中最重要的一个优化是利用闭包,简化了三个处理函数中对于404错误的异常处理
func makeHandler(fn func(http.ResponseWriter,*http.Request,string)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
m := validPath.FindStringSubmatch(r.URL.Path)
if m == nil{
http.NotFound(w,r)
return
}
fn(w,r,m[2])
}
}
这个函数主要是利用了闭包的特性,理解起来还是比较困难的!Google说闭包是函数式编程的特性,好吧,我的C水平还没达到这么高深的地步=,=
makeHandler的参数是一个函数,就是上面所定义的三个处理函数,返回值也是一个函数,是http的handler函数,这个返回值会在绑定请求时用到。
闭包体现在返回函数中,使用了外部函数的变量fn。
完整的代码如下
package main
import (
"io/ioutil"
"net/http"
"html/template"
"regexp"
)
type Page struct {
Title string
Body []byte
}
var (
templates= template.Must(template.ParseFiles("webapps/edit.html", "webapps/view.html"))
validPath = regexp.MustCompile("^/(edit|save|view)/([a-zA-Z0-9]+)$")
)
func (p *Page) save() error {
filename := p.Title + ".txt"
return ioutil.WriteFile(filename, p.Body, 0600)
}
func loadPage(title string) (*Page, error) {
filename := title + ".txt"
body, err := ioutil.ReadFile(filename)
if (err != nil) {
return nil, err
}
return &Page{title, body}, nil
}
func viewHandler(w http.ResponseWriter, r *http.Request,title string) {
p, err := loadPage(title)
if err != nil {
http.Redirect(w, r, "/edit/"+title, http.StatusFound)
return
}
renderTemplate(w, "view", p)
}
func saveHandler(w http.ResponseWriter, r *http.Request,title string) {
body := r.FormValue("body")
p := Page{Title: title, Body: []byte(body)}
err := p.save()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/view/"+title, http.StatusFound)
}
func editHandler(w http.ResponseWriter, r *http.Request,title string) {
p, err := loadPage(title)
if err != nil {
p = &Page{Title: title}
}
renderTemplate(w, "edit", p)//tmpl不包含路径
}
func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
err := templates.ExecuteTemplate(w, tmpl+".html", p)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func makeHandler(fn func(http.ResponseWriter,*http.Request,string)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
m := validPath.FindStringSubmatch(r.URL.Path)
if m == nil{
http.NotFound(w,r)
return
}
fn(w,r,m[2])
}
}
func main() {
http.HandleFunc("/view/", makeHandler(viewHandler))
http.HandleFunc("/save/", makeHandler(saveHandler))
http.HandleFunc("/edit/", makeHandler(editHandler))
http.ListenAndServe(":8080", nil)
}
说几个过程中遇到的坑:
1、
templates.ExecuteTemplate(w, tmpl+".html", p)
这段代码中的第二个参数,在使用了templates后,居然不需要加模板文件见面的路径!
2、使用正则做校验时
validPath.FindStringSubmatch(r.URL.Path)
这个函数的返回值是一个[]string,刚开始以为每个元素都是一个匹配项。后来看到API对这个函数的介绍是
FindStringSubmatch returns a slice of strings holding the text of the leftmost match of the regular expression in s and the matches, if any, of its subexpressions, as defined by the 'Submatch' description in the package comment. A return value of nil indicates no match.
简单说,第一个元素是完全匹配项,后面每一个元素都是一个子匹配项。这个子匹配项还是第一次听说,看了一些文档,感觉go的正则将每一组()当成一个子匹配项。因此前面的例子中,最终返回m[2]才是第二个/后面的内容。