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

使用 Go 处理 HTTP 请求主要涉及两件事:ServeMuxes 和 Handlers 。
ServeMux[1] 本质上是一个 HTTP 请求路由器(或多路复用器) 。它将传入的请求与预定义的 URL 路径列表进行比较,并在找到匹配时调用路径的关联 handler 。
handler 负责写入响应头和响应体 。几乎任何对象都可以是 handler,只要它满足http.Handler[2] 接口即可 。在非专业术语中,这仅仅意味着它必须是一个拥有以下签名的 ServeHTTP 方法:
ServeHTTP(http.ResponseWriter, *http.Request)Go 的 HTTP 包附带了一些函数来生成常用的 handler,例如FileServer[3],NotFoundHandler[4] 和RedirectHandler[5] 。让我们从一个简单的例子开始:
$ mkdir handler-example$ cd handler-example$ touch main.go

File: main.go
package mainimport ( "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)}【Go 如何处理 HTTP 请求?掌握这两点即可】让我们快速介绍一下:
  • 在 main 函数中,我们使用http.NewServeMux[6] 函数创建了一个空的 ServeMux 。
  • 然后我们使用http.RedirectHandler[7] 函数创建一个新的 handler 。该 handler 将其接收的所有请求 307 重定向到 http://example.org 。
  • 接下来我们使用mux.Handle[8] 函数向我们的新 ServeMux 注册它,因此它充当 URL 路径 /foo 的所有传入请求的 handler 。
  • 最后,我们创建一个新服务并使用http.ListenAndServe[9] 函数开始监听传入的请求,并传入 ServeMux 给这个方法以匹配请求 。
继续运行应用程序:
$ go run main.goListening...并在浏览器中访问http://localhost:3000/foo[10] 。你会发现请求已经被成功重定向 。
你可能已经注意到了一些有趣的东西:ListenAndServe 函数的签名是 ListenAndServe(addr string, handler Handler),但我们传递了一个 ServeMux 作为第二个参数 。
能这么做是因为 ServeMux 类型也有一个 ServeHTTP 方法,这意味着它也满足 Handler 接口 。
对我而言,它只是将 ServeMux 视为一种特殊的 handler,而不是把响应本身通过第二个 handler 参数传递给请求 。这不像刚刚听说时那么惊讶 - 将 handler 链接在一起在 Go 中相当普遍 。
自定义 handler
我们创建一个自定义 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 mainimport ( "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[11] 。
另请注意,我们可以轻松地在多个路径中重复使用 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)}普通函数作为 handler
对于简单的情况(如上例),定义新的自定义类型和 ServeHTTP 方法感觉有点啰嗦 。让我们看看另一个方法,我们利用 Go 的http.HandlerFunc[12] 类型来使正常的函数满足 Handler 接口 。
任何具有签名 func(http.ResponseWriter, *http.Request) 的函数都可以转换为 HandlerFunc 类型 。这很有用,因为 HandleFunc 对象带有一个内置的 ServeHTTP 方法 - 这非常巧妙且方便 - 执行原始函数的内容 。


推荐阅读