Go 如何处理 HTTP 请求?掌握这两点即可( 二 )


如果这听起来令人费解,请尝试查看相关的源代码[13] 。你将看到它是一种让函数满足 Handler 接口的非常简洁的方法 。
我们使用这种方法来重写 timeHandler 应用程序:

File: main.go
package mainimport ( "log" "net/http" "time")func timeHandler(w http.ResponseWriter, r *http.Request) { tm := time.Now().Format(time.RFC1123) w.Write([]byte("The time is: " + tm))}func main() { mux := http.NewServeMux() // Convert the timeHandler function to a HandlerFunc type th := http.HandlerFunc(timeHandler) // And add it to the ServeMux mux.Handle("/time", th) log.Println("Listening...") http.ListenAndServe(":3000", mux)}事实上,将函数转换为 HandlerFunc 类型,然后将其添加到 ServeMux 的情况比较常见,Go 提供了一个快捷的转换方法:mux.HandleFunc[14] 方法 。
如果我们使用这个转换方法,main() 函数将是这个样子:
func main() { mux := http.NewServeMux() mux.HandleFunc("/time", timeHandler) log.Println("Listening...") http.ListenAndServe(":3000", mux)}大多数时候使用这样的 handler 很有效 。但是当事情变得越来越复杂时,将会受限 。
你可能已经注意到,与之前的方法不同,我们必须在 timeHandler 函数中对时间格式进行硬编码 。当我们想要将信息或变量从 main() 传递给 handler 时会发生什么?
一个简洁的方法是将我们的 handler 逻辑放入一个闭包中,把我们想用的变量包起来:
File: main.go
package mainimport ( "log" "net/http" "time")func timeHandler(format string) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) {tm := time.Now().Format(format)w.Write([]byte("The time is: " + tm)) } return http.HandlerFunc(fn)}func main() { mux := http.NewServeMux() th := timeHandler(time.RFC1123) mux.Handle("/time", th) log.Println("Listening...") http.ListenAndServe(":3000", mux)}timeHandler 函数现在有一点点不同 。现在使用它来返回 handler,而不是将函数强制转换为 handler(就像我们之前所做的那样) 。能这么做有两个关键点 。
首先它创建了一个匿名函数 fn,它访问形成闭包的 format 变量 。无论我们如何处理闭包,它总是能够访问它作用域下所创建的局部变量 - 在这种情况下意味着它总是可以访问 format 变量 。
其次我们的闭包有签名为 func(http.ResponseWriter, *http.Request) 的函数 。你可能还记得,这意味着我们可以将其转换为 HandlerFunc 类型(以便它满足 Handler 接口) 。然后我们的 timeHandler 函数返回这个转换后的闭包 。
在这个例子中,我们仅仅将一个简单的字符串传递给 handler 。但在实际应用程序中,您可以使用此方法传递数据库连接,模板映射或任何其他应用程序级的上下文 。它是全局变量的一个很好的替代方案,并且可以使测试的自包含 handler 变得更整洁 。
你可能还会看到相同的模式,如下所示:
func timeHandler(format string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {tm := time.Now().Format(format)w.Write([]byte("The time is: " + tm)) })}或者在返回时使用隐式转换为 HandlerFunc 类型:
func timeHandler(format string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) {tm := time.Now().Format(format)w.Write([]byte("The time is: " + tm)) }}DefaultServeMux
你可能已经看到过很多地方提到的 DefaultServeMux,包括最简单的 Hello World 示例到 Go 源代码 。
我花了很长时间才意识到它并不特别 。DefaultServeMux 只是一个普通的 ServeMux,就像我们已经使用的那样,默认情况下在使用 HTTP 包时会实例化 。以下是 Go 源代码中的相关行:
var DefaultServeMux = NewServeMux()通常,你不应使用 DefaultServeMux,因为它会带来安全风险 。
由于 DefaultServeMux 存储在全局变量中,因此任何程序包都可以访问它并注册路由 - 包括应用程序导入的任何第三方程序包 。如果其中一个第三方软件包遭到破坏,他们可以使用 DefaultServeMux 向 Web 公开恶意 handler 。
因此,根据经验,避免使用 DefaultServeMux 是一个好主意,取而代之使用你自己的本地范围的 ServeMux,就像我们到目前为止一样 。但如果你决定使用它……
HTTP 包提供了一些使用 DefaultServeMux 的便捷方式:http.Handle[15] 和http.HandleFunc[16] 。这些与我们已经看过的同名函数完全相同,不同之处在于它们将 handler 添加到 DefaultServeMux 而不是你自己创建的 handler 。
此外,如果没有提供其他 handler(即第二个参数设置为 nil),ListenAndServe 将退回到使用 DefaultServeMux 。


推荐阅读