# 15.8 精巧的多功能网页服务器
为进一步深入理解 http
包以及如何构建网页服务器功能,让我们来学习和体会下面的例子:先列出代码,然后给出不同功能的实现方法,程序输出显示在表格中。
示例 15.20 elaborated_webserver.go
package main
import (
"bytes"
"expvar"
"flag"
"fmt"
"io"
"log"
"net/http"
"os"
"strconv"
)
// hello world, the web server
var helloRequests = expvar.NewInt("hello-requests")
// flags:
var webroot = flag.String("root", "/home/user", "web root directory")
// simple flag server
var booleanflag = flag.Bool("boolean", true, "another flag for testing")
// Simple counter server. POSTing to it will set the value.
type Counter struct {
n int
}
// a channel
type Chan chan int
func main() {
flag.Parse()
http.Handle("/", http.HandlerFunc(Logger))
http.Handle("/go/hello", http.HandlerFunc(HelloServer))
// The counter is published as a variable directly.
ctr := new(Counter)
expvar.Publish("counter", ctr)
http.Handle("/counter", ctr)
// http.Handle("/go/", http.FileServer(http.Dir("/tmp"))) // uses the OS filesystem
http.Handle("/go/", http.StripPrefix("/go/", http.FileServer(http.Dir(*webroot))))
http.Handle("/flags", http.HandlerFunc(FlagServer))
http.Handle("/args", http.HandlerFunc(ArgServer))
http.Handle("/chan", ChanCreate())
http.Handle("/date", http.HandlerFunc(DateServer))
err := http.ListenAndServe(":12345", nil)
if err != nil {
log.Panicln("ListenAndServe:", err)
}
}
func Logger(w http.ResponseWriter, req *http.Request) {
log.Print(req.URL.String())
w.WriteHeader(404)
w.Write([]byte("oops"))
}
func HelloServer(w http.ResponseWriter, req *http.Request) {
helloRequests.Add(1)
io.WriteString(w, "hello, world!\n")
}
// This makes Counter satisfy the expvar.Var interface, so we can export
// it directly.
func (ctr *Counter) String() string { return fmt.Sprintf("%d", ctr.n) }
func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
switch req.Method {
case "GET": // increment n
ctr.n++
case "POST": // set n to posted value
buf := new(bytes.Buffer)
io.Copy(buf, req.Body)
body := buf.String()
if n, err := strconv.Atoi(body); err != nil {
fmt.Fprintf(w, "bad POST: %v\nbody: [%v]\n", err, body)
} else {
ctr.n = n
fmt.Fprint(w, "counter reset\n")
}
}
fmt.Fprintf(w, "counter = %d\n", ctr.n)
}
func FlagServer(w http.ResponseWriter, req *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
fmt.Fprint(w, "Flags:\n")
flag.VisitAll(func(f *flag.Flag) {
if f.Value.String() != f.DefValue {
fmt.Fprintf(w, "%s = %s [default = %s]\n", f.Name, f.Value.String(), f.DefValue)
} else {
fmt.Fprintf(w, "%s = %s\n", f.Name, f.Value.String())
}
})
}
// simple argument server
func ArgServer(w http.ResponseWriter, req *http.Request) {
for _, s := range os.Args {
fmt.Fprint(w, s, " ")
}
}
func ChanCreate() Chan {
c := make(Chan)
go func(c Chan) {
for x := 0; ; x++ {
c <- x
}
}(c)
return c
}
func (ch Chan) ServeHTTP(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, fmt.Sprintf("channel send #%d\n", <-ch))
}
// exec a program, redirecting output
func DateServer(rw http.ResponseWriter, req *http.Request) {
rw.Header().Set("Content-Type", "text/plain; charset=utf-8")
r, w, err := os.Pipe()
if err != nil {
fmt.Fprintf(rw, "pipe: %s\n", err)
return
}
p, err := os.StartProcess("/bin/date", []string{"date"}, &os.ProcAttr{Files: []*os.File{nil, w, w}})
defer r.Close()
w.Close()
if err != nil {
fmt.Fprintf(rw, "fork/exec: %s\n", err)
return
}
defer p.Release()
io.Copy(rw, r)
wait, err := p.Wait()
if err != nil {
fmt.Fprintf(rw, "wait: %s\n", err)
return
}
if !wait.Exited() {
fmt.Fprintf(rw, "date: %v\n", wait)
return
}
}
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
处理函数 | 调用 URL | 浏览器获得响应 |
---|---|---|
Logger | http://localhost:12345/ (根) | oops |
Logger
处理函数用 w.WriteHeader(404)
来输出 “404 Not Found”头部。
此项技术通常很有用,无论何时服务器执行代码产生错误,都可以应用类似这样的代码:
if err != nil {
w.WriteHeader(400)
return
}
2
3
4
另外利用 logger
包的函数,针对每个请求在服务器端命令行打印日期、时间和 URL。
处理函数 | 调用 URL | 浏览器获得响应 |
---|---|---|
HelloServer | http://localhost:12345/go/hello | hello, world! |
包 expvar
可以创建(Int,Float 和 String 类型)变量,并将它们发布为公共变量。它会在 HTTP URL /debug/vars
上以 JSON 格式公布。通常它被用于服务器操作计数。helloRequests
就是这样一个 int64
变量,该处理函数对其加 1,然后写入“hello world!”到浏览器。
处理函数 | 调用 URL | 浏览器获得响应 |
---|---|---|
Counter | http://localhost:12345/counter | counter = 1 |
Counter | 刷新(GET 请求) | counter = 2 |
计数器对象 ctr
有一个 String()
方法,所以它实现了 expvar.Var
接口。这使其可以被发布,尽管它是一个结构体。ServeHTTP
函数使 ctr
成为处理器,因为它的签名正确实现了 http.Handler
接口。
处理函数 | 调用 URL | 浏览器获得响应 |
---|---|---|
FileServer | http://localhost:12345/go/ggg.html | 404 page not found |
FileServer(root FileSystem) Handler
返回一个处理器,它以 root
作为根,用文件系统的内容响应 HTTP 请求。要获得操作系统的文件系统,用 http.Dir
,例如:
http.Handle("/go/", http.FileServer(http.Dir("/tmp")))
处理函数 | 调用 URL | 浏览器获得响应 |
---|---|---|
FlagServer | http://localhost:12345/flags | Flags: boolean = true root = /home/rsc |
该处理函数使用了 flag
包。VisitAll
函数迭代所有的标签(flag),打印它们的名称、值和默认值(当不同于“值”时)。
处理函数 | 调用 URL | 浏览器获得响应 |
---|---|---|
ArgServer | http://localhost:12345/args | ./elaborated_webserver.exe |
该处理函数迭代 os.Args
以打印出所有的命令行参数。如果没有指定则只有程序名称(可执行程序的路径)会被打印出来。
处理函数 | 调用 URL | 浏览器获得响应 |
---|---|---|
Channel | http://localhost:12345/chan | channel send #1 |
Channel | 刷新 | channel send #2 |
每当有新请求到达,通道的 ServeHTTP
方法从通道获取下一个整数并显示。由此可见,网页服务器可以从通道中获取要发送的响应,它可以由另一个函数产生(甚至是客户端)。下面的代码片段正是一个这样的处理函数,但会在 30 秒后超时:
func ChanResponse(w http.ResponseWriter, req *http.Request) {
timeout := make (chan bool)
go func () {
time.Sleep(30e9)
timeout <- true
}()
select {
case msg := <-messages:
io.WriteString(w, msg)
case stop := <-timeout:
return
}
}
2
3
4
5
6
7
8
9
10
11
12
13
处理函数 | 调用 URL | 浏览器获得响应 |
---|---|---|
DateServer | http://localhost:12345/date | 显示当前时间(由于是调用 /bin/date,仅在 Unix 下有效) |
可能的输出:Thu Sep 8 12:41:09 CEST 2011
。
os.Pipe()
返回一对相关联的 File
,从 r
读取数据,返回已读取的字节数来自于 w
的写入。函数返回这两个文件和错误,如果有的话:
func Pipe() (r *File, w *File, err error)