如果你熟悉 MVC 架构,可以把处理程序看作是控制器的角色。它们负责执行应用程序的核心逻辑,并生成 HTTP 响应的头部和主体。而 Servemux(也叫路由器)则像是一个调度员,负责管理 URL 路径和处理程序之间的映射关系。通常,一个应用程序会使用一个 Servemux 来统一管理所有路由。
Go 的 net/http
包提供了一个简单但功能强大的 http.ServeMux,同时还包含一些用于生成常见处理程序的函数,例如 http.FileServer()、http.NotFoundHandler() 和 http.RedirectHandler()。
使用 ServeMux 和内置处理程序
下面我们通过一个简单的示例来了解它们的使用方式:
$ mkdir example
$ cd example
$ go mod init example.com
$ touch main.go
文件:main.go
package main
import (
"log"
"net/http"
)
func main() {
// 使用 http.NewServeMux() 创建一个空的 Servemux
mux := http.NewServeMux()
// 使用 http.RedirectHandler() 创建一个处理程序,将所有请求重定向到 http://example.org
rh := http.RedirectHandler("http://example.org", 307)
// 使用 mux.Handle() 将处理程序注册到 Servemux,并映射到路径 /foo
mux.Handle("/foo", rh)
log.Print("FunTester 监听中...") // 修改日志信息,增加 FunTester 相关内容
// 创建一个服务器并开始监听传入的请求,同时将 Servemux 作为参数传递
http.ListenAndServe(":3000", mux)
}
运行该程序:
$ go run main.go
2025/06/24 15:09:43 FunTester 监听中...
如果你向 http://localhost:3000/foo
发出请求,会发现它被成功重定向:
$ curl -IL localhost:3000/foo
HTTP/1.1 307 Temporary Redirect
Content-Type: text/html; charset=utf-8
Location: http://example.org
Date: Mon, 06 Dec 2021 14:10:18 GMT
而其他路径的请求会返回 404 Not Found 错误:
$ curl -IL localhost:3000/bar
HTTP/1.1 404 Not Found
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Mon, 06 Dec 2021 14:22:51 GMT
Content-Length: 19
自定义处理程序
虽然 net/http
提供了一些内置处理程序,但在实际开发中,我们通常需要创建自定义处理程序来满足具体需求。
在 Go 中,任何满足 http.Handler
接口的对象都可以作为处理程序。该接口定义如下:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
简单来说,处理程序必须实现一个 ServeHTTP()
方法,其签名为:
ServeHTTP(http.ResponseWriter, *http.Request)
以下是一个自定义处理程序的示例,它以特定格式返回当前时间:
type timeHandler struct {
format string
}
func (th timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
tm := time.Now().Format(th.format)
w.Write([]byte("FunTester 当前时间是: " + tm)) // 修改返回内容,增加 FunTester 相关内容
}
在这个例子中,我们定义了一个 timeHandler
结构体,并实现了 ServeHTTP()
方法。这样,timeHandler
就满足了 http.Handler
接口,可以作为处理程序使用。
让我们通过一个完整的示例来演示:
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("FunTester 当前时间是: " + tm)) // 修改返回内容,增加 FunTester 相关内容
}
func main() {
mux := http.NewServeMux()
// 初始化 timeHandler,并设置时间格式
th := timeHandler{format: time.RFC1123}
// 将 timeHandler 注册到 Servemux
mux.Handle("/time", th)
log.Print("FunTester 监听中...") // 修改日志信息,增加 FunTester 相关内容
http.ListenAndServe(":3000", mux)
}
运行该程序后,访问 http://localhost:3000/time
,你会收到包含当前时间的响应,例如:
$ curl localhost:3000/time
FunTester 当前时间是: Mon, 06 Dec 2021 15:33:21 CET
函数作为处理程序
对于简单的场景,定义一个结构体来实现处理程序可能显得过于繁琐。幸运的是,我们可以直接使用函数作为处理程序:
func timeHandler(w http.ResponseWriter, r *http.Request) {
tm := time.Now().Format(time.RFC1123)
w.Write([]byte("当前时间是: " + tm))
}
虽然这个函数本身并不满足 http.Handler
接口,但我们可以通过将其转换为 http.HandlerFunc 类型来实现这一点。http.HandlerFunc
是一个内置类型,它可以将任何具有签名 func(http.ResponseWriter, *http.Request)
的函数转换为处理程序。
以下是使用 http.HandlerFunc
的示例:
func main() {
mux := http.NewServeMux()
// 将 timeHandler 转换为 http.HandlerFunc 类型
th := http.HandlerFunc(timeHandler)
// 注册到 Servemux
mux.Handle("/time", th)
log.Print("监听中...")
http.ListenAndServe(":3000", mux)
}
实际上,Go 提供了一个更简洁的方式来注册函数处理程序:使用 mux.HandleFunc() 方法。示例如下:
func main() {
mux := http.NewServeMux()
// 使用 HandleFunc 注册函数处理程序
mux.HandleFunc("/time", timeHandler)
log.Print("监听中...")
http.ListenAndServe(":3000", mux)
}
将变量传递给处理程序
大多数情况下,像这样使用函数作为处理程序效果很好。但是,当情况变得更加复杂时,就会有一些限制。
你可能已经注意到,与之前的方法不同,我们必须在 timeHandler
函数中硬编码时间格式。当你想将信息或变量从 main()
传递给处理程序时,该怎么办?
一种巧妙的方法是将我们的处理程序逻辑放入闭包中,并封闭我们想要使用的变量,如下所示:
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("FunTester 当前时间是: " + tm)) // 修改返回内容,增加 FunTester 相关内容
}
return http.HandlerFunc(fn)
}
func main() {
mux := http.NewServeMux()
// 使用闭包将变量传递给处理程序
th := timeHandler(time.RFC1123)
mux.Handle("/time", th)
log.Print("FunTester 监听中...") // 修改日志信息,增加 FunTester 相关内容
http.ListenAndServe(":3000", mux)
}