用 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 的第一部分如下:

  1. package main
  2. import (
  3. "encoding/gob"
  4. "fmt"
  5. "html/template"
  6. "net/http"
  7. "os"
  8. )
  9. type myElement struct {
  10. Name string
  11. Surname string
  12. Id string
  13. }
  14. var DATA = make(map[string]myElement)
  15. var DATAFILE = "/tmp/dataFile.gob"

第八章(Go UNIX系统编程)中,您已经在 kvSaveLoad.go 见过上面的代码了。

kvWeb.go 的第二部分代码如下:

  1. func save() error {
  2. fmt.Println("Saving", DATAFILE)
  3. err := os.Remove(DATAFILE)
  4. if err != nil {
  5. fmt.Println(err)
  6. }
  7. saveTo, err := os.Create(DATAFILE)
  8. if err != nil {
  9. fmt.Println("Cannot create", DATAFILE)
  10. return err
  11. }
  12. defer saveTo.Close()
  13. encoder := gob.NewEncoder(saveTo)
  14. err = encoder.Encode(DATA)
  15. if err != nil {
  16. fmt.Println("Cannot save to", DATAFILE)
  17. return err
  18. }
  19. return nil
  20. }
  21. func load() error {
  22. fmt.Println("Loading", DATAFILE)
  23. loadFrom, err := os.Open(DATAFILE)
  24. defer loadFrom.Close()
  25. if err != nil {
  26. fmt.Println("Empty key/value store!")
  27. return err
  28. }
  29. decoder := gob.NewDecoder(loadFrom)
  30. decoder.Decode(&DATA)
  31. return nil
  32. }
  33. func ADD(k string, n myElement) bool {
  34. if k == "" {
  35. return false
  36. }
  37. if LOOKUP(k) == nil {
  38. DATA[k] = n
  39. return true
  40. }
  41. return false
  42. }
  43. func DELETE(k string) bool {
  44. if LOOKUP(k) != nil {
  45. delete(DATA, k)
  46. return true
  47. }
  48. return false
  49. }
  50. func LOOKUP(k string) *myElement {
  51. _, ok := DATA[k]
  52. if ok {
  53. n := DATA[k]
  54. return &n
  55. } else {
  56. return nil
  57. }
  58. }
  59. func CHANGE(k string, n myElement) bool {
  60. DATA[k] = n
  61. return true
  62. }
  63. func PRINT() {
  64. for k, d := range DATA {
  65. fmt.Println("key: %s value: %v\n", k, d)
  66. }
  67. }

您应该也熟悉上面这段代码,因为它曾第一次出现在第八章kvSaveLoad.go 中。

kvWeb.go 的第三部分如下:

  1. func homePage(w http.ResponseWriter, r *http.Request) {
  2. fmt.Println("Serving", r.Host, "for", r.URL.Path)
  3. myT := template.Must(template.ParseGlob("home.gohtml"))
  4. myT.ExecuteTemplate(w, "home.gohtml", nil)
  5. }
  6. func listAll(w http.ResponseWriter, r *http.Request) {
  7. fmt.Println("Listing the contents of the KV store!")
  8. fmt.Fprintf(w, "<a href=\"/\" style=\"margin-right: 20px;\">Home sweet home!</a>")
  9. fmt.Fprintf(w, "<a href=\"/list\" style=\"margin-right: 20px;\">List all elements!</a>")
  10. fmt.Fprintf(w, "<a href=\"/change\" style=\"margin-right: 20px;\">Change an elements!</a>")
  11. fmt.Fprintf(w, "<a href=\"/insert\" style=\"margin-right: 20px;\">Insert an elements!</a>")
  12. fmt.Fprintf(w, "<h1>The contents of the KV store are:</h1>")
  13. fmt.Fprintf(w, "<ul>")
  14. for k, v := range DATA {
  15. fmt.Fprintf(w, "<li>")
  16. fmt.Fprintf(w, "<strong>%s</strong> with value: %v\n", k, v)
  17. fmt.Fprintf(w, "</li>")
  18. }
  19. fmt.Fprintf(w, "</ul>")
  20. }

