自定义 handler
自定义 handler
- 原文地址:https://www.alexedwards.net/blog/a-recap-of-request-handling
- 原文作者:Alex Edwards
- 译文地址:https://github.com/watermelo/dailyTrans
- 译者:咔叽咔叽
- 译者水平有限,如有翻译或理解谬误,烦请帮忙指出
使用 Go 处理 HTTP 请求主要涉及两件事:ServeMuxes 和 Handlers。
ServeMux本质上是一个 HTTP 路由(或多路复用器)。它将传入的请求与预定义的 URL 路径列表进行比较,当路径匹配上时就调用该路径关联的 handler 进行处理。
handler 负责写入响应头和响应体。几乎任何对象都可以是 handler,只要它满足http.Handler接口即可。在非专业术语中,这仅仅意味着它必须是一个拥有以下签名的ServeHTTP
方法:
ServeHTTP(http.ResponseWriter, *http.Request)
Go 的 HTTP 包附带了一些函数来生成常用的 handler,例如FileServer,NotFoundHandler和RedirectHandler。让我们从一个简单的例子开始:
1$ mkdir handler-example
2$ cd handler-example
3$ touch main.go
File: main.go
1package main
2
3import (
4 "log"
5 "net/http"
6)
7
8func main() {
9 mux := http.NewServeMux()
10
11 rh := http.RedirectHandler("http://example.org", 307)
12 mux.Handle("/foo", rh)
13
14 log.Println("Listening...")
15 http.ListenAndServe(":3000", mux)
16}
让我们快速介绍一下:
- 在
main
函数中,我们使用http.NewServeMux函数创建了一个空的ServeMux。 - 然后我们使用http.RedirectHandler函数创建一个新的handler。该handler将其接收的所有请求307重定向到http://example.org。
- 接下来我们使用mux.Handle函数向我们的新ServeMux注册 handler,因此它充当所有传入URL路径
/foo
的所有请求的handler。 - 最后,我们创建一个新服务并使用http.ListenAndServe函数开始监听传入的请求,并传入ServeMux给这个方法以匹配请求。
继续运行应用程序:
1$ go run main.go
2Listening...
并在浏览器中访问http://localhost:3000/foo。你会发现请求已经被成功重定向。
你可能已经注意到了一些有趣的东西:ListenAndServe 函数的签名是ListenAndServe(addr string, handler Handler)
,但我们传递了一个 ServeMux 作为第二个参数。
能这么做是因为 ServeMux 类型也有一个 ServeHTTP 方法,这意味着它也满足 Handler 接口。
对我而言,它只是将 ServeMux 视为一种特殊的 handler,而不是把响应本身通过第二个 handler 参数传递给请求。这不像刚刚听说时那么惊讶 - 将 handler 链接在一起在 Go 中相当普遍。
自定义 handler
我们创建一个自定义 handler,它以当前本地时间的指定格式响应:
1type timeHandler struct {
2 format string
3}
4
5func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
6 tm := time.Now().Format(th.format)
7 w.Write([]byte("The time is: " + tm))
8}
这里确切的代码并不太重要。
真正重要的是我们有一个对象(在该示例中它是一个timeHandler
结构,它同样可以是一个字符串或函数或其他任何东西),并且我们已经实现了一个带有签名ServeHTTP(http.ResponseWriter, *http.Request)
的方法。这就是我们实现一个 handler 所需的全部内容。
让我们将其嵌入一个具体的例子中:
File: main.go
1package main
2
3import (
4 "log"
5 "net/http"
6 "time"
7)
8
9type timeHandler struct {
10 format string
11}
12
13func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
14 tm := time.Now().Format(th.format)
15 w.Write([]byte("The time is: " + tm))
16}
17
18func main() {
19 mux := http.NewServeMux()
20
21 th := &timeHandler{format: time.RFC1123}
22 mux.Handle("/time", th)
23
24 log.Println("Listening...")
25 http.ListenAndServe(":3000", mux)
26}
在main
函数中,我们使用&
符号生成指针,用与普通结构完全相同的方式初始化timeHandler
。然后,与前面的示例一样,我们使用mux.Handle
函数将其注册到我们的ServeMux。
现在,当我们运行应用程序时,ServeMux会将任何通过/time
路径的请求直接传递给我们的timeHandler.ServeHTTP
方法。
试一试:http://localhost:3000/time。
另请注意,我们可以轻松地在多个路径中重复使用timeHandler:
1func main() {
2 mux := http.NewServeMux()
3
4 th1123 := &timeHandler{format: time.RFC1123}
5 mux.Handle("/time/rfc1123", th1123)
6
7 th3339 := &timeHandler{format: time.RFC3339}
8 mux.Handle("/time/rfc3339", th3339)
9
10 log.Println("Listening...")
11 http.ListenAndServe(":3000", mux)
12}
普通函数作为 handler
对于简单的情况(如上例),定义新的自定义类型和 ServeHTTP 方法感觉有点啰嗦。让我们看看另一个方法,我们利用 Go 的http.HandlerFunc类型来使正常的函数满足 Handler 接口。
任何具有签名func(http.ResponseWriter, *http.Request)
的函数都可以转换为 HandlerFunc 类型。这很有用,因为 HandleFunc 对象带有一个内置的ServeHTTP
方法 - 这非常巧妙且方便 - 执行原始函数的内容。
如果这听起来令人费解,请尝试查看相关的源代码。你将看到它是一种让函数满足 Handler 接口的非常简洁的方法。
我们使用这种方法来重写 timeHandler 应用程序:
File: main.go
1package main
2
3import (
4 "log"
5 "net/http"
6 "time"
7)
8
9func timeHandler(w http.ResponseWriter, r *http.Request) {
10 tm := time.Now().Format(time.RFC1123)
11 w.Write([]byte("The time is: " + tm))
12}
13
14func main() {
15 mux := http.NewServeMux()
16
17 // Convert the timeHandler function to a HandlerFunc type
18 th := http.HandlerFunc(timeHandler)
19 // And add it to the ServeMux
20 mux.Handle("/time", th)
21
22 log.Println("Listening...")
23 http.ListenAndServe(":3000", mux)
24}
事实上,将函数转换为HandlerFunc类型,然后将其添加到ServeMux的情况比较常见,Go提供了一个快捷的转换方法:mux.HandleFunc方法。
如果我们使用这个转换方法,main()
函数将是这个样子:
1func main() {
2 mux := http.NewServeMux()
3
4 mux.HandleFunc("/time", timeHandler)
5
6 log.Println("Listening...")
7 http.ListenAndServe(":3000", mux)
8}
大多数时候使用这样的 handler 很有效。但是当事情变得越来越复杂时,将会受限。
你可能已经注意到,与之前的方法不同,我们必须在timeHandler
函数中对时间格式进行硬编码。当我们想要将信息或变量从main()
传递给 handler 时会发生什么?
一个简洁的方法是将我们的 handler 逻辑放入一个闭包中,把我们想用的变量包起来:
File: main.go
1package main
2
3import (
4 "log"
5 "net/http"
6 "time"
7)
8
9func timeHandler(format string) http.Handler {
10 fn := func(w http.ResponseWriter, r *http.Request) {
11 tm := time.Now().Format(format)
12 w.Write([]byte("The time is: " + tm))
13 }
14 return http.HandlerFunc(fn)
15}
16
17func main() {
18 mux := http.NewServeMux()
19
20 th := timeHandler(time.RFC1123)
21 mux.Handle("/time", th)
22
23 log.Println("Listening...")
24 http.ListenAndServe(":3000", mux)
25}
timeHandler
函数现在有一点点不同。现在使用它来返回handler,而不是将函数强制转换为handler(就像我们之前所做的那样)。能这么做有两个关键点。
首先它创建了一个匿名函数fn
,它访问形成闭包的format
变量。无论我们如何处理闭包,它总是能够访问它作用域下所创建的局部变量 - 在这种情况下意味着它总是可以访问format
变量。
其次我们的闭包有签名为func(http.ResponseWriter, *http.Request)
的函数。你可能还记得,这意味着我们可以将其转换为HandlerFunc类型(以便它满足Handler接口)。然后我们的timeHandler
函数返回这个转换后的闭包。
在这个例子中,我们仅仅将一个简单的字符串传递给 handler。但在实际应用程序中,你可以使用此方法传递数据库连接,模板映射或任何其他应用程序级的上下文。它是全局变量的一个很好的替代方案,并且可以使测试的自包含 handler 变得更整洁。
你可能还会看到相同的模式,如下所示:
1func timeHandler(format string) http.Handler {
2 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
3 tm := time.Now().Format(format)
4 w.Write([]byte("The time is: " + tm))
5 })
6}
或者在返回时使用隐式转换为 HandlerFunc 类型:
1func timeHandler(format string) http.HandlerFunc {
2 return func(w http.ResponseWriter, r *http.Request) {
3 tm := time.Now().Format(format)
4 w.Write([]byte("The time is: " + tm))
5 }
6}
DefaultServeMux
你可能已经看到过很多地方提到的 DefaultServeMux,包括最简单的 Hello World 示例到 Go 源代码。
我花了很长时间才意识到它并不特别。 DefaultServeMux 只是一个普通的 ServeMux,就像我们已经使用的那样,默认情况下在使用 HTTP 包时会实例化。以下是 Go 源代码中的相关行:
1var DefaultServeMux = NewServeMux()
通常,你不应使用 DefaultServeMux,因为它会带来安全风险。
由于 DefaultServeMux 存储在全局变量中,因此任何程序包都可以访问它并注册路由 - 包括应用程序导入的任何第三方程序包。如果其中一个第三方软件包遭到破坏,他们可以使用 DefaultServeMux 向 Web 公开恶意 handler。
因此,根据经验,避免使用 DefaultServeMux 是一个好主意,取而代之使用你自己的本地范围的 ServeMux,就像我们到目前为止一样。但如果你决定使用它……
HTTP 包提供了一些使用 DefaultServeMux 的便捷方式:http.Handle和http.HandleFunc。这些与我们已经看过的同名函数完全相同,不同之处在于它们将 handler 添加到 DefaultServeMux 而不是你自己创建的 handler。
此外,如果没有提供其他 handler(即第二个参数设置为nil
),ListenAndServe 将退回到使用 DefaultServeMux。
因此,作为最后一步,让我们更新我们的 timeHandler 应用程序以使用 DefaultServeMux:
File: main.go
1package main
2
3import (
4 "log"
5 "net/http"
6 "time"
7)
8
9func timeHandler(format string) http.Handler {
10 fn := func(w http.ResponseWriter, r *http.Request) {
11 tm := time.Now().Format(format)
12 w.Write([]byte("The time is: " + tm))
13 }
14 return http.HandlerFunc(fn)
15}
16
17func main() {
18 // Note that we skip creating the ServeMux...
19
20 var format string = time.RFC1123
21 th := timeHandler(format)
22
23 // We use http.Handle instead of mux.Handle...
24 http.Handle("/time", th)
25
26 log.Println("Listening...")
27 // And pass nil as the handler to ListenAndServe.
28 http.ListenAndServe(":3000", nil)
29}
如果你喜欢这篇博文,请不要忘记查看我的新书《用 Go 构建专业的 Web 应用程序》!
在推特上关注我 @ajmedwards。
此文章中的所有代码都可以在MIT Licence许可下免费使用。