您的位置:首页 > 编程语言 > Go语言

Go学习笔记:写一个简单的web程序

2018-01-10 00:00 906 查看
作为资深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]才是第二个/后面的内容。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Go
相关文章推荐