mirror of
https://github.com/unknwon/the-way-to-go_ZH_CN.git
synced 2025-08-12 05:33:04 +08:00
improve chapter 15 (#693)
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
# 15.1 tcp服务器
|
# 15.1 tcp 服务器
|
||||||
|
|
||||||
这部分我们将使用 TCP 协议和在 14 章讲到的协程范式编写一个简单的客户端-服务器应用,一个(web)服务器应用需要响应众多客户端的并发请求:Go 会为每一个客户端产生一个协程用来处理请求。我们需要使用 net 包中网络通信的功能。它包含了处理 TCP/IP 以及 UDP 协议、域名解析等方法。
|
这部分我们将使用 TCP 协议和在 14 章讲到的协程范式编写一个简单的客户端-服务器应用,一个(web)服务器应用需要响应众多客户端的并发请求:Go 会为每一个客户端产生一个协程用来处理请求。我们需要使用 net 包中网络通信的功能。它包含了处理 TCP/IP 以及 UDP 协议、域名解析等方法。
|
||||||
|
|
||||||
@@ -299,13 +299,13 @@ func checkError(error error, info string) {
|
|||||||
* 服务器地址和端口不再是硬编码,而是通过命令行参数传入,并通过 `flag` 包来读取这些参数。这里使用了 `flag.NArg()` 检查是否按照期望传入了2个参数:
|
* 服务器地址和端口不再是硬编码,而是通过命令行参数传入,并通过 `flag` 包来读取这些参数。这里使用了 `flag.NArg()` 检查是否按照期望传入了2个参数:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
if flag.NArg() != 2{
|
if flag.NArg() != 2 {
|
||||||
panic("usage: host port")
|
panic("usage: host port")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
传入的参数通过 `fmt.Sprintf` 函数格式化成字符串
|
传入的参数通过 `fmt.Sprintf` 函数格式化成字符串
|
||||||
```go
|
```go
|
||||||
hostAndPort := fmt.Sprintf("%s:%s", flag.Arg(0), flag.Arg(1))
|
hostAndPort := fmt.Sprintf("%s:%s", flag.Arg(0), flag.Arg(1))
|
||||||
```
|
```
|
||||||
* 在 `initServer` 函数中通过 `net.ResolveTCPAddr` 得到了服务器地址和端口,这个函数最终返回了一个 `*net.TCPListener`
|
* 在 `initServer` 函数中通过 `net.ResolveTCPAddr` 得到了服务器地址和端口,这个函数最终返回了一个 `*net.TCPListener`
|
||||||
* 每一个连接都会以协程的方式运行 `connectionHandler` 函数。函数首先通过 `conn.RemoteAddr()` 获取到客户端的地址并显示出来
|
* 每一个连接都会以协程的方式运行 `connectionHandler` 函数。函数首先通过 `conn.RemoteAddr()` 获取到客户端的地址并显示出来
|
||||||
@@ -336,11 +336,11 @@ type Error interface {
|
|||||||
通过类型断言,客户端代码可以测试 `net.Error`,从而区分是临时发生的还是必然会出现的错误。举例来说,一个网络爬虫程序在遇到临时发生的错误时可能会休眠或者重试,如果是一个必然发生的错误,则他会放弃继续执行。
|
通过类型断言,客户端代码可以测试 `net.Error`,从而区分是临时发生的还是必然会出现的错误。举例来说,一个网络爬虫程序在遇到临时发生的错误时可能会休眠或者重试,如果是一个必然发生的错误,则他会放弃继续执行。
|
||||||
```go
|
```go
|
||||||
// in a loop - some function returns an error err
|
// in a loop - some function returns an error err
|
||||||
if nerr, ok := err.(net.Error); ok && nerr.Temporary(){
|
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
|
||||||
time.Sleep(1e9)
|
time.Sleep(1e9)
|
||||||
continue // try again
|
continue // try again
|
||||||
}
|
}
|
||||||
if err != nil{
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -349,4 +349,4 @@ if err != nil{
|
|||||||
|
|
||||||
- [目录](directory.md)
|
- [目录](directory.md)
|
||||||
- 上一节:[网络、模版与网页应用](15.0.md)
|
- 上一节:[网络、模版与网页应用](15.0.md)
|
||||||
- 下一节:[一个简单的web服务器](15.2.md)
|
- 下一节:[一个简单的网页服务器](15.2.md)
|
||||||
|
@@ -1,24 +1,24 @@
|
|||||||
# 15.2 一个简单的网页服务器
|
# 15.2 一个简单的网页服务器
|
||||||
|
|
||||||
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` 字段。
|
`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
|
```go
|
||||||
var1, found := request.Form["var1"]
|
var1, found := request.Form["var1"]
|
||||||
```
|
```
|
||||||
第二个参数 `found` 为 `true`。如果 `var1` 并未出现在表单中,`found` 就是 `false`。
|
第二个参数 `found` 为 `true`。如果 `var1` 并未出现在表单中,`found` 就是 `false`。
|
||||||
|
|
||||||
表单属性实际上是一个 `map[string][]string` 类型。网页服务器返回了一个 `http.Response`,它是通过 `http.ResponseWriter` 对象输出的,它会组装 HTTP 服务器响应;通过对它写入内容,我们就将数据发送给了 HTTP 客户端。
|
表单属性实际上是 `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")`。
|
||||||
|
|
||||||
总结:第一个参数是请求的路径,第二个参数是当路径被请求时,需要调用的处理函数的引用。
|
总结:第一个参数是请求的路径,第二个参数是当路径被请求时,需要调用的处理函数的引用。
|
||||||
|
|
||||||
@@ -48,8 +48,7 @@ func main() {
|
|||||||
```
|
```
|
||||||
使用命令行启动程序,会打开一个命令窗口显示如下文字:
|
使用命令行启动程序,会打开一个命令窗口显示如下文字:
|
||||||
```
|
```
|
||||||
Starting Process E:/Go/GoBoek/code_examples/chapter_14/hello_world_webserver.exe
|
Starting Process E:/Go/GoBoek/code_examples/chapter_14/hello_world_webserver.exe...
|
||||||
...
|
|
||||||
```
|
```
|
||||||
|
|
||||||
然后打开浏览器并输入 url 地址:`http://localhost:8080/world`,浏览器就会出现文字:`Hello, world`,网页服务器会响应你在 `:8080/` 后边输入的内容。
|
然后打开浏览器并输入 url 地址:`http://localhost:8080/world`,浏览器就会出现文字:`Hello, world`,网页服务器会响应你在 `:8080/` 后边输入的内容。
|
||||||
@@ -69,7 +68,7 @@ fmt.Fprintf(w, "<h1>%s<h1><div>%s</div>", title, body)
|
|||||||
```
|
```
|
||||||
来构建一个非常简单的网页并插入 `title` 和 `body` 的值。
|
来构建一个非常简单的网页并插入 `title` 和 `body` 的值。
|
||||||
|
|
||||||
如果你需要更多复杂的替换,使用模板包(请看 [15.7节](15.7.md))
|
如果你需要更多复杂的替换,使用模板包(见 [15.7节](15.7.md))
|
||||||
|
|
||||||
3)如果你需要使用安全的 https 连接,使用 `http.ListenAndServeTLS()` 代替 `http.ListenAndServe()`
|
3)如果你需要使用安全的 https 连接,使用 `http.ListenAndServeTLS()` 代替 `http.ListenAndServe()`
|
||||||
|
|
||||||
@@ -81,7 +80,7 @@ func HFunc(w http.ResponseWriter, req *http.Request) {
|
|||||||
```
|
```
|
||||||
也可以使用这种方式:`http.Handle("/", http.HandlerFunc(HFunc))`
|
也可以使用这种方式:`http.Handle("/", http.HandlerFunc(HFunc))`
|
||||||
|
|
||||||
`HandlerFunc` 只是一个类型名称,它定义如下:
|
`HandlerFunc` 只是定义了上述 HFunc 签名的别名:
|
||||||
```go
|
```go
|
||||||
type HandlerFunc func(ResponseWriter, *Request)
|
type HandlerFunc func(ResponseWriter, *Request)
|
||||||
```
|
```
|
||||||
@@ -97,22 +96,22 @@ 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)
|
练习 15.2:[webhello2.go](exercises/chapter_15/webhello2.go)
|
||||||
|
|
||||||
编写一个网页服务器监听端口 9999,有如下处理函数:
|
编写一个网页服务器监听端口 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`
|
* 当请求 `http://localhost:9999/shouthello/Name` 时,响应:`hello NAME`
|
||||||
|
|
||||||
练习 15.3:[hello_server.go](exercises/chapter_15/hello_server.go)
|
练习 15.3:[hello_server.go](exercises/chapter_15/hello_server.go)
|
||||||
|
|
||||||
创建一个空结构 `hello` 并使它实现 `http.Handler`。运行并测试。
|
创建一个空结构 `hello` 并为它实现 `http.Handler`。运行并测试。
|
||||||
|
|
||||||
## 链接
|
## 链接
|
||||||
|
|
||||||
- [目录](directory.md)
|
- [目录](directory.md)
|
||||||
- 上一节:[tcp服务器](15.1.md)
|
- 上一节:[tcp 服务器](15.1.md)
|
||||||
- 下一节:[访问并读取页面数据](15.3.md)
|
- 下一节:[访问并读取页面数据](15.3.md)
|
||||||
|
Reference in New Issue
Block a user