用 Go 创建网站
您还记得第四章(组合类型的使用)中的 keyValue.go
应用,和第八章(Go UNIX系统编程)中的 kvSaveLoad.go
吗?在这节,您将学习使用 Go 标准库给它们创建 web 接口。这个新的 Go 源代码文件命名为 kvWeb.go
,并分为6部分展示。
这个 kvWeb.go
和之前的 www.go
的第一个不同是,kvWeb.go
使用 http.NewServeMux
来处理 HTTP 请求,因为对于稍复杂点的 web 应用来说,它有更多的功能。
kvWeb.go
的第一部分如下:
package main
import (
"encoding/gob"
"fmt"
"html/template"
"net/http"
"os"
)
type myElement struct {
Name string
Surname string
Id string
}
var DATA = make(map[string]myElement)
var DATAFILE = "/tmp/dataFile.gob"
在第八章(Go UNIX系统编程)中,您已经在 kvSaveLoad.go
见过上面的代码了。
kvWeb.go
的第二部分代码如下:
func save() error {
fmt.Println("Saving", DATAFILE)
err := os.Remove(DATAFILE)
if err != nil {
fmt.Println(err)
}
saveTo, err := os.Create(DATAFILE)
if err != nil {
fmt.Println("Cannot create", DATAFILE)
return err
}
defer saveTo.Close()
encoder := gob.NewEncoder(saveTo)
err = encoder.Encode(DATA)
if err != nil {
fmt.Println("Cannot save to", DATAFILE)
return err
}
return nil
}
func load() error {
fmt.Println("Loading", DATAFILE)
loadFrom, err := os.Open(DATAFILE)
defer loadFrom.Close()
if err != nil {
fmt.Println("Empty key/value store!")
return err
}
decoder := gob.NewDecoder(loadFrom)
decoder.Decode(&DATA)
return nil
}
func ADD(k string, n myElement) bool {
if k == "" {
return false
}
if LOOKUP(k) == nil {
DATA[k] = n
return true
}
return false
}
func DELETE(k string) bool {
if LOOKUP(k) != nil {
delete(DATA, k)
return true
}
return false
}
func LOOKUP(k string) *myElement {
_, ok := DATA[k]
if ok {
n := DATA[k]
return &n
} else {
return nil
}
}
func CHANGE(k string, n myElement) bool {
DATA[k] = n
return true
}
func PRINT() {
for k, d := range DATA {
fmt.Println("key: %s value: %v\n", k, d)
}
}
您应该也熟悉上面这段代码,因为它曾第一次出现在第八章的 kvSaveLoad.go
中。
kvWeb.go
的第三部分如下:
func homePage(w http.ResponseWriter, r *http.Request) {
fmt.Println("Serving", r.Host, "for", r.URL.Path)
myT := template.Must(template.ParseGlob("home.gohtml"))
myT.ExecuteTemplate(w, "home.gohtml", nil)
}
func listAll(w http.ResponseWriter, r *http.Request) {
fmt.Println("Listing the contents of the KV store!")
fmt.Fprintf(w, "<a href=\"/\" style=\"margin-right: 20px;\">Home sweet home!</a>")
fmt.Fprintf(w, "<a href=\"/list\" style=\"margin-right: 20px;\">List all elements!</a>")
fmt.Fprintf(w, "<a href=\"/change\" style=\"margin-right: 20px;\">Change an elements!</a>")
fmt.Fprintf(w, "<a href=\"/insert\" style=\"margin-right: 20px;\">Insert an elements!</a>")
fmt.Fprintf(w, "<h1>The contents of the KV store are:</h1>")
fmt.Fprintf(w, "<ul>")
for k, v := range DATA {
fmt.Fprintf(w, "<li>")
fmt.Fprintf(w, "<strong>%s</strong> with value: %v\n", k, v)
fmt.Fprintf(w, "</li>")
}
fmt.Fprintf(w, "</ul>")
}
listAll()
函数没有使用任何 Go 模版来生成动态输出,而是使用 Go 动态生成的。您可以把这当作一个例外,因为 web 应用通常使用 HTML 模版和 html/templates
标准库比较好。
kvWeb.go
的第四部分包含如下代码:
func changeElement(w http.ResponseWriter, r *http.Request){
fmt.Println("Change an element of the KV store!")
tmpl := template.Must(template.ParseFiles("update.gohtml"))
if r.Method != http.MethodPost {
tmpl.Execute(w, nil)
return
}
key := r.FormValue("key")
n := myElement{
Name: r.FormValue("name"),
Surname: r.FormValue("surname"),
Id: r.FormValue("id"),
}
if !CHANGE(key, n) {
fmt.Println("Update operation failed!")
} else {
err := save()
if err != nil {
fmt.Println(err)
return
}
tmpl.Execute(w, struct {Struct bool}{true})
}
}
从上面这段代码,您能看到在 FormValue()
函数的帮助下怎么读取一个 HTML 表单中字段的值。这个 template.Must()
函数是一个帮助函数,用于确保提供的模版文件不包含错误。
kvWeb.go
的第五段代码如下:
func insertElement(w http.ResponseWriter, r *http.Request){
fmt.Println("Inserting an element to the KV store!")
tmpl := template.Must(template.ParseFiles("insert.gohtml"))
if r.Method != http.MethodPost {
tmpl.Execute(w, nil)
return
}
key := r.FormValue("key")
n := myElement {
Name: r.FormValue("name"),
Surname: r.FormValue("surname"),
Id: r.FormValue("id"),
}
if !ADD(key, n) {
fmt.Println("Add operation failed!")
} else {
err := save()
if err != nil {
fmt.Println(err)
return
}
tmpl.Execute(w, struct{Success bool}{true})
}
}
余下代码如下:
func main() {
err := load()
if err != nil {
fmt.Println(err)
}
PORT := ":8001"
arguments := os.Args
if len(arguments) == 1 {
fmt.Println("Using default port number: ", PORT)
} else {
PORT = ":" + arguments[1]
}
http.HandleFunc("/", homePage)
http.HandleFunc("/change", changeElement)
http.HandleFunc("/list", listAll)
http.HandleFunc("/insert", insertElement)
err = http.ListenAndServe(PORT, nil)
if err != nil {
fmt.Println(err)
}
}
这个 kvWeb.go
的 main()
函数要比第八章(Go UNIX系统编程)的 kvSaveLoad.go
的 main()
函数简单,因为这俩个程序有完全不同的设计。
现在看一下 gohtml
文件,这个工程使用的起始文件是如下的 home.gohtml
:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>A Key Value Store!</title>
</head>
<body>
<a href="/" style="margin-right: 20px;">Home sweet home!</a>
<a href="/list" style="margin-right: 20px;">List all elements!</a>
<a href="/change" style="margin-right: 20px;">Change an elements!</a>
<a href="/insert" style="margin-right: 20px;">Insert an elements!</a>
<h2>Welcome to the Go KV store!</h2>
</body>
</html>
这个 home.gohtml
文件是静态的,就是说它的内容没有改变。而,其他的 gohtml
文件是动态显示信息。
update.gohtml
的内容如下:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>A Key Value Store!</title>
</head>
<body>
<a href="/" style="margin-right: 20px;">Home sweet home!</a>
<a href="/list" style="margin-right: 20px;">List all elements!</a>
<a href="/change" style="margin-right: 20px;">Change an elements!</a>
<a href="/insert" style="margin-right: 20px;">Insert an elements!</a>
{{if .Success}} <h1>Element updated!<h1>{{else}}
<h1>Please fill in the fields:</h1>
<form method="POST">
<label>Key:</label><br/>
<input type="text" name="key"><br/>
<label>Name:</label><br/>
<input type="text" name="name"><br/>
<label>Surname:</label><br/>
<input type="text" name="surname"><br/>
<label>Id:</label><br/>
<input type="text" name="id"><br/>
<input type="submit">
</form>
{{end}}
</body>
</html>
上面是主要的 HTML 代码。它最有趣的部分是 if
声明,它定义了您应该看到表单还是 Element updated!
信息。
最后,insert.gohtml
的内容如下:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>A Key Value Store!</title>
</head>
<body>
<a href="/" style="margin-right: 20px;">Home sweet home!</a>
<a href="/list" style="margin-right: 20px;">List all elements!</a>
<a href="/change" style="margin-right: 20px;">Change an elements!</a>
<a href="/insert" style="margin-right: 20px;">Insert an elements!</a>
{{if .Success}}
<h1>Element inserted!</h1>
{{else}}
<h1>Please fill in the fields:</h1>
<form method="POST">
<label>Key:</label><br/>
<input type="text" name="key"><br/>
<label>Name:</label><br/>
<input type="text" name="name"><br/>
<label>Surname:</label><br/>
<input type="text" name="surname"><br/>
<label>Id:</label><br/>
<input type="text" name="id"><br/>
<input type="submit">
</form>
{{end}}
</body>
</html>
您能从 <title>
标签的内容看出,insert.gohtml
和 update.gohtml
是完全相同的!
在 Unix 命令行中执行 kvWeb.go
将产生如下输出:
$ go run kvWeb.go
Loading /tmp/dataFile.gob
Using default port number: :8001
Serving localhost:8001 for /
Serving localhost:8001 for /favicon.ico
Listing the contents of the KV store!
Serving localhost:8001 for /favicon.ico
Inserting an element to the KV store!
Serving localhost:8001 for /favicon.ico
Inserting an element to the KV store!
Add operation failed!
Serving localhost:8001 for /favicon.ico
Inserting an element to the KV store!
Serving localhost:8001 for /favicon.ico
Inserting an element to the KV store!
Saving /tmp/dataFile.gob
Serving localhost:8001 for /favicon.ico
Inserting an element to the KV store!
Serving localhost:8001 for /favicon.ico
Changing an element of the KV store!
Serving localhost:8001 for /vavicon.ico
另外,真正有趣的是您可以用 web 浏览器和 kvWeb.go
交互。这个网站的首页,定义在 home.gohtml
显示截屏如下:
下面的截屏显示的是 key-value 存储的内容:
下面的截屏显示的是使用 kvWeb.go
应用的 web 接口添加新的数据到 key-value 存储中。
下面的截屏显示的是使用 kvWeb.go
应用的 web 接口更新已存在 key 数据的值。
这个 kvWeb.go
web 应用还不够完美,所以把它作为一个练习尽可能完善它。
这节说明了您如何使用 Go 开放整个网址和 web 应用。尽管您的需求无疑是多样的,但是和
kvWeb.go
使用的技术是相同的。注意,自己做的网站被认为要比那些由流行的管理系统创建的更安全。