第十五章修改 (#834)

Co-authored-by: Joe Chen <jc@unknwon.io>
This commit is contained in:
Haigang Zhou
2022-05-17 16:33:20 +08:00
committed by GitHub
parent 60fe3dd076
commit 72f2eccbc5
12 changed files with 102 additions and 92 deletions

View File

@@ -1,12 +1,12 @@
# 15.2 一个简单的 web 服务器
http 是比 tcp 更高层的协议它描述了网页服务器如何与客户端浏览器进行通信。Go 提供了 `net/http`我们马上就来看下。先从一些简单的示例开始首先编写一个“Hello world!”网页服务器:[查看示例15.6](examples/chapter_15/hello_world_webserver.go)
http 是比 tcp 更高层的协议它描述了网页服务器如何与客户端浏览器进行通信。Go 提供了 `net/http`我们马上就来看下。先从一些简单的示例开始首先编写一个“Hello world!”网页服务器:[查看示例 15.6](examples/chapter_15/hello_world_webserver.go)
我们引入了 `http` 包并启动了网页服务器,和 [15.1节](15.1.md) `net.Listen("tcp", "localhost:50000")` 函数的 tcp 服务器是类似的,使用 `http.ListenAndServe("localhost:8080", nil)` 函数,如果成功会返回空,否则会返回一个错误(地址 localhost 部分可以省略8080 是指定的端口号)。
我们引入了 `http` 包并启动了网页服务器,和 [15.1 ](15.1.md)的 `net.Listen("tcp", "localhost:50000")` 函数的 tcp 服务器是类似的,使用 `http.ListenAndServe("localhost:8080", nil)` 函数,如果成功会返回空,否则会返回一个错误(地址 `localhost` 部分可以省略,`8080` 是指定的端口号)。
`http.URL` 用于表示网页地址,其中字符串属性 `Path` 用于保存 url 的路径;`http.Request` 描述了客户端请求,内含一个 `URL` 字段。
如果 `req` 是来自 html 表单的 POST 类型请求“var1” 是该表单中一个输入域的名称,那么用户输入的值就可以通过 Go 代码 `req.FormValue("var1")` 获取到(见 [15.4节](15.4.md))。还有一种方法是先执行 `request.ParseForm()`,然后再获取 `request.Form["var1"]` 的第一个返回参数,就像这样:
如果 `req` 是来自 html 表单的 POST 类型请求,`“var1”` 是该表单中一个输入域的名称,那么用户输入的值就可以通过 Go 代码 `req.FormValue("var1")` 获取到(见 [15.4 ](15.4.md))。还有一种方法是先执行 `request.ParseForm()`,然后再获取 `request.Form["var1"]` 的第一个返回参数,就像这样:
```go
var1, found := request.Form["var1"]
```
@@ -14,11 +14,11 @@ var1, found := request.Form["var1"]
表单属性实际上是 `map[string][]string` 类型。网页服务器发送一个 `http.Response` 响应,它是通过 `http.ResponseWriter` 对象输出的,后者组装了 HTTP 服务器响应,通过对其写入内容,我们就将数据发送给了 HTTP 客户端。
现在我们仍然要编写程序,以实现服务器必须做的事,即如何处理请求。这是通过 `http.HandleFunc` 函数完成的。在这个例子中,当根路径“/”url地址是 `http://localhost:8080`)被请求的时候(或者这个服务器上的其他任意地址),`HelloServer` 函数就被执行了。这个函数是 `http.HandlerFunc` 类型的,它们通常被命名为 Prefhandler和某个路径前缀 Pref 匹配。
现在我们仍然要编写程序,以实现服务器必须做的事,即如何处理请求。这是通过 `http.HandleFunc()` 函数完成的。在这个例子中,当根路径“/”url 地址是 `http://localhost:8080`)被请求的时候(或者这个服务器上的其他任意地址),`HelloServer()` 函数就被执行了。这个函数是 `http.HandlerFunc` 类型的,它们通常被命名为 Prefhandler和某个路径前缀 Pref 匹配。
`http.HandleFunc` 注册了一个处理函数(这里是 `HelloServer`)来处理对应 `/` 的请求。
`http.HandleFunc` 注册了一个处理函数(这里是 `HelloServer()`)来处理对应 `/` 的请求。
`/` 可以被替换为其他更特定的 url比如 `/create``/edit` 等等;你可以为每一个特定的 url 定义一个单独的处理函数。这个函数需要两个参数:第一个是 `ReponseWriter` 类型的 `w`;第二个是请求 `req`。程序向 `w` 写入了 `Hello``r.URL.Path[1:]` 组成的字符串:末尾的 `[1:]` 表示“创建一个从索引为 1 的字符到结尾的子切片”,用来丢弃路径开头的“/”,`fmt.Fprintf()` 函数完成了本次写入(见 [12.8节](12.8.md));另一种可行的写法是 `io.WriteString(w, "hello, world!\n")`
`/` 可以被替换为其他更特定的 url比如 `/create``/edit` 等等;你可以为每一个特定的 url 定义一个单独的处理函数。这个函数需要两个参数:第一个是 `ReponseWriter` 类型的 `w`;第二个是请求 `req`。程序向 `w` 写入了 `Hello``r.URL.Path[1:]` 组成的字符串:末尾的 `[1:]` 表示“创建一个从索引为 1 的字符到结尾的子切片”,用来丢弃路径开头的“/”,`fmt.Fprintf()` 函数完成了本次写入(见 [12.8 ](12.8.md));另一种可行的写法是 `io.WriteString(w, "hello, world!\n")`
总结:第一个参数是请求的路径,第二个参数是当路径被请求时,需要调用的处理函数的引用。
@@ -53,7 +53,7 @@ Starting Process E:/Go/GoBoek/code_examples/chapter_14/hello_world_webserver.exe
然后打开浏览器并输入 url 地址:`http://localhost:8080/world`,浏览器就会出现文字:`Hello, world`,网页服务器会响应你在 `:8080/` 后边输入的内容。
`fmt.Println` 在服务器端控制台打印状态;在每个处理函数被调用时,把请求记录下来也许更为有用。
`fmt.Println()` 在服务器端控制台打印状态;在每个处理函数被调用时,把请求记录下来也许更为有用。
注:
1前两行没有错误处理代码可以替换成以下写法
@@ -61,8 +61,10 @@ Starting Process E:/Go/GoBoek/code_examples/chapter_14/hello_world_webserver.exe
http.ListenAndServe(":8080", http.HandlerFunc(HelloServer))
```
2`fmt.Fprint``fmt.Fprintf` 都是可以用来写入 `http.ResponseWriter` 的函数(他们实现了 `io.Writer`)。
2`fmt.Fprint()``fmt.Fprintf()` 都是可以用来写入 `http.ResponseWriter` 的函数(他们实现了 `io.Writer`)。
比如我们可以使用
```go
fmt.Fprintf(w, "<h1>%s</h1><div>%s</div>", title, body)
```
@@ -73,6 +75,7 @@ fmt.Fprintf(w, "<h1>%s</h1><div>%s</div>", title, body)
3如果你需要使用安全的 https 连接,使用 `http.ListenAndServeTLS()` 代替 `http.ListenAndServe()`
4除了 `http.HandleFunc("/", Hfunc)`,其中的 `HFunc` 是一个处理函数,签名为:
```go
func HFunc(w http.ResponseWriter, req *http.Request) {
...
@@ -80,29 +83,30 @@ func HFunc(w http.ResponseWriter, req *http.Request) {
```
也可以使用这种方式:`http.Handle("/", http.HandlerFunc(HFunc))`
`HandlerFunc` 只是定义了上述 HFunc 签名的别名:
`HandlerFunc` 只是定义了上述 `HFunc` 签名的别名:
```go
type HandlerFunc func(ResponseWriter, *Request)
```
它是一个可以把普通的函数当做 HTTP 处理器`Handler`的适配器。如果函数 `f` 声明合适,`HandlerFunc(f)` 就是一个执行 `f` 函数的 `Handler` 对象。
它是一个可以把普通的函数当做 HTTP 处理器 (`Handler`) 的适配器。如果函数 `f` 声明合适,`HandlerFunc(f)` 就是一个执行 `f` 函数的 `Handler` 对象。
`http.Handle` 的第二个参数也可以是 `T` 类型的对象 obj`http.Handle("/", obj)`
`http.Handle()` 的第二个参数也可以是 `T` 类型的对象 `obj``http.Handle("/", obj)`
如果 T`ServeHTTP` 方法那就实现了http 的 `Handler` 接口:
如果 `T``ServeHTTP()` 方法,那就实现了 http 的 `Handler` 接口:
```go
func (obj *Typ) ServeHTTP(w http.ResponseWriter, req *http.Request) {
...
}
```
这个用法也在 [15.8节](15.8.md) `Counter``Chan` 类型上使用。只要实现了 `http.Handler``http` 包就可以处理任何 HTTP 请求。
这个用法也在 [15.8 ](15.8.md) `Counter``Chan` 类型上使用。只要实现了 `http.Handler``http` 包就可以处理任何 HTTP 请求。
练习 15.2[webhello2.go](exercises/chapter_15/webhello2.go)
编写一个网页服务器监听端口 9999有如下处理函数
* 当请求 `http://localhost:9999/hello/Name` 时,响应:`hello Name`Name 需是一个合法的姓,比如 Chris 或者 Madeleine
* 当请求 `http://localhost:9999/hello/Name` 时,响应:`hello Name``Name` 需是一个合法的姓,比如 Chris 或者 Madeleine
* 当请求 `http://localhost:9999/shouthello/Name` 时,响应:`hello NAME`