# 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
	}
}
1
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
}
1
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")))
1
处理函数 调用 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
	}
}
1
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)
1