From e84480c03f7e84161d912abe96fdf7e5eefad66d Mon Sep 17 00:00:00 2001 From: marjune Date: Fri, 19 Jul 2019 11:56:39 +0800 Subject: [PATCH] add chapter 15.8 (#685) --- eBook/15.8.md | 243 ++++++++++++++++++ .../chapter_15/elaborated_webserver.go | 148 +++++++++++ 2 files changed, 391 insertions(+) create mode 100644 eBook/15.8.md create mode 100644 eBook/examples/chapter_15/elaborated_webserver.go diff --git a/eBook/15.8.md b/eBook/15.8.md new file mode 100644 index 0000000..8db17fa --- /dev/null +++ b/eBook/15.8.md @@ -0,0 +1,243 @@ +# 15.8 精巧的多功能网页服务器 + +为进一步深入理解 `http` 包以及如何构建网页服务器功能,让我们来学习和体会下面的例子:先列出代码,然后给出不同功能的实现方法,程序输出显示在表格中。 + +示例 15.20 [elaborated_webserver.go](examples/chapter_15/elaborated_webserver.go) + +```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 + } +} +``` + +处理函数 | 调用 URL | 浏览器获得响应 +--------|----------|--------------- +Logger | http://localhost:12345/ (根) | oops + +`Logger` 处理函数用 `w.WriteHeader(404)` 来输出 “404 Not Found”头部。 + +此项技术通常很有用,无论何时服务器执行代码产生错误,都可以应用类似这样的代码: +```go +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`,例如: +```go +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 秒后超时: +```go +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` 的写入。函数返回这两个文件和错误,如果有的话: +```go +func Pipe() (r *File, w *File, err error) +``` + +## 链接 + +- [目录](directory.md) +- 上一节:[探索 template 包](15.7.md) +- 下一节:[用 rpc 实现远程过程调用](15.9.md) diff --git a/eBook/examples/chapter_15/elaborated_webserver.go b/eBook/examples/chapter_15/elaborated_webserver.go new file mode 100644 index 0000000..0707f2c --- /dev/null +++ b/eBook/examples/chapter_15/elaborated_webserver.go @@ -0,0 +1,148 @@ +//Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +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 + } +}