> For the complete documentation index, see [llms.txt](https://ryanyang.gitbook.io/the-way-to-go-zh-cn/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ryanyang.gitbook.io/the-way-to-go-zh-cn/di-san-bu-fen-go-gao-ji-bian-cheng/15.0/15.2.md).

# 一个简单的web服务器

Http是一个比tcp更高级的协议，它描述了客户端浏览器如何与网页服务器进行通信。Go有自己的`net/http`包，我们来看看它。我们从一些简单的示例开始， 首先编写一个“Hello world!”：[查看示例15.6](https://github.com/yangchuansheng/the-way-to-go_ZH_CN/tree/f30ab7d8c58f85840a0afb548024b93642b518d5/eBook/examples/chapter_15/hello_world_webserver.go)

我们引入了`http`包并启动了网页服务器，和15.1的`net.Listen("tcp", "localhost:50000")`函数的tcp服务器是类似的，使用`http.ListenAndServe("localhost:8080", nil)`函数，如果成功会返回空，否则会返回一个错误（可以指定localhost为其他地址，8080是指定的端口号）

`http.URL`描述了web服务器的地址，内含存放了url字符串的`Path`属性；`http.Request`描述了客户端请求，内含一个`URL`属性

如果`req`请求是一个POST类型的html表单，“var1”就是html表单中一个输入属性的名称，然后用户输入的值就可以通过GO代码：`req.FormValue("var1")`获取到（请看[章节15.4](/the-way-to-go-zh-cn/di-san-bu-fen-go-gao-ji-bian-cheng/15.0/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`类型的，它们通常用使用Prehandler来命名，在前边加了一个Pref前缀。

`http.HandleFunc`注册了一个处理函数(这里是`HelloServer`)来处理对应`/`的请求。

`/`可以被替换为其他特定的url比如`/create`，`/edit`等等；你可以为每一个特定的url定义一个单独的处理函数。这个函数需要两个参数：第一个是`ReponseWriter`类型的`w`；第二个是请求`req`。程序向`w`写入了`Hello`和`r.URL.Path[1:]`组成的字符串后边的`[1:]`表示“创建一个从第一个字符到结尾的子切片”，用来丢弃掉路径开头的“/”，`fmt.Fprintf()`函数完成了本次写入（请看[章节12.8](/the-way-to-go-zh-cn/di-san-bu-fen-go-gao-ji-bian-cheng/12.0/12.8.md)）；另外一种写法是`io.WriteString(w, "hello, world!\n")`

总结：第一个参数是请求的路径，第二个参数是处理这个路径请求的函数的引用。

示例 15.6 [hello\_world\_webserver.go](https://github.com/yangchuansheng/the-way-to-go_ZH_CN/tree/f30ab7d8c58f85840a0afb548024b93642b518d5/eBook/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`在控制台打印状态，在每个handler被请求的时候，在他们内部打印日志会很有帮助

注： 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](https://github.com/yangchuansheng/the-way-to-go_ZH_CN/tree/f30ab7d8c58f85840a0afb548024b93642b518d5/eBook/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`只是一个类型名称，它定义如下：

```go
type HandlerFunc func(ResponseWriter, *Request)
```

它是一个可以把普通的函数当做HTTP处理器的适配器。如果`f`函数声明的合适，`HandlerFunc(f)`就是一个执行了`f`函数的处理器对象。

`http.Handle`的第二个参数也可以是`T`的一个obj对象：`http.Handle("/", obj)`给T提供了`ServeHTTP`方法，实现了http的`Handler`接口：

```go
func (obj *Typ) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    ...
}
```

这个用法在[章节15.8](https://github.com/yangchuansheng/the-way-to-go_ZH_CN/tree/f30ab7d8c58f85840a0afb548024b93642b518d5/eBook/15.8.md)类型`Counter`和`Chan`上使用过。只要实现了`http.Handler`，`http`包就可以处理任何HTTP请求。

练习 15.2：[webhello2.go](https://github.com/yangchuansheng/the-way-to-go_ZH_CN/tree/f30ab7d8c58f85840a0afb548024b93642b518d5/eBook/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](https://github.com/yangchuansheng/the-way-to-go_ZH_CN/tree/f30ab7d8c58f85840a0afb548024b93642b518d5/eBook/exercises/chapter_15/hello_server.go)

创建一个空结构`hello`并使它实现`http.Handler`。运行并测试。

## 链接

* [目录](https://github.com/yangchuansheng/the-way-to-go_ZH_CN/tree/f30ab7d8c58f85840a0afb548024b93642b518d5/eBook/directory.md)
* 上一章：[tcp服务器](/the-way-to-go-zh-cn/di-san-bu-fen-go-gao-ji-bian-cheng/15.0/15.1.md)
* 下一节：[访问并读取页面数据](/the-way-to-go-zh-cn/di-san-bu-fen-go-gao-ji-bian-cheng/15.0/15.3.md)
