最近在看golang的中间件时发现http中handler的实现比较特殊。以前一直以为通过 http.HandleFunc 向http注册回调函数时,http会记录我们注册的函数,然后再需要的时候来调用这个函数。看了一些middlware的相关资料才发现,原来都是通过 ServeHTTP 这个接口来实现注册函数的调用的。

HandleFunc 基础

  package main

  import "net/http"

  func Hello(w http.ResponseWriter, r *http.Request) {
          w.Write([]byte("Hello"))
  }

  func main() {
          http.HandleFunc("/hello", Hello)
          http.ListenAndServe(":8080",nil)
  }

代码首先通过 http.HandleFunc 向路由 /hello 注册了一个处理函数 Hello , 然后通过 ListenAndServe 函数启动Web服务。按照C语言编码的思路,我会觉得http内部会维护一个函数 指针 对象,这个对象记录我们注册的函数,然而通过阅读 http 的代码可以得知,源码中并没有直接存储函数并调用这个函数的地方,http处理都是通过调用接口函数 ServeHTTP 实现的。

Handler 接口

在深入理解 http.HandleFunc 之前,我们需要明确,http的调用都时通过接口进行的,没有例外。

http 的处理接口约定如下:

  type Handler interface {
          ServeHTTP(ResponseWriter, *Request)
  }

这就意味着,我们需要定义一个数据类型,并为这个数据类型实现接口函数 ServeHTTP

  package main

  import "net/http"

  type X struct{}

  func (x *X)ServeHTTP(w http.ResponseWriter, r *http.Request) {
      w.Write([]byte("Hello"))
  }

  func main() {
      http.Handle("/hello", &X{})
      http.ListenAndServe(":8080", nil)
  }

那么在回过头来看看我们第一个例子,为何我们定义新的数据类型,也没有实现 ServeHTTP 这个接口,第一个例子也能正常工作呢?那么我们就需要看看 http.HandleFunc 这个函数做了什么。

HandleFunc 分析

  func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
          DefaultServeMux.HandleFunc(pattern, handler)
  }

  func(mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
          if handler == nil {
                  panic("http: nil handler")
          }
          mux.Handle(pattern, HandlerFunc(handler))
  }

  type HandlerFunc func(ResponseWriter, *Request)

  func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
          f(w, r)
  }

首先 http.HandleFunc 调用了默认路由处理器的 DefaultServeMux.HandleFunc ,在这个处理函数中用调用了 mux.Handle 函数,看一下 mux.Handle 的函数原型:

   func (mux *ServeMux) Handle(pattern string, handler Handler)

请注意这里的第二个参数类型是 Handler 这个就是我们一开始说需要实现的接口:

  type Handler interface {
          ServeHTTP(ResponseWriter, *Request)
  }

HandlerFunc

代码通过 HandlerFunc 将我们传入的函数强转成了 HandlerFunc 类型,因为我们的 Hello 函数的签名和 HandlerFunc 定义函数的签名是一致的,所以这个强转是没有问题的。

同时,代码中为 HandleFunc 实现了http需要的接口 ServeHTTP ,这个接口只做了一件事,那就是调用函数自身,简单的说,就是哪个函数被强转了,在ServeHTTP被调用时,就等同于调用这个被强转的函数。

  func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
          f(w, r)
  }

总结

想要理解 HandleFunc 运行原理,可以按如下顺序来理解:

  1. type Handler interface

  2. func Handle(pattern string, handler Handler)

  3. type HandlerFunc func(ResponseWriter, *Request)

  4. func HandleFunc(pattern string, handler func(ResponseWriter, *Request))

首先,要明白所有的HTTP调用都是基于实现 Handler 接口的,这个接口约定所有对象都必须实现 ServeHTTP 函数。

其次,http 内部是通过 http.Handle()函数来实现的,http.HandlerFunc只是一个封装。http.Handle()的第二个参数必须是 Handler 接口类型。

再次,想把普通的 func(ResponseWrite, *Request) 函数转换成实现 ServeHTTP 接口的函数,可以通过 HandlerFunc 进行转换。

最后,用户不需要自己进行函数类型转换,因为http提供了封装函数 HandleFunc ,用户只要确保函数签名一致即可。