- 原文地址:www.alexedwards.net/blog/a-reca…
- 原文做者:Alex Edwards
- 译文地址:github.com/watermelo/d…
- 译者:咔叽咔叽
- 译者水平有限,若有翻译或理解谬误,烦请帮忙指出
使用 Go 处理 HTTP 请求主要涉及两件事:ServeMuxes 和 Handlers。git
ServeMux本质上是一个 HTTP 请求路由器(或多路复用器)。它将传入的请求与预约义的 URL 路径列表进行比较,并在找到匹配时调用路径的关联 handler。github
handler 负责写入响应头和响应体。几乎任何对象均可以是 handler,只要它知足http.Handler接口便可。在非专业术语中,这仅仅意味着它必须是一个拥有如下签名的ServeHTTP
方法:golang
ServeHTTP(http.ResponseWriter, *http.Request)
数据库
Go 的 HTTP 包附带了一些函数来生成经常使用的 handler,例如FileServer,NotFoundHandler和RedirectHandler。让咱们从一个简单的例子开始:浏览器
$ mkdir handler-example
$ cd handler-example
$ touch main.go
复制代码
File: main.go安全
package main
import (
"log"
"net/http"
)
func main() {
mux := http.NewServeMux()
rh := http.RedirectHandler("http://example.org", 307)
mux.Handle("/foo", rh)
log.Println("Listening...")
http.ListenAndServe(":3000", mux)
}
复制代码
让咱们快速介绍一下:闭包
main
函数中,咱们使用http.NewServeMux函数建立了一个空的ServeMux。/foo
的全部传入请求的handler。继续运行应用程序:函数
$ go run main.go
Listening...
复制代码
并在浏览器中访问http://localhost:3000/foo。你会发现请求已经被成功重定向。测试
你可能已经注意到了一些有趣的东西:ListenAndServe 函数的签名是ListenAndServe(addr string, handler Handler)
,但咱们传递了一个 ServeMux 做为第二个参数。ui
能这么作是由于 ServeMux 类型也有一个 ServeHTTP 方法,这意味着它也知足 Handler 接口。
对我而言,它只是将 ServeMux 视为一种特殊的 handler,而不是把响应自己经过第二个 handler 参数传递给请求。这不像刚刚据说时那么惊讶 - 将 handler 连接在一块儿在 Go 中至关广泛。
咱们建立一个自定义 handler,它以当前本地时间的指定格式响应:
type timeHandler struct {
format string
}
func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
tm := time.Now().Format(th.format)
w.Write([]byte("The time is: " + tm))
}
复制代码
这里确切的代码并不过重要。
真正重要的是咱们有一个对象(在该示例中它是一个timeHandler
结构,它一样能够是一个字符串或函数或其余任何东西),而且咱们已经实现了一个带有签名ServeHTTP(http.ResponseWriter, *http.Request)
的方法。这就是咱们实现一个 handler 所需的所有内容。
让咱们将其嵌入一个具体的例子中:
File: main.go
package main
import (
"log"
"net/http"
"time"
)
type timeHandler struct {
format string
}
func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
tm := time.Now().Format(th.format)
w.Write([]byte("The time is: " + tm))
}
func main() {
mux := http.NewServeMux()
th := &timeHandler{format: time.RFC1123}
mux.Handle("/time", th)
log.Println("Listening...")
http.ListenAndServe(":3000", mux)
}
复制代码
在main
函数中,咱们使用&
符号生成指针,用与普通结构彻底相同的方式初始化timeHandler
。而后,与前面的示例同样,咱们使用mux.Handle
函数将其注册到咱们的ServeMux。
如今,当咱们运行应用程序时,ServeMux会将任何经过/time
路径的请求直接传递给咱们的timeHandler.ServeHTTP
方法。
试一试:http://localhost:3000/time。
另请注意,咱们能够轻松地在多个路径中重复使用timeHandler:
func main() {
mux := http.NewServeMux()
th1123 := &timeHandler{format: time.RFC1123}
mux.Handle("/time/rfc1123", th1123)
th3339 := &timeHandler{format: time.RFC3339}
mux.Handle("/time/rfc3339", th3339)
log.Println("Listening...")
http.ListenAndServe(":3000", mux)
}
复制代码
对于简单的状况(如上例),定义新的自定义类型和 ServeHTTP 方法感受有点啰嗦。让咱们看看另外一个方法,咱们利用 Go 的http.HandlerFunc类型来使正常的函数知足 Handler 接口。
任何具备签名func(http.ResponseWriter, *http.Request)
的函数均可以转换为 HandlerFunc 类型。这颇有用,由于 HandleFunc 对象带有一个内置的ServeHTTP
方法 - 这很是巧妙且方便 - 执行原始函数的内容。
若是这听起来使人费解,请尝试查看相关的源代码。你将看到它是一种让函数知足 Handler 接口的很是简洁的方法。
咱们使用这种方法来重写 timeHandler 应用程序:
File: main.go
package main
import (
"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方法。
若是咱们使用这个转换方法,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 main
import (
"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,包括最简单的 Hello World 示例到 Go 源代码。
我花了很长时间才意识到它并不特别。 DefaultServeMux 只是一个普通的 ServeMux,就像咱们已经使用的那样,默认状况下在使用 HTTP 包时会实例化。如下是 Go 源代码中的相关行:
var 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
package main
import (
"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() {
// Note that we skip creating the ServeMux...
var format string = time.RFC1123
th := timeHandler(format)
// We use http.Handle instead of mux.Handle...
http.Handle("/time", th)
log.Println("Listening...")
// And pass nil as the handler to ListenAndServe.
http.ListenAndServe(":3000", nil)
}
复制代码
若是你喜欢这篇博文,请不要忘记查看个人新书《用 Go 构建专业的 Web 应用程序》!
在推特上关注我 @ajmedwards。
此文章中的全部代码均可以在MIT Licence许可下无偿使用。