15.8 精巧的多功能网页服务器

为进一步深入理解 http 包以及如何构建网页服务器功能,让我们来学习和体会下面的例子:先列出代码,然后给出不同功能的实现方法,程序输出显示在表格中。

示例 15.20 elaborated_webserver.go

  1. package main
  2. import (
  3. "bytes"
  4. "expvar"
  5. "flag"
  6. "fmt"
  7. "io"
  8. "log"
  9. "net/http"
  10. "os"
  11. "strconv"
  12. )
  13. // hello world, the web server
  14. var helloRequests = expvar.NewInt("hello-requests")
  15. // flags:
  16. var webroot = flag.String("root", "/home/user", "web root directory")
  17. // simple flag server
  18. var booleanflag = flag.Bool("boolean", true, "another flag for testing")
  19. // Simple counter server. POSTing to it will set the value.
  20. type Counter struct {
  21. n int
  22. }
  23. // a channel
  24. type Chan chan int
  25. func main() {
  26. flag.Parse()
  27. http.Handle("/", http.HandlerFunc(Logger))
  28. http.Handle("/go/hello", http.HandlerFunc(HelloServer))
  29. // The counter is published as a variable directly.
  30. ctr := new(Counter)
  31. expvar.Publish("counter", ctr)
  32. http.Handle("/counter", ctr)
  33. // http.Handle("/go/", http.FileServer(http.Dir("/tmp"))) // uses the OS filesystem
  34. http.Handle("/go/", http.StripPrefix("/go/", http.FileServer(http.Dir(*webroot))))
  35. http.Handle("/flags", http.HandlerFunc(FlagServer))
  36. http.Handle("/args", http.HandlerFunc(ArgServer))
  37. http.Handle("/chan", ChanCreate())
  38. http.Handle("/date", http.HandlerFunc(DateServer))
  39. err := http.ListenAndServe(":12345", nil)
  40. if err != nil {
  41. log.Panicln("ListenAndServe:", err)
  42. }
  43. }
  44. func Logger(w http.ResponseWriter, req *http.Request) {
  45. log.Print(req.URL.String())
  46. w.WriteHeader(404)
  47. w.Write([]byte("oops"))
  48. }
  49. func HelloServer(w http.ResponseWriter, req *http.Request) {
  50. helloRequests.Add(1)
  51. io.WriteString(w, "hello, world!\n")
  52. }
  53. // This makes Counter satisfy the expvar.Var interface, so we can export
  54. // it directly.
  55. func (ctr *Counter) String() string { return fmt.Sprintf("%d", ctr.n) }
  56. func (ctr *Counter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
  57. switch req.Method {
  58. case "GET": // increment n
  59. ctr.n++
  60. case "POST": // set n to posted value
  61. buf := new(bytes.Buffer)
  62. io.Copy(buf, req.Body)
  63. body := buf.String()
  64. if n, err := strconv.Atoi(body); err != nil {
  65. fmt.Fprintf(w, "bad POST: %v\nbody: [%v]\n", err, body)
  66. } else {
  67. ctr.n = n
  68. fmt.Fprint(w, "counter reset\n")
  69. }
  70. }
  71. fmt.Fprintf(w, "counter = %d\n", ctr.n)
  72. }
  73. func FlagServer(w http.ResponseWriter, req *http.Request) {
  74. w.Header().Set("Content-Type", "text/plain; charset=utf-8")
  75. fmt.Fprint(w, "Flags:\n")
  76. flag.VisitAll(func(f *flag.Flag) {
  77. if f.Value.String() != f.DefValue {
  78. fmt.Fprintf(w, "%s = %s [default = %s]\n", f.Name, f.Value.String(), f.DefValue)
  79. } else {
  80. fmt.Fprintf(w, "%s = %s\n", f.Name, f.Value.String())
  81. }
  82. })
  83. }
  84. // simple argument server
  85. func ArgServer(w http.ResponseWriter, req *http.Request) {
  86. for _, s := range os.Args {
  87. fmt.Fprint(w, s, " ")
  88. }
  89. }
  90. func ChanCreate() Chan {
  91. c := make(Chan)
  92. go func(c Chan) {
  93. for x := 0; ; x++ {
  94. c <- x
  95. }
  96. }(c)
  97. return c
  98. }
  99. func (ch Chan) ServeHTTP(w http.ResponseWriter, req *http.Request) {
  100. io.WriteString(w, fmt.Sprintf("channel send #%d\n", <-ch))
  101. }
  102. // exec a program, redirecting output
  103. func DateServer(rw http.ResponseWriter, req *http.Request) {
  104. rw.Header().Set("Content-Type", "text/plain; charset=utf-8")
  105. r, w, err := os.Pipe()
  106. if err != nil {
  107. fmt.Fprintf(rw, "pipe: %s\n", err)
  108. return
  109. }
  110. p, err := os.StartProcess("/bin/date", []string{"date"}, &os.ProcAttr{Files: []*os.File{nil, w, w}})
  111. defer r.Close()
  112. w.Close()
  113. if err != nil {
  114. fmt.Fprintf(rw, "fork/exec: %s\n", err)
  115. return
  116. }
  117. defer p.Release()
  118. io.Copy(rw, r)
  119. wait, err := p.Wait()
  120. if err != nil {
  121. fmt.Fprintf(rw, "wait: %s\n", err)
  122. return
  123. }
  124. if !wait.Exited() {
  125. fmt.Fprintf(rw, "date: %v\n", wait)
  126. return
  127. }
  128. }
处理函数 调用 URL 浏览器获得响应
Logger() http://localhost:12345/ (根) oops

Logger() 处理函数用 w.WriteHeader(404) 来输出 “404 Not Found”头部。

这项技术通常很有用,无论何时服务器执行代码产生错误,都可以应用类似这样的代码:

  1. if err != nil {
  2. w.WriteHeader(400)
  3. return
  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,例如:

  1. 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 秒后超时:

  1. func ChanResponse(w http.ResponseWriter, req *http.Request) {
  2. timeout := make (chan bool)
  3. go func () {
  4. time.Sleep(30e9)
  5. timeout <- true
  6. }()
  7. select {
  8. case msg := <-messages:
  9. io.WriteString(w, msg)
  10. case stop := <-timeout:
  11. return
  12. }
  13. }
处理函数 调用 URL 浏览器获得响应
DateServer() http://localhost:12345/date 显示当前时间(由于是调用 /bin/date,仅在 Unix 下有效)

可能的输出:Thu Sep 8 12:41:09 CEST 2011

os.Pipe() 返回一对相关联的 File,从 r 读取数据,返回已读取的字节数来自于 w 的写入。函数返回这两个文件和错误,如果有的话:

  1. func Pipe() (r *File, w *File, err error)

链接