listAll() 函数没有使用任何 Go 模版来生成动态输出,而是使用 Go 动态生成的。您可以把这当作一个例外,因为 web 应用通常使用 HTML 模版和 html/templates标准库比较好。

kvWeb.go的第四部分包含如下代码:

  1. func changeElement(w http.ResponseWriter, r *http.Request){
  2. fmt.Println("Change an element of the KV store!")
  3. tmpl := template.Must(template.ParseFiles("update.gohtml"))
  4. if r.Method != http.MethodPost {
  5. tmpl.Execute(w, nil)
  6. return
  7. }
  8. key := r.FormValue("key")
  9. n := myElement{
  10. Name: r.FormValue("name"),
  11. Surname: r.FormValue("surname"),
  12. Id: r.FormValue("id"),
  13. }
  14. if !CHANGE(key, n) {
  15. fmt.Println("Update operation failed!")
  16. } else {
  17. err := save()
  18. if err != nil {
  19. fmt.Println(err)
  20. return
  21. }
  22. tmpl.Execute(w, struct {Struct bool}{true})
  23. }
  24. }

从上面这段代码,您能看到在 FormValue() 函数的帮助下怎么读取一个 HTML 表单中字段的值。这个 template.Must() 函数是一个帮助函数,用于确保提供的模版文件不包含错误。

kvWeb.go 的第五段代码如下:

  1. func insertElement(w http.ResponseWriter, r *http.Request){
  2. fmt.Println("Inserting an element to the KV store!")
  3. tmpl := template.Must(template.ParseFiles("insert.gohtml"))
  4. if r.Method != http.MethodPost {
  5. tmpl.Execute(w, nil)
  6. return
  7. }
  8. key := r.FormValue("key")
  9. n := myElement {
  10. Name: r.FormValue("name"),
  11. Surname: r.FormValue("surname"),
  12. Id: r.FormValue("id"),
  13. }
  14. if !ADD(key, n) {
  15. fmt.Println("Add operation failed!")
  16. } else {
  17. err := save()
  18. if err != nil {
  19. fmt.Println(err)
  20. return
  21. }
  22. tmpl.Execute(w, struct{Success bool}{true})
  23. }
  24. }

余下代码如下:

  1. func main() {
  2. err := load()
  3. if err != nil {
  4. fmt.Println(err)
  5. }
  6. PORT := ":8001"
  7. arguments := os.Args
  8. if len(arguments) == 1 {
  9. fmt.Println("Using default port number: ", PORT)
  10. } else {
  11. PORT = ":" + arguments[1]
  12. }
  13. http.HandleFunc("/", homePage)
  14. http.HandleFunc("/change", changeElement)
  15. http.HandleFunc("/list", listAll)
  16. http.HandleFunc("/insert", insertElement)
  17. err = http.ListenAndServe(PORT, nil)
  18. if err != nil {
  19. fmt.Println(err)
  20. }
  21. }

这个 kvWeb.gomain() 函数要比第八章(Go UNIX系统编程)的 kvSaveLoad.gomain() 函数简单,因为这俩个程序有完全不同的设计。

现在看一下 gohtml 文件,这个工程使用的起始文件是如下的 home.gohtml

  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>A Key Value Store!</title>
  6. </head>
  7. <body>
  8. <a href="/" style="margin-right: 20px;">Home sweet home!</a>
  9. <a href="/list" style="margin-right: 20px;">List all elements!</a>
  10. <a href="/change" style="margin-right: 20px;">Change an elements!</a>
  11. <a href="/insert" style="margin-right: 20px;">Insert an elements!</a>
  12. <h2>Welcome to the Go KV store!</h2>
  13. </body>
  14. </html>

这个 home.gohtml 文件是静态的,就是说它的内容没有改变。而,其他的 gohtml 文件是动态显示信息。

