从http.ListenAndServe开始看golang源码

前言

我们在直接使用golang系统package搭建的http服务时,总会使用类似如下的代码:

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

但你知道这些代码背后是以什么的逻辑运行的吗?

http.HandleFunc声明的HelloServer是怎么被系统调用到的呢?

为何http.ListenAndServe的handler参数设置为nil呢?

今天我们就从http.ListenAndServe入口查看源码背后的逻辑。

为避免文章大片的代码影响可读性,以下分析到的源码只会保留重要部分,其余部分省略。

一、http.ListenAndServe

1.http.ListenAndServe

(1)ListenAndServe

ListenAndServe是整个服务运行的总入口,我们从此出发。

代码如下:

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

此func构造了Server,并调用了server的ListenAndServe继续处理。

看下func上方的注释及示例,注释中这一句比较重要:

// Handler is typically nil, in which case the DefaultServeMux is used.
Handler通常是nil,此时,会默认使用DefaultServeMux。

这也是我们示例中为何Handler为nil的原因,至于DefaultServeMux很重要,不过我们放到第二部分再讲。

(2)Server

Server的结构如下:

type Server struct {
    Addr      string      // TCP address to listen on, ":http" if empty
    Handler   Handler     // handler to invoke http.DefaultServeMux if nil
    TLSConfig *tls.Config // optional TLS config, used by ListenAndServeTLS
...

此处再次提到Handler为nil时会使用DefaultServeMux,由此可见DefaultServeMux的重要性。至于,何时使用DefaultServeMux,看后面的源码分析DefaultServeMux

2.server.ListenAndServe

监听tcp端口

func (srv *Server) ListenAndServe() error {
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
    return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

此段代码主要是tcp端口的监听Listener的获取及转为tcpKeepAliveListener后继续执行。

3.srv.Serve

新建连接,并发处理每个连接。

...
for {
        ...
        tempDelay = 0
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve(ctx)
    }
...

此处有个无限循环来处理各个连接,并最终新启协程来处理每个连接,这也就是服务端可以同时处理多请求的缘由。

我们重点看:c := srv.newConn(rw)

// Create new connection from rwc.
func (srv *Server) newConn(rwc net.Conn) *conn {
    c := &conn{
        server: srv,
        rwc:    rwc,
    }
    if debugServerConnections {
        c.rwc = newLoggingConn("server", c.rwc)
    }
    return c
}

通过此func,将server及连接net.Conn传入*conn,意味者针对每一连接,srv都会新建一个conn来处理,随后开启新的goroutine并发处理。

4.srv.Serve

(1)Serve

处理具体的请求。

这部分最重要的就一句

func (c *conn) serve(ctx context.Context) {
...
    serverHandler{c.server}.ServeHTTP(w, w.req)

...
}

就是这一句负责路由的最终分发处理。

(2)server.Handler

查询对应pattern的HandleFunc

// serverHandler delegates to either the server's Handler or
// DefaultServeMux and also handles "OPTIONS *" requests.
type serverHandler struct {
    srv *Server
}

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    handler := sh.srv.Handler
    if handler == nil {
        handler = DefaultServeMux //此处重点
    }
    if req.RequestURI == "*" && req.Method == "OPTIONS" {
        handler = globalOptionsHandler{}
    }
    handler.ServeHTTP(rw, req)
}

此func中sh.srv即为最开始提到的Server,sh.srv.Handler即为http.ListenAndServe中传入的handler。同时,此方法中还说明了之前反复提到的问题,即

(1)当传入的handler为nil时,将默认使用DefaultServeMux,这就是问题的根本原因。
(2)当传入的handler不为nil时,将直接交由我们自定义的Handler直接处理。那我们如何自定义Handler呢?
    mux := http.NewServeMux()
    mux.HandleFunc("/hello", HelloServer)
    http.ListenAndServe(":12345", mux)
(3)通过(2)中自定义的Handler,我们可以看出,自定义Handler显然比系统自动构建的要麻烦,同时还增加了代码及逻辑,调用更内部的ServeMux,明显增加了学习成本。因此,系统建议我们设置为nil。

因此,为了了解更多的内容,我们还需要看DefaultServeMux的实现了。

二、http.HandleFunc与http.DefaultServeMux

1.HandleFunc

调用http.Func后发生了什么?

(1)http.HandleFunc

交由DefaultServeMux处理。

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

mux进一步处理pattern及handler,此处也对我们的handler转换为HandleFunc,为最后调用ServerHTTP做准备。

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    mux.Handle(pattern, HandlerFunc(handler))
}
(3)mux.Handle

存储pattern及handler信息到mux中

func (mux *ServeMux) Handle(pattern string, handler Handler) {
    ...
    mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}
    ...
}


由此可以看到我们自定义的handler转为HandlerFunc后,根据pattern的不同,然后存储在mux的m中,m格式是map[string]muxEntry,里面存储着pattern对应的muxEntry,而muxEntry中存储着patternHandler

type muxEntry struct {
    explicit bool
    h        Handler
    pattern  string
}

2.DefaultServeMux

(1)DefaultServeMux实际是类型是ServeMux
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux

ServeMux针对ServeHTTP的实现如下:

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
    ...
    h, _ := mux.Handler(r)
    h.ServeHTTP(w, r)
}
(2)mux.Handler

此处针对r,mux的Handler处理如下,主要是针对重定向和一般的处理,此处我们只讨论一般的处理方式。

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
    ...
    return mux.handler(r.Host, r.URL.Path)
}
(3)mux.handler

mux.handler则是获取host和path组成的pattern对应的Handler。

func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
    mux.mu.RLock()
    defer mux.mu.RUnlock()

    // Host-specific pattern takes precedence over generic ones
    if mux.hosts {
        h, pattern = mux.match(host + path)
    }
    if h == nil {
        h, pattern = mux.match(path)
    }
    if h == nil {
        h, pattern = NotFoundHandler(), ""
    }
    return
}
(4)mux.match

mux.match则是根据host和path的来匹配pattern和Handler。

func (mux *ServeMux) match(path string) (h Handler, pattern string) {
    var n = 0
    for k, v := range mux.m {
        if !pathMatch(k, path) {
            continue
        }
        if h == nil || len(k) > n {
            n = len(k)
            h = v.h //此处即为muxEntry中的Handler
            pattern = v.pattern
        }
    }
    return
}

里面最终要的一句:

h = v.h

此处即为muxEntry中的Handler,根据之前我们关于muxEntry相关的了解知道,muxEntry中的h即为我们在http.HandleFunc中传入的自定义HandleFunc。

如示例中:

    http.HandleFunc("/hello", HelloServer)

当我们请求/hello时,即可在mux.m根据/hello此key即可获取对应的HandleFunc HelloServer。

三、总结

最后,我们再看下前言中提到的两个问题,其实是一个问题:

1.http.HandleFunc将pattern及我们自定义的handler存储在DefaultServeMux的一个map中。

2.当http.ListenAndServe的handler为nil时,系统会从DefaultServeMux存储信息的map中匹配pattern获取对应的handler,进而处连接请求。

下图是一个简易的思维导图:
思维导图

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值