解释处理器和处理器函数的关系

剖析HandleHandleFuncHanderHanderFunc的联系

1 处理器是什么?

Web应用的工作流程如下:

  • 客户端将请求发送到服务器的一个URL上
  • 服务器的多路复用器将接收到的请求重定向到正确的处理器,然后由该处理器对请求进行处理
  • 处理器处理请求并执行必要的动作。
  • 处理器调用模板引擎,生成相应的HTML并且将其返回给客户端

所以简单来说处理器就是处理请求和执行动作

2 处理器的工作原理

2.1 处理请求

先来看一段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import (
"fmt"
"net/http"
)

type MyHandler struct{}

func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
}

func main() {
handler := MyHandler{}
server := http.Server{
Addr: "127.0.0.1:8080",
Handler: &handler,
}
server.ListenAndServe()
}

可以看到此代码是自己编写了一个处理器MyHandler,然后将其直接与服务器进行了绑定,以此代替原本在使用的默认多路复用器。这就意味着服务器不会再通过URL匹配来将请求路由至不同的处理器,而是直接使用同一个处理器处理所有的请求,因此无论浏览器访问什么地址,服务器返回的都是Hello World!

从上面代码我们可以知道要实现一个处理器我们需要实现一个Handler接口,其源码如下:

1
2
3
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}

2.2 使用多个处理器

实际情况下我们不可能使用一个处理器去处理所有请求,所以我们最常用的就是不在Server里面去指定处理器,而是让服务器使用默认多路复用器,然后使用http.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
package main

import (
"fmt"
"net/http"
)

type HelloHandler struct {}

func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello!")
}

type WorldHandler struct {}

func (h *WorldHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello!")
}

func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
hello := HelloHandler{}
world := WorldHandler{}
http.Handle("/hello", &hello)
http.Handle("/world", &world)

server.ListenAndServe()
}

2.3 处理器函数

从2.2和2.3的代码我们可以看到如果直接写处理器,那么我们需要额外定义一个结构体,这样写起来非常麻烦,所以我们有一种比较清爽的书写方式,其代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"fmt"
"net/http"
)

func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello!")
}

func world(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "World!")
}

func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/hello", hello)
http.HandleFunc("/world", world)

server.ListenAndServe()
}

可以看到上面代码的helloworld函数和之前代码的ServeHTTP方法有着相同的签名,然后在main函数中通过http.HandleFunc函数将helloworld转换成Handler并和多路复用器绑定。

要分析其实现原理,我们要先看http.Handlehttp.HandleFunc的源码:

首先来看http.Handle的源码:

1
2
3
func Handle(pattern string, handler Handler) { 
DefaultServeMux.Handle(pattern, handler)
}

可以看到他第二个参数就是处理器Handler,我们发现他其实调用的是默认多路复用器DefaultServeMuxHandle方法,后者的Handle只是多了一些处理细节这里不再赘述。

那么接下来看http.HandleFunc的源码:

1
2
3
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}

可以看到上述代码其实第二个参数是一个Handler处理器,但是其实实参是一个相同签名的其他函数,那么这个转换是通过什么进行的呢?

我们发现这里也是调用的是默认多路复用器DefaultServeMuxHandleFunc方法,其源码如下:

1
2
3
4
5
6
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}

通过观察源码答案就很清晰了,是HandlerFunc进行了转换工作。

我们来看一下HandlerFunc的源码:

1
2
3
4
5
6
type HandlerFunc func(ResponseWriter, *Request)

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

看完上述代码我们可以明白其实将hello函数转换成处理器的实际代码如下:

1
HelloHandler := HandlerFunc(hello)

看到这里想必各位已经明白,对于和ServeHTTP有相同签名的函数是如何转换成处理器Handler的了。

  • PS:虽然处理器函数的代码更加整洁,但是处理器函数不能完全替代处理器。这是因为在某些情况下,代码可能已经包含了某个接口或者某种类型,这时我们只需要为它们添加ServeHTTP方法就可以将它们转变成处理器。

2.4 串联多个处理器和处理器函数

有了上面的知识铺垫,现在我们来深入探讨一下串联多个处理器(处理器函数)的做法。

因为在GO语言中程序可以将一个函数传递给另一个函数,所以我们可以实现这个功能。

2.4.1 串联多个处理器函数

直接用例子上手,假设现在要将helloworld串联起来,其代码如下:

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
package main

import (
"fmt"
"net/http"
"reflect"
"runtime"
)

func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello!\n")
}

func world(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "world!\n")
h(w, r)
}
}

func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/hello", world(hello))
server.ListenAndServe()
}

如果要再增加一个funny函数,其函数原型如下:

1
2
3
4
5
6
func funny(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
...
h(w, r)
}
}

那么只需将http.HandleFunc修改为如下:

1
http.HandleFunc("/hello", funny(world(hello)))

2.4.2 串联多个处理器

同样是串联helloworldfunny,代码如下:

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
package main

import (
"fmt"
"net/http"
)

type HelloHandler struct{}

func (h HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello!\n")
}

func world(h http.Handler) http.Handler {
return http.HandlerFunc (func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "world!\n")
h.ServeHTTP(w, r)
})
}

func funny(h http.Handler) http.Handler {
return http.HandlerFunc (func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "funny!\n")
h.ServeHTTP(w, r)
})
}

func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
hello := HelloHandler{}
http.Handle("/hello", funny(world(hello)))
server.ListenAndServe()
}

参考资料:Go Web Programming