引子
刚开始学习使用net/http包搭建http服务时,基本都会看到如下代码:
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *Request) {
fmt.Fprintln(w, "Hello World")
})
http.ListenAndServe()
}
代码非常简单,但想要知道它的内部是怎么处理的,我们可以查看源码,打开源码后发现,有很多相似的方法、结构体、接口等,刚开始的时候会感到云里雾里,不好屡清,下面,我将从一段简单的代码开始,逐步梳理其中的逻辑
一个最简单的http服务器
func main() {
server := &http.Server{}
server.ListenAndServe()
}
这两行代码(如果你想,甚至可以把它整理为一行),构造了一个最简单的http服务器,它会默认监听80端口,但不会处理任何请求,任何请求都将返回404。
这其实也是http.ListenAndServe的源码,但更加精简,省略传递给Server结构体的参数。
Server是http包中定义的一个结构体,具体结构如下
type Server struct {
Addr string
Handler Handler
// 其它字段省略
}
其中Addr表示要监听的地址,格式为host:port,如:8080,127.0.0.1:8080等;Handler为http包中定义的一个接口,只要实现了该接口,便可以作为Server中的Handler字段,具体看一下Handler接口的定义
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
非常简单,只要实现了ServeHTTP方法,便是Hanler接口类型。
我们来实现一个极简版的
package main
import (
"fmt"
"net/http"
)
type MyHandler struct{}
func (h MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello World")
}
func main() {
server := &http.Server{}
server.Handler = new(MyHandler)
server.ListenAndServe()
}
这时的服务器可以接收任何请求,任何请求都会返回Hello World
路由处理
上面的例子,不会区分路由,任何路由都会返回相同的结果,如何处理路由,最简单的方式是直接改造MyHandler中的ServeHTTP方法,如下
func (h MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/":
fmt.Fprintln(w, "Hello World")
default:
fmt.Fprintln(w, "404")
}
}
简单粗暴!
当然,也可以实现一个类似引子中的HandleFunc的方式,直接上代码
package main
import (
"fmt"
"net/http"
)
type MyHandler struct {
m map[string]http.Handler
}
func (h MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if _, ok := h.m[r.URL.Path]; !ok {
fmt.Fprintln(w, "404")
return
}
h.m[r.URL.Path].ServeHTTP(w, r)
}
func (h *MyHandler) HandleFunc(pattern string, handler func(w http.ResponseWriter, r *http.Request)) {
if h.m == nil {
h.m = map[string]http.Handler{}
}
h.m[pattern] = http.HandlerFunc(handler)
}
func main() {
server := &http.Server{}
handler := new(MyHandler)
handler.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello World")
})
server.Handler = handler
server.ListenAndServe()
}
这样一来,注册路由就比较优雅了。这里有个地方需要注意,就是Myhandler的HandleFunc方法中,使用了http.HandlerFunc进行了类型转换(注意并不是函数调用,而是类型转换),将func(http.ResponseWriter, *http.Request)函数转换成了http.Handler接口类型,它是怎么实现的呢?直接看源码
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
http包中直接将HandleFunc定义为func(ResponseWriter, *Request)函数类型,而且HandleFunc又实现http.Handler接口的ServeHTTP方法,所以HandleFunc又是http.Handler接口类型的,所以以在执行http.HandlerFunc(handler)时,会将handler转换成http.HandleFunc类型,可以赋值给MyHandler中的m字段map的值(htttp.Handler类型)
封装
如果每次都要自己实现http.Handler接口,代码会比较啰嗦,官方http包中其实已经进行了封装,其内部定义了一个ServeMux类型
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对标上面自己定义的MyHandler,它内部也实现了HandleFunc、ServeHTTP方法以及一些其它方法,为了文章逻辑顺畅,具体不再描述。我们可以使用该结构体简化前面的代码
package main
import (
"fmt"
"net/http"
)
func main() {
server := &http.Server{}
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello World")
})
server.Handler = mux
server.ListenAndServe()
}
这样一来,不需要自己实现http.Handler了,直接使用官方提供的ServeMux即可,非常方便,但和引子中的示例一比,还是不够简单,那引子中的示例是如何实现的呢,稍微追一个源码便会清楚。http包中定义了一个全局变量DefaultServeMux,它是一个指向ServeMux类型实例的指针
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
http.HandleFunc中正是使用了这个全局变量,代码非常简单
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
再看一下http.ListenAndServe
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
这样一来,引子中的示例为什么如此简单,也就十分清楚了
结尾
本文只是简单梳理了下golang搭建http服务的流程,以及它内部的一些原理,但其实源码还有很多值得说的地方,如http.Server的ListenAndServe方法的一些细节,这里不再详述。有了大致的逻辑,再去详细看源码也会比较清楚了