您还记得第四章 (组合类型的使用)中的 keyValue.go
应用,和第八章 (Go UNIX系统编程)中的 kvSaveLoad.go
吗?在这节,您将学习使用 Go 标准库给它们创建 web 接口。这个新的 Go 源代码文件命名为 kvWeb.go
,并分为6部分展示。
这个 kvWeb.go
和之前的 www.go
的第一个不同是,kvWeb.go
使用 http.NewServeMux
来处理 HTTP 请求,因为对于稍复杂点的 web 应用来说,它有更多的功能。
kvWeb.go
的第一部分如下:
Copy 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
的第二部分代码如下:
Copy 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
的第三部分如下:
Copy 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
的第四部分包含如下代码:
Copy 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
的第五段代码如下:
Copy 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 })
}
}
余下代码如下:
Copy 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
:
Copy <!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
的内容如下:
Copy <!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
的内容如下:
Copy <!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
将产生如下输出:
Copy $ 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
使用的技术是相同的。注意,自己做的网站被认为要比那些由流行的管理系统创建的更安全。