前言
关于Golang HTTP服务器实现原理,本文将通过下面两点来讲述,希望能帮到大家!
- 如何创建一个HTTP服务器
- HTTP服务器实现原理
如何创建一个HTTP服务器
创建一个 HTTP服务器 的步骤:
- 创建一个处理器(实际的业务逻辑)
- 创建一个路由器并与处理器进行绑定/注册(这样才能根据URL匹配到对应的函数)
- 启动HTTP服务器,并监听指定端口
对于处理器的实现,其实只有两种,一是使用处理器函数实现,二是创建一个结构体,并实现ServeHTTP方法。
而对于处理器与路由器的绑定方式,一种是通过HandleFunc方法直接绑定处理器;一种是通过 Handle方法变向绑定。
使用处理器函数实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41// 使用处理器函数实现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) } }
使用处理器结构体实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24type 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路由器的定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 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的区别:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25// 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) }
上面列举了四个迷惑性比较大的函数,或者接口,它们的作用如下:
- Handler接口:从技术看,它的确是一个接口,并包含 ServeHTTP 方法;但从逻辑上看,它是一个处理器,作用是接收HTTP请求并响应,同时,只要有结构体实现了 ServeHTTP 方法,它就可以看做一个处理器。
- HandlerFunc类型:从源码注释可以看到,它是一个适配器,作用是将一个普通函数转变为一个处理器。为什么要这样设计?因为之前那种创建处理器的方式太麻烦,也不够简洁,需要创建一个结构体,并实现 ServeHTTP 方法。
这也是为什么我们常见的处理器都是这样的:
1
2
3func index(w http.ResponseWriter, r *http.Request) { }
那 HandlerFunc 是如何将普通函数转换成一个处理器呢?
答案就在下面的源码中,HandlerFunc虽然是一个类型,但在这里个人觉得有点结构体的味道,且它实现 ServeHTTP 方法,也就意味着 HandlerFunc 也是一个Handler,这也是为什么一个普通函数能作为一个处理器的原因。
1
2
3
4
5
6
7
8type HandlerFunc func(ResponseWriter, *Request) // ServeHTTP calls f(w, r). // 调用我们实际的handler函数 func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) }
- Handle和HandleFunc:这两个方法的作用都是一致的,都是将处理器注册到路由器中,它们的区别主要是HandleFunc是在 Handle之上再封装一层,仅此而已(可以通过查看源码得证)
我们可以对以上三点进行一个简单总结:ServeHTTP 与 HandlerFunc 的作用是生成一个 Handler,而 Handle 和 HandleFunc 则是将生成的Handler与路由器进行绑定,或者也可以说是注册。
最后的结论如下:
- HTTP服务器的实现涉及到路由器,处理器,以及它们之间的映射关系
- 路由器有两种形式,一是使用默认的路由(DefaultServeMux),二是使用 NewServeMux 方法创建一个路由,两者无本质区别
- Handler表示处理器,它的作用是接收HTTP请求,并响应 ,也就我们写业务逻辑的地方,上面例子的 index、login都是处理器
- 从源码上看,Handler是一个接口,包含 ServeHTTP 方法,这就意味着只要有结构体实现了 ServeHTTP,它就可以作为一个处理器。
- 由于实现ServeHTTP才能成为一个处理器这种方式比较冗余,也不够简洁,所以就有了 HandlerFunc 类型,它的作用是将一个普通函数转变为一个处理器
- 值得注意的是,ServeMux 本身也实现了 ServeHTTP方法,即它也是一个处理器,但它的作用只是将请求转发到其他处理器。
最后,关于HTTP服务器实现原理,实际涉及的知识点远比上文多的多,本文只是重点讲述了路由器、处理器的创建以及两者的绑定,希望能帮到大家!
补充
ServeMux 的 ServeHTTP
这里只是为了证明 ServeMux 也实现了ServeHTTP方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 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路由闭包问题
示例如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17func 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 的处理函数,这是因为闭包导致的。
1
2
3
4
5
6$ curl http://127.0.0.1:5000/index method name is : logout% $ curl http://127.0.0.1:5000/login method name is : logout%
解决办法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18func 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) } }
作者:言淦
链接:https://juejin.im/post/6867149633328513038
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
最后
以上就是大胆小霸王最近收集整理的关于Golang HTTP使用方式的全部内容,更多相关Golang内容请搜索靠谱客的其他文章。
发表评论 取消回复