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

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

作为资深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]才是第二个/后面的内容。

转载于:https://my.oschina.net/coderzhoutf/blog/1604996

你可能感兴趣的:(Go学习笔记:写一个简单的web程序)