七叶笔记 » golang编程 » 谈谈 Golang HTTP服务器实现原理

谈谈 Golang HTTP服务器实现原理

前言

关于Golang HTTP服务器实现原理,本文将通过下面两点来讲述,希望能帮到大家!

  • 1.如何创建一个HTTP服务器
  • 2.HTTP服务器实现原理

如何创建一个HTTP服务器

创建一个 HTTP服务器 的步骤:

  • 1.创建一个处理器(实际的业务逻辑)
  • 2.创建一个路由器并与处理器进行绑定/注册(这样才能根据URL匹配到对应的函数)
  • 3.启动HTTP服务器,并监听指定端口

对于处理器的实现,其实只有两种,一是使用 处理器函数 实现,二是创建一个结构体,并 实现ServeHTTP 方法。

而对于处理器与路由器的绑定方式,一种是通过HandleFunc方法直接绑定处理器(如写法一);一种是通过 Handle方法变向绑定(如写法二)。

使用处理器函数实现

 // 使用处理器函数实现HTTP服务器 (写法一)
func index(w http.ResponseWriter, r *http.Request) {
    _, err := w.Write([]byte("Hello World"))
    if err != nil {
        fmt.Println("err", err)
    }
}

func httpServerByFunc() {
    http.HandleFunc("/", index)
    err := http.ListenAndServe(":5000", nil)
    if err != nil {
        panic(err)
    }
}


// 使用处理器函数实现HTTP服务器 (写法二)
func httpServerByFunc2()  {
    http.Handle("/", http.HandlerFunc(index))
    err := http.ListenAndServe(":5000", nil)
    if err != nil {
        panic(err)
    }
}

// 使用处理器函数实现HTTP服务器 (写法三)
func httpServerByFunc3() {
    // 匿名函数
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        _, err := w.Write([]byte("Hello World"))
        if err != nil {
            fmt.Println("err", err)
        }
    })
    err := http.ListenAndServe(":5000", nil)
    if err != nil {
        panic(err)
    }
}
  

使用处理器结构体实现

 type MyHandler struct {

}

// 实现ServeHTTP方法
func (my *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    _, err := w.Write([]byte("Hello World"))
    if err != nil {
        fmt.Println("err", err)
    }
}

func httpServerByStruct()  {
    mux := http.NewServeMux()
    myHandler := MyHandler{}
    mux.Handle("/", &myHandler)

    // 注意这里传的是mux (mux也是一个Handler)
    err := http.ListenAndServe(":5000", mux)
    if err != nil {
        panic(err)
    }
}
  

注:上面的例子一开始看不懂没关系,可以先看下面的原理,然后再返回来看,就能理解了。

HTTP服务器实现原理

关于 HTTP服务器实现原理,本质上是一个 如何创建路由器,并如何将路由器与处理器绑定 的过程。(个人觉得比较核心的一个问题)

Golang HTTP服务器的实现不算很复杂,但是比较蛋疼的一个点是它的函数名(或者是函数签名)非常接近,所以一开始的时候会非常懵,典型的如 Handler、Handle、HandlerFunc以及HandleFunc,这四者的区别会在下文中讲到,注意看源码的时候不要看错了。

如何创建路由

创建路由有两种方式,第一是使用默认路由,即 DefaultServeMux,第二是使用 NewServeMux方法创建一个路由,这两种方式没有本质的区别,只不过Golang 一贯的写法 就是定义一个路由结构体,并同时提供默认路由,以及自定义路由的方法。

HTTP路由器的定义如下:

 // ServeMux is an HTTP request multiplexer.
// It matches the URL of each incoming request against a list of registered
// patterns and calls the handler for the pattern that
// most closely matches the URL.
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
}
  

上面的注释翻译后就是:ServeMux是一个HTTP请求多路复用器,也就是我们常说的路由器,它会将请求的URL与注册的URL进行匹配,匹配成功的话就交由 处理器(Handler) 处理。

路由器与处理器的绑定

路由器与处理器的绑定,主要用到 Handler 和 Handle。

首先从语义上看,Handler表示的是一个名词,代表 处理器 ;而Handle表示的是一个动词,代表 处理 。与之相对应的还有 HandlerFunc 和 HandleFunc。

接着,我们从源码上看 Handler 和 Handle的区别:

 // A Handler responds to an HTTP request.
