注册路由
初学web时,我们常用http.HandleFunc()来注册路由,然后用http.ListenAndServe()来启动服务,接下来让我们分析一下http包内部是如何注册路由的。
除了常用的http.HandleFunc()可以注册路由,还有http.Handle可以注册,先看一下源码。
golang
func Handle(pattern string, handler Handler) {
DefaultServeMux.Handle(pattern, handler)
}
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
对比一下两个函数的不同:1、分别调用了DefaultServeMux的Handle和HandleFunc方法。2、handler参数类型分别为http.Handler接口,和func(ResponseWriter, *Request)类型。说明一下,DefaultServerMux是http包的全局变量,如果不使用默认的复用器
接下来看一下Handle和HandleFunc的主要源码,和Handler接口
golang
// 注册路由
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
...
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e
...
}
// 调用Handle注册路由
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
// 实现了Handler的函数类型
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
可以看到ServeMux.Handle最终实现了路由的注册,mux.m记录了路由与处理器的映射;而ServeMux.HandleFunc将handler参数转换成了HandlerFunc类型,然后调用ServeMux.Handle。
那么问题来了,ServeMux.Handle的handler参数是Handler接口类型,我们调用http.HandleFunc()传入的处理器函数签名是func(ResponseWriter, *Request),我们传入的函数咋就实现了Handler接口?
答案就在于HandlerFunc类型,它实现了Handler接口。我们传入的处理器函数与HandlerFunc类型函数签名是一致的,如果没有HandlerFunc,要注册函数的话,我们就要自己定义结构体,写ServeHTTP方法,实现Handler接口,而有了HandlerFunc我们就可以把这一步省去了,在设计模式中,这叫装饰器模式。
处理请求
ServerMux
使用http.HandleFunc和http.Handle注册的路由都注册到了DefaultServerMux,它也实现了handler接口,那让我们来看一下ServerMux的ServeHTTP方法。
golang
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
mux.Handler()中会调用mux.match(),从ServeMux.m(map[string]muxEntry类型),获取路由和对应处理器函数(我们传入的),然后就可以调用h.ServeHTTP(w,r)来处理对应的请求。
现在已得知我们传入的处理函数是被ServeMux.ServeHTTP()调用,那ServerMus.ServeHTTP()又是怎么被调用的呢?接下来跟踪一下http.ListenAndServe()的源码。
Server
golang
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
用http.ListenAndServe()启动服务的话,handler参数一般传nil,使用DefaultServerMux做处理器,下面的分析都以此为前提。
在函数内部帮我们创建了一个Server结构体,如果想更灵活地使用,可以自己创建Server结构体,调用server.ListenAndServe()。
golang
func (srv *Server) ListenAndServe() error {
if srv.shuttingDown() {
return ErrServerClosed
}
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr) // 监听地址
if err != nil {
return err
}
return srv.Serve(ln)
}
Server.ListenAndServe()中完成了监听地址的绑定,然后再调用Server.Serve()
golang
func (srv *Server) Serve(l net.Listener) error {
...
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {
rw, err := l.Accept()
...
connCtx := ctx
if cc := srv.ConnContext; cc != nil {
connCtx = cc(connCtx, rw)
if connCtx == nil {
panic("ConnContext returned nil")
}
}
c := srv.newConn(rw)
c.setState(c.rwc, StateNew, runHooks)
go c.serve(connCtx)
}
}
Server.Serve中开启了一个for循环来接收连接,并为每一个连接创建contexxt,开一个协程继续处理。
golang
func (c *conn) serve(ctx context.Context) {
c.remoteAddr = c.rwc.RemoteAddr().String()
ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
var inFlightResponse *response
defer func() {
if err := recover(); err != nil && err != ErrAbortHandler {
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
}
if inFlightResponse != nil {
inFlightResponse.cancelCtx()
}
if !c.hijacked() {
if inFlightResponse != nil {
inFlightResponse.conn.r.abortPendingRead()
inFlightResponse.reqBody.Close()
}
c.close()
c.setState(c.rwc, StateClosed, runHooks)
}
}()
...
for {
w, err := c.readRequest(ctx)
...
inFlightResponse = w
serverHandler{c.server}.ServeHTTP(w, w.req) // 这里并不是Handler接口的ServeHTTP方法
inFlightResponse = nil
w.cancelCtx()
if c.hijacked() {
return
}
...
}
...
}
在http包server.go 1991行,嵌套了Server结构体的serverHandler结构体调用ServeHTTP方法,需要注意的是这并不是Handler接口的ServeHTTP方法,我们传入的处理器函数的调用还在其内部。
golang
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux // 如果在创建Server时,Handler参数为nil,就使用DefaultServeMux
// 通过http.ListenAndServe开启服务一般都用DefaultServeMux
}
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
if req.URL != nil && strings.Contains(req.URL.RawQuery, ";") {
var allowQuerySemicolonsInUse int32
req = req.WithContext(context.WithValue(req.Context(), silenceSemWarnContextKey, func() {
atomic.StoreInt32(&allowQuerySemicolonsInUse, 1)
}))
defer func() {
if atomic.LoadInt32(&allowQuerySemicolonsInUse) == 0 {
sh.srv.logf("http: URL query contains semicolon, which is no longer a supported separator; parts of the query may be stripped when parsed; see golang.org/issue/25192")
}
}()
}
handler.ServeHTTP(rw, req) // 调用DefaultServeMux的ServeHTTP
}
最后一行调用DefaultServeMux的ServeHTTP,上文已介绍过,它的作用就是获取到请求对应的处理器函数并执行。
延伸
gin框架是基于http包封装的,gin匹配路由的方式不同于原生包,使用前缀路由树来匹配路由,通过engin.Run启动服务,其内部也是调用的http.ListenAndServe(),那gin是如何应用的自定义匹配方式呢?其实很简单,上文提到,调用http.ListenAndServe()时第二个参数handler是nil的话,会使用DefaultServeMux当做复用器,那engin.Run()中调用时传入gin定义的复用器就好了。
golang
func (engine *Engine) Run(addr ...string) (err error) {
defer func() { debugPrintError(err) }()
if engine.isUnsafeTrustedProxies() {
debugPrint("[WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.\n" +
"Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.")
}
address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
err = http.ListenAndServe(address, engine.Handler()) // engin.Handler()返回gin的自定义处理器
return
}