Go指南11-谈谈 Golang HTTP服务器实现原理

3,255 阅读7分钟

前言

关于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服务器的实现不算很复杂,但是比较蛋疼的一个点是它的函数名(或者是函数签名)非常接近,所以一开始的时候会非常懵,典型的如 HandlerHandleHandlerFunc以及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)**处理。

路由器与处理器的绑定

路由器与处理器的绑定,主要用到 HandlerHandle

首先从语义上看,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.HandleHandleFunc:这两个方法的作用都是一致的,都是将处理器注册到路由器中,它们的区别主要是HandleFunc是在 Handle之上再封装一层,仅此而已(可以通过查看源码得证)

我们可以对以上三点进行一个简单总结:ServeHTTPHandlerFunc 的作用是生成一个 Handler,而 HandleHandleFunc 则是将生成的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 http://127.0.0.1:5000/index
method name is : logout% 

$ curl http://127.0.0.1:5000/login
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 包使用模式