Go Web开发笔记

整理Go Web开发的相关知识点。

Go Web开发笔记

1 GoWiki:Go Web应用案例

GoWiki是一个极简的Go Web应用,使用Go语言内置的html/templatenet/http等库实现,实现基本的百科网站功能,包含词条创建、编辑、保存和浏览功能。

本节内容总结自官方教程Writing Web Applications

1.1 项目结构

gowiki

  • wiki.go
  • edit.html
  • view.html

1.2 代码实现

1.3.1 wiki.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// Writing Web Applications
// Official Example from https://golang.org/doc/articles/wiki/
package main

import (
"errors"
"fmt"
"html/template"
"io/ioutil"
"log"
"net/http"
"regexp"
)

type Page struct {
Title string
Body []byte
}

var templates = template.Must(template.ParseFiles("edit.html", "view.html"))
var 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: title, Body: body}, nil
}

func getTitle(w http.ResponseWriter, r *http.Request) (string, error) {
m := validPath.FindStringSubmatch(r.URL.Path)
if m == nil {
http.NotFound(w, r)
return "", errors.New("invalid Page Title")
}
return m[2], nil // The title is the second subexpression.
}

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 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 editHandler(w http.ResponseWriter, r *http.Request, title string) {
p, err := loadPage(title)
if err != nil {
p = &Page{Title: title}
}
renderTemplate(w, "edit", 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 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("/edit/", makeHandler(editHandler))
http.HandleFunc("/save/", makeHandler(saveHandler))
host := "127.0.0.1"
port := 8080
addr := fmt.Sprintf("%s:%v", host, port)
fmt.Printf("goWiki start listening at http://%s\n", addr)
log.Fatal(http.ListenAndServe(addr, nil))
}

1.3.2 edit.html

1
2
3
4
5
6
<h1>Editing {{.Title}}</h1>

<form action="/save/{{.Title}}" method="POST">
<div><textarea name="body" rows="20" cols="80">{{printf "%s" .Body}}</textarea></div>
<div><input type="submit" value="Save"></div>
</form>

1.3.3 view.html

1
2
3
4
5
<h1>{{.Title}}</h1>

<p>[<a href="/edit/{{.Title}}">edit</a>]</p>

<div>{{printf "%s" .Body}}</div>

1.3 运行说明

单文件go程序,通过以下命令即可运行:

1
go run wiki.go

或编译后再运行:

1
2
go build wiki.go
./wiki.go

2 Gin:Go Web框架

从上节可以看到,Go语言的net/httphtml/template已经足够实现基本的Web应用,但Go自带的路由http.ServerMux机制简单,只能实现从请求路径(string)到处理函数(handler)的映射,无法根据HTTP的方法(Method),请求头(header)进行路由。Go Web框架实现了比内置库更丰富的功能,例如Gin

Gin Web Framework

Gin is a web framework written in Go (Golang). It features a martini-like API with performance that is up to 40 times faster thanks to httprouter. If you need performance and good productivity, you will love Gin.

此外,还有其他Go Web框架,如:gorilla/muxecho

3 数据库存储

3.1 SQL

Go语言没有内置数据库驱动。

Go语言定义了database/sql接口,分离出接口实现与接口调用,使得调用方改换数据库时无需修改代码。

参阅:longjoy/micro-go-book/ch5-web/mysql/mysql.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
func init() {
db, err = sql.Open("mysql",
"root:a123456@tcp(47.96.140.41:3366)/user?charset=utf8")
checkErr(err)
}

func queryByName(name string) User {
user := User{}
stmt, err := db.Prepare("select * from user where name=?")
checkErr(err)

rows, _ := stmt.Query(name)

fmt.Println("\nafter deleting records: ")
for rows.Next() {
var id int
var name string
var habits string
var createdTime string
err = rows.Scan(&id, &name, &habits, &createdTime)
checkErr(err)
fmt.Printf("[%d, %s, %s, %s]\n", id, name, habits, createdTime)
user = User{id, name, habits, createdTime}
break
}
return user
}

func store(user User) {
//插入数据
stmt, err := db.Prepare("INSERT INTO user SET name=?,habits=?,created_time=?")
t := time.Now().UTC().Format("2006-01-02")
res, err := stmt.Exec(user.Name, user.Habits, t)
checkErr(err)

id, err := res.LastInsertId()
checkErr(err)

fmt.Printf("last insert id is: %d\n", id)
}

3.2 NoSQL

Go语言的结构体和NoSQL的JSON可以很好地直接对应起来,因此,Go语言中一般可以直接操作NoSQL,不依赖ORM。

参阅:longjoy/micro-go-book/ch5-web/mongo/mongo.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func connect(cName string) (*mgo.Session, *mgo.Collection) {
session, err := mgo.Dial("mongodb://47.96.140.41:27017/") //Mongodb's connection
checkErr(err)
session.SetMode(mgo.Monotonic, true)
//return a instantiated collect
return session, session.DB("test").C(cName)
}

func queryByName(name string) []User {
var user []User
s, c := connect("user")
defer s.Close()
err := c.Find(bson.M{"name": name}).All(&user)
checkErr(err)
return user
}

func store(user User) error {
s, c := connect("user")
defer s.Close()
user.Id = bson.NewObjectId().Hex()
return c.Insert(&user)
}

3.3 beego/orm:Go ORM框架

Beego

Beego is used for rapid development of enterprise application in Go, including RESTful APIs, web apps and backend services.

It is inspired by Tornado, Sinatra and Flask. beego has some Go-specific features such as interfaces and struct embedding.

Beego是一个简单易用的企业级Go应用开发框架,其中包含了ORM框架。

Beego的ORM的具体使用方法可以参阅其文档:

ORM 使用方法