Files
the-way-to-go_ZH_CN/eBook/15.2.md
2019-07-23 14:32:12 -07:00

118 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 15.2 一个简单的网页服务器
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.URL` 用于表示网页地址,其中字符串属性 `Path` 用于保存 url 的路径;`http.Request` 描述了客户端请求,内含一个 `URL` 字段。
如果 `req` 是来自 html 表单的 POST 类型请求“var1” 是该表单中一个输入域的名称,那么用户输入的值就可以通过 Go 代码 `req.FormValue("var1")` 获取到(见 [15.4节](15.4.md))。还有一种方法是先执行 `request.ParseForm()`,然后再获取 `request.Form["var1"]` 的第一个返回参数,就像这样:
```go
var1, found := request.Form["var1"]
```
第二个参数 `found``true`。如果 `var1` 并未出现在表单中,`found` 就是 `false`
表单属性实际上是 `map[string][]string` 类型。网页服务器发送一个 `http.Response` 响应,它是通过 `http.ResponseWriter` 对象输出的,后者组装了 HTTP 服务器响应,通过对其写入内容,我们就将数据发送给了 HTTP 客户端。
现在我们仍然要编写程序,以实现服务器必须做的事,即如何处理请求。这是通过 `http.HandleFunc` 函数完成的。在这个例子中,当根路径“/”url地址是 `http://localhost:8080`)被请求的时候(或者这个服务器上的其他任意地址),`HelloServer` 函数就被执行了。这个函数是 `http.HandlerFunc` 类型的,它们通常被命名为 Prefhandler和某个路径前缀 Pref 匹配。
`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")`
总结:第一个参数是请求的路径,第二个参数是当路径被请求时,需要调用的处理函数的引用。
示例 15.6 [hello_world_webserver.go](examples/chapter_15/hello_world_webserver.go)
```go
package main
import (
"fmt"
"log"
"net/http"
)
func HelloServer(w http.ResponseWriter, req *http.Request) {
fmt.Println("Inside HelloServer handler")
fmt.Fprintf(w, "Hello,"+req.URL.Path[1:])
}
func main() {
http.HandleFunc("/", HelloServer)
err := http.ListenAndServe("localhost:8080", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err.Error())
}
}
```
使用命令行启动程序,会打开一个命令窗口显示如下文字:
```
Starting Process E:/Go/GoBoek/code_examples/chapter_14/hello_world_webserver.exe...
```
然后打开浏览器并输入 url 地址:`http://localhost:8080/world`,浏览器就会出现文字:`Hello, world`,网页服务器会响应你在 `:8080/` 后边输入的内容。
`fmt.Println` 在服务器端控制台打印状态;在每个处理函数被调用时,把请求记录下来也许更为有用。
注:
1前两行没有错误处理代码可以替换成以下写法
```go
http.ListenAndServe(":8080", http.HandlerFunc(HelloServer))
```
2`fmt.Fprint``fmt.Fprintf` 都是可以用来写入 `http.ResponseWriter` 的函数(他们实现了 `io.Writer`)。
比如我们可以使用
```go
fmt.Fprintf(w, "<h1>%s<h1><div>%s</div>", title, body)
```
来构建一个非常简单的网页并插入 `title``body` 的值。
如果你需要更多复杂的替换,使用模板包(见 [15.7节](15.7.md)
3如果你需要使用安全的 https 连接,使用 `http.ListenAndServeTLS()` 代替 `http.ListenAndServe()`
4除了 `http.HandleFunc("/", Hfunc)`,其中的 `HFunc` 是一个处理函数,签名为:
```go
func HFunc(w http.ResponseWriter, req *http.Request) {
...
}
```
也可以使用这种方式:`http.Handle("/", http.HandlerFunc(HFunc))`
`HandlerFunc` 只是定义了上述 HFunc 签名的别名:
```go
type HandlerFunc func(ResponseWriter, *Request)
```
它是一个可以把普通的函数当做 HTTP 处理器(`Handler`)的适配器。如果函数 `f` 声明的合适,`HandlerFunc(f)` 就是一个执行 `f` 函数的 `Handler` 对象。
`http.Handle` 的第二个参数也可以是 `T` 类型的对象 obj`http.Handle("/", obj)`
如果 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.2[webhello2.go](exercises/chapter_15/webhello2.go)
编写一个网页服务器监听端口 9999有如下处理函数
* 当请求 `http://localhost:9999/hello/Name` 时,响应:`hello Name`Name 需是一个合法的姓,比如 Chris 或者 Madeleine
* 当请求 `http://localhost:9999/shouthello/Name` 时,响应:`hello NAME`
练习 15.3[hello_server.go](exercises/chapter_15/hello_server.go)
创建一个空结构 `hello` 并为它实现 `http.Handler`。运行并测试。
## 链接
- [目录](directory.md)
- 上一节:[tcp 服务器](15.1.md)
- 下一节:[访问并读取页面数据](15.3.md)