forked from unknwon/the-way-to-go_ZH_CN
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e4c10d1
commit e84480c
Showing
2 changed files
with
391 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
} |