# 15.2 一个简单的 web 服务器

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)
