为进一步深入理解 http 包以及如何构建网页服务器功能,让我们来学习和体会下面的例子:先列出代码,然后给出不同功能的实现方法,程序输出显示在表格中。
示例 15.20 elaborated_webserver.go
package mainimport ("bytes""expvar""flag""fmt""io""log""net/http""os""strconv")// hello world, the web servervar helloRequests = expvar.NewInt("hello-requests")// flags:var webroot = flag.String("root", "/home/user", "web root directory")// simple flag servervar 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 channeltype Chan chan intfunc 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 filesystemhttp.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 nctr.n++case "POST": // set n to posted valuebuf := 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 = nfmt.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 serverfunc 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 outputfunc 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}}
| 处理函数 | 调用 URL | 浏览器获得响应 | | —- | —- | —- |
| Logger | http://localhost:12345/ (根) | oops |
Logger 处理函数用 w.WriteHeader(404) 来输出 “404 Not Found”头部。
此项技术通常很有用,无论何时服务器执行代码产生错误,都可以应用类似这样的代码:
if err != nil {w.WriteHeader(400)return}
另外利用 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}}
| 处理函数 | 调用 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)
链接
- 目录
- 上一节:探索 template 包
- 下一节:用 rpc 实现远程过程调用
