【GO】28.golang http包源码

Go 语言标准库 net/http 包提供了非常易用的接口,如下所示,我们可以利用标准库提供的功能快速搭建新的 HTTP 服务:

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
}

func main() {
    http.HandleFunc("/", handler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Go

上述的 main 函数只调用了两个标准库提供的函数,它们分别是用于注册处理器的 net/http.HandleFunc 函数和用于监听和处理器请求的 net/http.ListenAndServe,多数的服务器框架都会包含这两类接口,分别负责注册处理器和处理外部请求,这一种非常常见的模式,我们在这里也会按照这两个维度介绍标准库如何支持 HTTP 服务器的实现。

注册处理器 #

HTTP 服务是由一组实现了 net/http.Handler 接口的处理器组成的,处理 HTTP 请求时会根据请求的路由选择合适的处理器:

http-server-and-handlers

图 9-11 HTTP 服务与处理器

当我们直接调用 net/http.HandleFunc 注册处理器时,标准库会使用默认的 HTTP 服务器 net/http.DefaultServeMux 处理请求,该方法会直接调用 net/http.ServeMux.HandleFunc

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

Go

上述方法会将处理器转换成 net/http.Handler 接口类型调用 net/http.ServeMux.Handle 注册处理器:

func (mux *ServeMux) Handle(pattern string, handler Handler) {
	if _, exist := mux.m[pattern]; exist {
		panic("http: multiple registrations for " + pattern)
	}

	e := muxEntry{h: handler, pattern: pattern}
	mux.m[pattern] = e
	if pattern[len(pattern)-1] == '/' {
		mux.es = appendSorted(mux.es, e)
	}

	if pattern[0] != '/' {
		mux.hosts = true
	}
}

Go

路由和对应的处理器会被组成 net/http.DefaultServeMux,该结构会持有一个 net/http.muxEntry 哈希,其中存储了从 URL 到处理器的映射关系,HTTP 服务器在处理请求时就会使用该哈希查找处理器。

ServeMux 的自定义

我们前面小节讲述 conn.server 的时候,其实内部是调用了 http 包默认的路由器,通过路由器把本次请求的信息传递到了后端的处理函数。那么这个路由器是怎么实现的呢?

它的结构如下:

type ServeMux struct {
    mu sync.RWMutex   // 锁,由于请求涉及到并发处理,因此这里需要一个锁机制
    m  map[string]muxEntry  // 路由规则,一个 string 对应一个 mux 实体,这里的 string 就是注册的路由表达式
    hosts bool // 是否在任意的规则中带有 host 信息
}

下面看一下 muxEntry

type muxEntry struct {
    explicit bool   // 是否精确匹配
    h        Handler // 这个路由表达式对应哪个 handler
    pattern  string  // 匹配字符串
}

接着看一下 Handler 的定义

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)  // 路由实现器
}

Handler 是一个接口,但是前一小节中的 sayhelloName 函数并没有实现 ServeHTTP 这个接口,为什么能添加呢?原来在 http 包里面还定义了一个类型 HandlerFunc, 我们定义的函数 sayhelloName 就是这个 HandlerFunc 调用之后的结果,这个类型默认就实现了 ServeHTTP 这个接口,即我们调用了 HandlerFunc (f), 强制类型转换 f 成为 HandlerFunc 类型,这样 f 就拥有了 ServeHTTP 方法。
 

type HandlerFunc func(ResponseWriter, *Request)

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

处理请求 #

标准库提供的 net/http.ListenAndServe 可以用来监听 TCP 连接并处理请求,该函数会使用传入的监听地址和处理器初始化一个 HTTP 服务器 net/http.Server,调用该服务器的 net/http.Server.ListenAndServe 方法:

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

Go

net/http.Server.ListenAndServe 会使用网络库提供的 net.Listen 监听对应地址上的 TCP 连接并通过 net/http.Server.Serve 处理客户端的请求:

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

Go

net/http.Server.Serve 会在循环中监听外部的 TCP 连接并为每个连接调用 net/http.Server.newConn 创建新的 net/http.conn,它是 HTTP 连接的服务端表示:

func (srv *Server) Serve(l net.Listener) error {
	l = &onceCloseListener{Listener: l}
	defer l.Close()

	baseCtx := context.Background()
	ctx := context.WithValue(baseCtx, ServerContextKey, srv)
	for {
		rw, err := l.Accept()
		if err != nil {
			select {
			case <-srv.getDoneChan():
				return ErrServerClosed
			default:
			}
			...
			return err
		}
		connCtx := ctx
		c := srv.newConn(rw)
		c.setState(c.rwc, StateNew) // before Serve can return
		go c.serve(connCtx)
	}
}

Go

创建了服务端的连接之后,标准库中的实现会为每个 HTTP 请求创建单独的 Goroutine 并在其中调用 net/http.Conn.serve 方法,如果当前 HTTP 服务接收到了海量的请求,会在内部创建大量的 Goroutine,这可能会使整个服务质量明显降低无法处理请求。

