15.2 一个简单的 web 服务器
http 是比 tcp 更高层的协议,它描述了网页服务器如何与客户端浏览器进行通信。Go 提供了 net/http
包,我们马上就来看下。先从一些简单的示例开始,首先编写一个“Hello world!”网页服务器:查看示例 15.6
我们引入了 http
包并启动了网页服务器,和 15.1 节的 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 节)。还有一种方法是先执行 request.ParseForm()
,然后再获取 request.Form["var1"]
的第一个返回参数,就像这样:
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 节);另一种可行的写法是 io.WriteString(w, "hello, world!\n")
。
总结:第一个参数是请求的路径,第二个参数是当路径被请求时,需要调用的处理函数的引用。
示例 15.6 hello_world_webserver.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)前两行(没有错误处理代码)可以替换成以下写法:
http.ListenAndServe(":8080", http.HandlerFunc(HelloServer))
2)fmt.Fprint()
和 fmt.Fprintf()
都是可以用来写入 http.ResponseWriter
的函数(他们实现了 io.Writer
)。
比如我们可以使用
fmt.Fprintf(w, "<h1>%s</h1><div>%s</div>", title, body)
来构建一个非常简单的网页并插入 title
和 body
的值。
如果你需要更多复杂的替换,使用模板包(见 15.7节)
3)如果你需要使用安全的 https 连接,使用 http.ListenAndServeTLS()
代替 http.ListenAndServe()
4)除了 http.HandleFunc("/", Hfunc)
,其中的 HFunc
是一个处理函数,签名为:
func HFunc(w http.ResponseWriter, req *http.Request) {
...
}
也可以使用这种方式:http.Handle("/", http.HandlerFunc(HFunc))
HandlerFunc
只是定义了上述 HFunc
签名的别名:
type HandlerFunc func(ResponseWriter, *Request)
它是一个可以把普通的函数当做 HTTP 处理器 (Handler
) 的适配器。如果函数 f
声明得合适,HandlerFunc(f)
就是一个执行 f
函数的 Handler
对象。
http.Handle()
的第二个参数也可以是 T
类型的对象 obj
:http.Handle("/", obj)
。
如果 T
有 ServeHTTP()
方法,那就实现了 http 的 Handler
接口:
func (obj *Typ) ServeHTTP(w http.ResponseWriter, req *http.Request) {
...
}
这个用法也在 15.8 节 Counter
和 Chan
类型上使用。只要实现了 http.Handler
,http
包就可以处理任何 HTTP 请求。
练习 15.2: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
创建一个空结构 hello
并为它实现 http.Handler
。运行并测试。