摘要
由于 Golang 优秀的并发处理, 很多公司使用 Golang 编写微服务. 对于 Golang 来说, 只需要短短几行代码就可以实现一个简单的 Http 服务器. 加上 Golang 的协程, 这个服务器可以拥有极高的性能. 然而, 正是因为代码过于简单, 我们才应该去研究他的底层实现, 做到会用, 也知道为什么这么用.
在本文中, 会以自顶向下的方式, 从如何使用, 到如何实现, 一点点的分析 Golang 中 net/http 这个包中关于 Http 服务器的实现方式. 内容可能会越来越难理解, 作者会尽量把这些源码讲的更清楚一些, 希望对各位有所帮助.
1 创建
首先, 我们以怎么用为起点.
毕竟, 知道了怎么用, 才能一步一步的深入挖掘为什么这么用.
先来看第一种最简单的创建方式 (省略了导包):
- func helloWorldHandler(w http.ResponseWriter, r *http.Request) {
- fmt.Fprintf(w, "Hello World !")
- }
- func main() {
- http.HandleFunc("/", helloWorldHandler)
- http.ListenAndServe(":8000", nil)
- }
其实在这一部分中, 代码应该很容易理解. 就是先做一个映射, 把需要访问的地址, 和访问后执行的函数, 写在一起. 然后再加上监听的端口, 就可以了.
如果你是一个 Java 程序员, 你应该能发觉这个和 Java 中的 Servlet 很相似. 也是创建一个个的 Servlet, 然后注册.
再来看看第二种创建方式, 也一样省略了导包:
- type helloWorldHandler struct {
- content string
- }
- func (handler *helloWorldHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- fmt.Fprintf(w, handler.content)
- }
- func main() {
- http.Handle("/", &helloWorldHandler{content: "Hello World!"})
- http.ListenAndServe(":8000", nil)
- }
在这里, 我们能发现相较于第一种方法, 有些许的改动.
我们定义了一个结构体, 然后又给这个结构体编写了一个方法. 根据我们之前对于接口的概念: 要实现一个接口必须要实现这个接口的所有方法.
那么我们是不是可以推测: 存在这么一个接口 A, 里面有一个名为 ServeHTTP 的方法, 而我们所编写的这个结构体, 他已经实现了这个接口 A 了, 他现在是属于这个 A 类型的一个结构体了.
- type A interface{
- ServeHTTP()
- }
并且, 在 main 函数中关于映射 URI 和方法的参数部分, 需要调用实现了这个接口 A 的一个对象.
带着这个问题, 我们可以继续往下.
2 注册
在第一部分, 我们提到了两种注册方式, 一种是传入一个函数, 一种是传入一个结构体指针.
- http.HandleFunc("/", helloWorldHandler)
- http.Handle("/", &helloWorldHandler{
- content: "Hello World!"
- })
我们来看看 http 包内的源码:
- package http
- func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
- DefaultServeMux.HandleFunc(pattern, handler)
- }
- func Handle(pattern string, handler Handler) {
- DefaultServeMux.Handle(pattern, handler)
- }
先看一下这里的代码, 他们被称为注册函数.
首先研究一下 HandleFunc 这个函数. 在 main 函数中, 调用了这个具有 func(pattern string, handler func(ResponseWriter, *Request)) 签名的函数, 这里的 pattern 是 string 类型的, 指的是匹配的 URI, 这个很容易理解. 第二个参数是一个具有 func(ResponseWriter, *Request) 签名的函数.
然后我们继续看, 在这个函数中, 调用了这个方法:
- func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
- if handler == nil {
- panic("http: nil handler")
- }
- mux.Handle(pattern, HandlerFunc(handler))
- }
我们可以看到, 最终是调用了 DefaultServeMux 对象的 Handle 方法.
好, 先到这里, 我们再看一看刚刚提到的签名为 func (pattern string, handler Handler) 另外一个函数. 在这个函数里面, 同样是调用了 DefaultServeMux 对象的 Handle 方法.
也就是说, 无论我们使用哪种注册函数, 最终调用的都是这个函数:
func (mux *ServeMux) Handle(pattern string, handler Handler)
这里涉及到了两种对象, 第一是 ServeMux 对象, 第二是 Handler 对象.
ServeMux 对象我们一会再聊, 先聊聊 Handler 对象.
- type Handler interface {
- ServeHTTP(ResponseWriter, *Request)
- }
在 Golang 中, Handler 是一种接口类型, 只要实现了 ServeHTTP 这个方法, 那个就可以称这个结构体是 Handler 类型的.
注意到, 在前面有一行代码是这样的:
mux.Handle(pattern, HandlerFunc(handler))
有人可能会想, HandlerFunc func(ResponseWriter, *Request) 这个函数, 是输入一个函数, 返回一个 Handler 类型的对象, 其实这是不对的. 我们来看看这个函数的源码:
- type HandlerFunc func(ResponseWriter, *Request)
- func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
- f(w, r)
- }
我们可以发现, 这个函数, 他是一个结构体类型, 而且这个结构体也是实现了 ServeHTTP 方法的, 也就是说, 这个结构体也是一个 Handler 类型. 所以, 这个方法其实并不是输入一组参数, 返回一个 Handler 类型, 而是他本身就是一个 Handler 类型, 可以直接调用 ServeHTTP 方法.
这里比较绕, 但是相信当你理解了之后, 会感觉妙啊.
说完了 Handler, 我们再来聊聊 ServeMux. 先来看看他的结构:
- type ServeMux struct {
- mu sync.RWMutex
- m map[string]muxEntry
- es []muxEntry // slice of entries sorted from longest to shortest.
- hosts bool // whether any patterns contain hostnames
- }
- type muxEntry struct {
- h Handler
- pattern string
- }
我们先关注一下这个结构里面的 m 字段. 这个字段是一个 map 类型, key 是 URI,value 是 muxEntry 类型. 而这个 muxEntry 类型, 里面包含了一个 Handler 和 URI. 也就是说, 通过这个 m 字段, 我们可以用 URI 找到对应的 Handler 对象.
继续说回上面提到的 func (mux *ServeMux) Handle(pattern string, handler Handler) 方法. 我们已经知道了调用这个方法的对象是 ServeMux, 也知道了这个方法的参数中的 Handler 是什么, 下面让我们来看看这个方法的详细实现:
- func (mux *ServeMux) Handle(pattern string, handler Handler) {
- mux.mu.Lock()
- defer mux.mu.Unlock()
- if pattern == "" {
- panic("http: invalid pattern")
- }
- if handler == nil {
- panic("http: nil handler")
- }
- if _, exist := mux.m[pattern]; exist {
- panic("http: multiple registrations for" + pattern)
- }
- if mux.m == nil {
- mux.m = make(map[string]muxEntry)
- }
- 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
- }
- }
在这个方法中, 我们可以看到, Handle 方法会先判断传入的 URI 和 handler 是否合法, 然后判断这个 URI 对应的处理器是否已经注册, 然后将这个 URI 和 handler 对应的 map 写入 ServeMux 对象中.
注意, 这里还有一个步骤. 如果这个 URI 是以 / 结尾的, 将会被送入 es 数组中, 按长度排序. 至于为什么会这么做, 我们在后面的内容将会提到.
说完了这些, 我们应该可以猜到这个 ServeMux 对象的作用了. 他可以存储我们注册的 URI 和 Handler, 以实现当有请求进来的时候, 可以委派给相对应的 Handler 的功能.
考虑到这个功能, 那么我们也可以推断出, 这个 ServeMux 也是一个 Handler, 只不过他和其他的 Handler 不同. 其他的 Handler 处理的是具体的请求, 而这个 ServeMux 处理的是请求的分配.
所以, ServeMux 也实现了 ServeHTTP 方法, 他也是一个 Handler. 而对于他是怎么实现 ServeHTTP 方法的, 我们也在后面的内容提到.
3 监听
现在, 让我们来聊聊 main 函数中的第二行:
http.ListenAndServe(":8000", nil)
按照惯例, 我们来看一看这个方法的实现:
- func ListenAndServe(addr string, handler Handler) error {
- server := &Server{Addr: addr, Handler: handler}
- return server.ListenAndServe()
- }
这里的 Server, 是一个复杂的结构体, 里面包含了设置服务器的很多参数, 但是这里我们只聊 Addr 和 Handler 这两个属性.
Addr 很容易理解, 就是这个服务器所监听的地址.
Handler 是处理器, 负责把请求分配给各个对应的 handler. 在这里留空, 则使用 Golang 默认的处理器, 也就是上文中我们提到的实现了 ServeHTTP 方法的 ServeMux.
知道了这些, 我们继续往下看 server.ListenAndServe() 的实现:
- 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)
- }
这里比较重要的有两行, 第一是 ln, err := net.Listen("tcp", addr), 也就是说, 开始监听 addr 这个地址的 tcp 连接.
然后, 调用 srv.Serve(ln), 我们来看看代码 (省略部分, 只保留与本文有关的逻辑):
- func (srv *Server) Serve(l.NET.Listener) error {
- ...
- for{
- ...
- c := srv.newConn(rw)
- c.setState(c.rwc, StateNew) // before Serve can return
- go c.serve(connCtx)
- }
- }
简单来讲, 在这个方法中, 有一个死循环, 他不断接收新的连接, 然后启动一个协程, 处理这个连接. 我们来看看 c.serve(connCtx) 的具体实现:
- func (c *conn) serve(ctx context.Context) {
- ...
- serverHandler{c.server}.ServeHTTP(w, w.req)
- ...
- }
省略其他所有的细节, 最关键的就是这一行代码了, 然后我们再看看这个 ServeHTTP 方法. 注意, 这里的 c.server, 还是指的是最开始的那个 Server 结构体. 坚持一下下, 马上就到最关键的地方啦:
- 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)
- }
这里的 ServeHTTP 方法逻辑很容易看出, 如果最开始没有定义一个全局处理的 Handler, 则会使用 Golang 的默认 handler:DefaultServeMux.
假设, 我们这里使用的是 DefaultServeMux, 执行 ServeHTTP 方法. 说到这里你是否有印象, 我们在上一个章节里提到的:
所以, ServeMux 也实现了 ServeHTTP 方法, 他也是一个 Handler. 而对于他是怎么实现 ServeHTTP 方法的, 我们也在后面的内容提到.
就是这里, 对于 ServeMux 来说, 他就是一个处理请求分发的 Handler.
如果你学过 Java, 我跟你说他和 ServletDispatcher 很相似, 你应该能理解吧.
4 处理
到了这里, 就是最后一步了, 我们来看看这里处理请求分发的 ServeHTTP 方法具体实现:
- func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
- ...
- h, _ := mux.Handler(r)
- h.ServeHTTP(w, r)
- }
在省去其他细节之后我们应该可以推断, 这个 mux.Handler(r) 方法返回的 h, 应该是所请求的 URI 所对应的 Handler. 然后, 执行这个 Handler 所对应的 ServeHTTP 方法. 我们来看看 mux.Handler(r) 这个方法:
- func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
- ...
- host := stripHostPort(r.Host)
- path := cleanPath(r.URL.Path)
- ...
- return mux.handler(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
- }
到了这里, 代码就变得简洁明了了. 重点就是这个 mux.match 方法, 会根据地址, 来返回对应的 Handler. 我们来看看这个方法:
- func (mux *ServeMux) match(path string) (h Handler, pattern string) {
- // Check for exact match first.
- v, ok := mux.m[path]
- if ok {
- return v.h, v.pattern
- }
- // Check for longest valid match. mux.es contains all patterns
- // that end in / sorted from longest to shortest.
- for _, e := range mux.es {
- if strings.HasPrefix(path, e.pattern) {
- return e.h, e.pattern
- }
- }
- return nil, ""
- }
这段代码也应该很容易理解. 如果在 ServeMux 中存储了 key 为这个 URI 的路由规则的映射, 则直接返回这个 URI 对应的 Handler.
否则, 就去匹配 es 数组. 还记得吗, 这个数组是之前注册路由的时候提到的, 如果 URI 是以 / 结尾的, 就会把这个路由映射添加到 es 数组中, 并由长到短进行排序.
这样的作用是, 可以优先匹配到最长的 URI, 以达到近似匹配的时候能够匹配到最合适的路由的目的.
至此, 返回对应的 Handler, 然后执行, 就成功的实现了处理相对应的请求了.
写在最后
首先, 谢谢你能看到这里!
不知道你有没有理解我所说的内容, 希望这篇文章可以给你一些帮助.
其实写这篇文章的目的是这样的, 学完了 Golang 的基础之后作者准备开始研究 Golang web. 但是查找各种资料后发现, 并没有找到一条很合适的学习路线. 然后本来作者打算去直接研究一个框架, 如 MeeGo,Gin 等. 但是又考虑到, 框架只是用来解决问题的, 学会了框架却不知道基础内容, 有种知其然不知其所以然的感觉.
所以, 作者打算从 Golang 的 net/http 包的源码开始, 慢慢去了解怎么用原生的 Go 语言去建立一个 HTTP 服务器, 然后去了解一下怎么进行缓存, 做持久化等, 这也是作者思考之后决定的一条学习路线. 当能够把这些内容都研究明白之后, 再去研究框架, 去看这些框架是怎么解决问题的, 可能才是比较合适的.
当然了, 作者也是刚入门. 所以, 可能会有很多的疏漏. 如果在阅读的过程中, 有哪些解释不到位, 或者理解出现了偏差, 也请你留言指正.
再次感谢~
来源: https://www.cnblogs.com/hongjijun/p/12702292.html