func (c *conn) serve(ctx context.Context) {
	c.remoteAddr = c.rwc.RemoteAddr().String()

	ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
	ctx, cancelCtx := context.WithCancel(ctx)
	c.cancelCtx = cancelCtx
	defer cancelCtx()

	c.r = &connReader{conn: c}
	c.bufr = newBufioReader(c.r)
	c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)

	for {
		w, _ := c.readRequest(ctx)
		serverHandler{c.server}.ServeHTTP(w, w.req)
		w.finishRequest()
		...
	}
}

Go

上述代码片段是我们简化后的连接处理过程,其中包含读取 HTTP 请求、调用 Handler 处理 HTTP 请求以及调用完成该请求。读取 HTTP 请求会调用 net/http.Conn.readRequest,该方法会从连接中获取 HTTP 请求并构建一个实现了 net/http.ResponseWriter 接口的变量 net/http.response,向该结构体写入的数据都会被转发到它持有的缓冲区中:

func (w *response) write(lenData int, dataB []byte, dataS string) (n int, err error) {
	...
	w.written += int64(lenData)
	if w.contentLength != -1 && w.written > w.contentLength {
		return 0, ErrContentLength
	}
	if dataB != nil {
		return w.w.Write(dataB)
	} else {
		return w.w.WriteString(dataS)
	}
}

Go

解析了 HTTP 请求并初始化 net/http.ResponseWriter 之后,我们就可以调用 net/http.serverHandler.ServeHTTP 查找处理器来处理 HTTP 请求了:

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)
}

Go

如果当前的 HTTP 服务器中不包含任何处理器,我们会使用默认的 net/http.DefaultServeMux 处理外部的 HTTP 请求。

net/http.ServeMux 是一个 HTTP 请求的多路复用器,它可以接收外部的 HTTP 请求、根据请求的 URL 匹配并调用最合适的处理器:

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

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
    if r.Method != "CONNECT" {
        if p := cleanPath(r.URL.Path); p != r.URL.Path {
            _, pattern = mux.handler(r.Host, p)
            return RedirectHandler(p, StatusMovedPermanently), pattern
        }
    }   
    return mux.handler(r.Host, r.URL.Path)
}

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
}

Go

经过一系列的函数调用,上述过程最终会调用 HTTP 服务器的 net/http.ServerMux.match,该方法会遍历前面注册过的路由表并根据特定规则进行匹配:

func (mux *ServeMux) match(path string) (h Handler, pattern string) {
	v, ok := mux.m[path]
	if ok {
		return v.h, v.pattern
	}

	for _, e := range mux.es {
		if strings.HasPrefix(path, e.pattern) {
			return e.h, e.pattern
		}
	}
	return nil, ""
}

Go

如果请求的路径和路由中的表项匹配成功,我们会调用表项中对应的处理器,处理器中包含的业务逻辑会通过 net/http.ResponseWriter 构建 HTTP 请求对应的响应并通过 TCP 连接发送回客户端。

Go 代码的执行流程

通过对 http 包的分析之后,现在让我们来梳理一下整个的代码执行过程。

    首先调用 Http.HandleFunc

    按顺序做了几件事:

    1 调用了 DefaultServeMux 的 HandleFunc

    2 调用了 DefaultServeMux 的 Handle

    3 往 DefaultServeMux 的 map [string] muxEntry 中增加对应的 handler 和路由规则

    其次调用 http.ListenAndServe (":9090", nil)

    按顺序做了几件事情:

    1 实例化 Server

    2 调用 Server 的 ListenAndServe ()

    3 调用 net.Listen ("tcp", addr) 监听端口

    4 启动一个 for 循环,在循环体中 Accept 请求

    5 对每个请求实例化一个 Conn,并且开启一个 goroutine 为这个请求进行服务 go c.serve ()

    6 读取每个请求的内容 w, err := c.readRequest ()

    7 判断 handler 是否为空,如果没有设置 handler(这个例子就没有设置 handler),handler 就设置为 DefaultServeMux

    8 调用 handler 的 ServeHttp

    9 在这个例子中,下面就进入到 DefaultServeMux.ServeHttp

    10 根据 request 选择 handler,并且进入到这个 handler 的 ServeHTTP

    mux.handler(r).ServeHTTP(w, r)

    11 选择 handler:

    A 判断是否有路由能满足这个 request(循环遍历 ServeMux 的 muxEntry)

    B 如果有路由满足,调用这个路由 handler 的 ServeHTTP

    C 如果没有路由满足,调用 NotFoundHandler 的 ServeHTTP

参考:

https://draveness.me/golang/docs/part4-advanced/ch09-stdlib/golang-net-http/

https://learnku.com/docs/build-web-application-with-golang/034-gos-http-package-detailed-solution/3171

猜你喜欢

转载自blog.csdn.net/chen_peng7/article/details/118731624