// Handler,即处理器,接收请求并响应
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

// Handle registers the handler for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
// 注册Handler
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }

// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)

// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}  

上面列举了四个迷惑性比较大的函数,或者接口,它们的作用如下: 1.Handler接口:从技术看,它的确是一个接口,并包含 ServeHTTP 方法;但从逻辑上看,它是一个处理器,作用是 接收HTTP请求并响应 ,同时,只要有结构体实现了 ServeHTTP 方法,它就可以看做一个处理器。

2.HandlerFunc类型:从源码注释可以看到,它是一个适配器,作用是 将一个普通函数转变为一个处理器 。为什么要这样设计?因为之前那种创建处理器的方式太麻烦,也不够简洁,需要创建一个结构体,并实现 ServeHTTP 方法。

这也是为什么我们常见的处理器都是这样的:

 func index(w http.ResponseWriter, r *http.Request) {
}  

那 HandlerFunc 是如何将普通函数转换成一个处理器呢?

答案就在下面的源码中,HandlerFunc虽然是一个类型,但在这里个人觉得有点结构体的味道,且它实现 ServeHTTP 方法, 也就意味着 HandlerFunc 也是一个Handler ,这也是为什么一个普通函数能作为一个处理器的原因。

 type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
// 调用我们实际的handler函数
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}
  

3.Handle和HandleFunc:这两个方法的作用都是一致的,都是将处理器注册到路由器中,它们的区别主要是HandleFunc是在 Handle之上再封装一层,仅此而已(可以通过查看源码得证)

我们可以对以上三点进行一个简单总结:ServeHTTP 与 HandlerFunc 的作用是生成一个 Handler,而 Handle 和 HandleFunc 则是将生成的Handler与路由器进行绑定,或者也可以说是 注册

最后的结论如下:
1.HTTP服务器的实现涉及到路由器,处理器,以及它们之间的映射关系
2.路由器有两种形式,一是使用默认的路由(DefaultServeMux),二是使用 NewServeMux 方法创建一个路由,两者无本质区别
3.Handler表示处理器,它的作用是 接收HTTP请求,并响应 ,也就我们写业务逻辑的地方,上面例子的 index、login都是处理器
4.从源码上看,Handler是一个接口,包含 ServeHTTP 方法,这就意味着只要有结构体实现了 ServeHTTP,它就可以作为一个处理器。
5.由于实现ServeHTTP才能成为一个处理器这种方式比较冗余,也不够简洁,所以就有了 HandlerFunc 类型,它的作用是 将一个普通函数转变为一个处理器
6.值得注意的是,ServeMux 本身也实现了 ServeHTTP方法,即它也是一个处理器,但它的作用只是 将请求转发到其他处理器

最后,关于HTTP服务器实现原理,实际涉及的知识点远比上文多的多,本文只是重点讲述了路由器、处理器的创建以及两者的绑定,希望能帮到大家!

补充

ServeMux 的 ServeHTTP

这里只是为了证明 ServeMux 也实现了ServeHTTP方法

 // ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
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)
}
  

HTTP路由闭包问题

示例如下:

 func routerClosure()  {
    // 当方法名/URL 是动态获取的时候
    methods := []string{"index", "login", "logout"}

    for _, m := range methods {
        http.HandleFunc("/" + m, func(w http.ResponseWriter, r *http.Request) {
            fmt.Println("method", m)
            w.Write([]byte("method name is : " + m))
        })
    }

    err := http.ListenAndServe(":5000", nil)
    if err != nil {
        panic(err)
    }
}
  

通过测试,你会发现,不管用index,login,最终都会路由到 logout 的处理函数,这是因为闭包导致的。

 $ curl 
method name is : logout% 

$ curl 
method name is : logout%   

解决办法如下:

 func routerClosure2()  {
    // 当方法名/URL 是动态获取的时候
    methods := []string{"index", "login", "logout"}

    for _, m := range methods {
        newM := m
        http.HandleFunc("/" + newM, func(w http.ResponseWriter, r *http.Request) {
            fmt.Println("method", newM)
            w.Write([]byte("method name is : " + newM))
        })
    }

    err := http.ListenAndServe(":5000", nil)
    if err != nil {
        panic(err)
    }
}
  

参考

Go 语言net/http 包使用模式

作者:言淦
链接:
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

相关文章