前言
关于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 包使用模式
作者:言淦
链接:
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。