update.gohtml 的内容如下:

  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>A Key Value Store!</title>
  6. </head>
  7. <body>
  8. <a href="/" style="margin-right: 20px;">Home sweet home!</a>
  9. <a href="/list" style="margin-right: 20px;">List all elements!</a>
  10. <a href="/change" style="margin-right: 20px;">Change an elements!</a>
  11. <a href="/insert" style="margin-right: 20px;">Insert an elements!</a>
  12. {{if .Success}} <h1>Element updated!<h1>{{else}}
  13. <h1>Please fill in the fields:</h1>
  14. <form method="POST">
  15. <label>Key:</label><br/>
  16. <input type="text" name="key"><br/>
  17. <label>Name:</label><br/>
  18. <input type="text" name="name"><br/>
  19. <label>Surname:</label><br/>
  20. <input type="text" name="surname"><br/>
  21. <label>Id:</label><br/>
  22. <input type="text" name="id"><br/>
  23. <input type="submit">
  24. </form>
  25. {{end}}
  26. </body>
  27. </html>

上面是主要的 HTML 代码。它最有趣的部分是 if 声明,它定义了您应该看到表单还是 Element updated! 信息。

最后,insert.gohtml 的内容如下:

  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>A Key Value Store!</title>
  6. </head>
  7. <body>
  8. <a href="/" style="margin-right: 20px;">Home sweet home!</a>
  9. <a href="/list" style="margin-right: 20px;">List all elements!</a>
  10. <a href="/change" style="margin-right: 20px;">Change an elements!</a>
  11. <a href="/insert" style="margin-right: 20px;">Insert an elements!</a>
  12. {{if .Success}}
  13. <h1>Element inserted!</h1>
  14. {{else}}
  15. <h1>Please fill in the fields:</h1>
  16. <form method="POST">
  17. <label>Key:</label><br/>
  18. <input type="text" name="key"><br/>
  19. <label>Name:</label><br/>
  20. <input type="text" name="name"><br/>
  21. <label>Surname:</label><br/>
  22. <input type="text" name="surname"><br/>
  23. <label>Id:</label><br/>
  24. <input type="text" name="id"><br/>
  25. <input type="submit">
  26. </form>
  27. {{end}}
  28. </body>
  29. </html>

您能从 <title> 标签的内容看出,insert.gohtmlupdate.gohtml 是完全相同的!

在 Unix 命令行中执行 kvWeb.go 将产生如下输出:

  1. $ go run kvWeb.go
  2. Loading /tmp/dataFile.gob
  3. Using default port number: :8001
  4. Serving localhost:8001 for /
  5. Serving localhost:8001 for /favicon.ico
  6. Listing the contents of the KV store!
  7. Serving localhost:8001 for /favicon.ico
  8. Inserting an element to the KV store!
  9. Serving localhost:8001 for /favicon.ico
  10. Inserting an element to the KV store!
  11. Add operation failed!
  12. Serving localhost:8001 for /favicon.ico
  13. Inserting an element to the KV store!
  14. Serving localhost:8001 for /favicon.ico
  15. Inserting an element to the KV store!
  16. Saving /tmp/dataFile.gob
  17. Serving localhost:8001 for /favicon.ico
  18. Inserting an element to the KV store!
  19. Serving localhost:8001 for /favicon.ico
  20. Changing an element of the KV store!
  21. Serving localhost:8001 for /vavicon.ico

另外,真正有趣的是您可以用 web 浏览器和 kvWeb.go 交互。这个网站的首页,定义在 home.gohtml 显示截屏如下:

web 应用的静态首页

下面的截屏显示的是 key-value 存储的内容:

key-value 存储的内容

下面的截屏显示的是使用 kvWeb.go 应用的 web 接口添加新的数据到 key-value 存储中。

添加一个新数据到 key-value 存储中

下面的截屏显示的是使用 kvWeb.go 应用的 web 接口更新已存在 key 数据的值。

更新 key-value 存储中一个 key 的值

这个 kvWeb.go web 应用还不够完美,所以把它作为一个练习尽可能完善它。

这节说明了您如何使用 Go 开放整个网址和 web 应用。尽管您的需求无疑是多样的,但是和 kvWeb.go 使用的技术是相同的。注意,自己做的网站被认为要比那些由流行的管理系统创建的更安全。