12.7.2 用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.gomain() 函数要比第八章(Go UNIX系统编程)的 kvSaveLoad.gomain() 函数简单,因为这俩个程序有完全不同的设计。

现在看一下 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.gohtmlupdate.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 使用的技术是相同的。注意,自己做的网站被认为要比那些由流行的管理系统创建的更安全。

Last updated