Parcourir la source

add chapter 15.8 (#685)

marjune il y a 6 ans
Parent
commit
e84480c03f
2 fichiers modifiés avec 391 ajouts et 0 suppressions
  1. 243 0
      eBook/15.8.md
  2. 148 0
      eBook/examples/chapter_15/elaborated_webserver.go

+ 243 - 0
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)

+ 148 - 0
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
+	}
+}