一. 执行流程,与路由注册原理
- http接口执行流程
- 客户端发送请求;
- 服务器中的多路复⽤器收到请求;
- 多路复⽤器根据请求的 URL 找到注册的处理器,将请求交由处理器处理;
- 处理器执⾏业务逻辑,得到处理结果;
- 处理器调⽤模板引擎将指定的模板和上⼀步得到的结果渲染成客户端可识别的数据格式(通常是HTML)
- 最后将数据通过响应返回给客户端;
- 客户端拿到数据,执⾏对应的操作,例如渲染出来呈现给⽤户
- 根据上面的流程引出一下问题
- 什么是多路复用器
- 请求url与对应的处理器是如何注册到多路复用器中的
- 多路复用器接收到请求后是如何找到对应的处理器进行执行的
- 执行完毕后,响应是如何返回的
1. 什么是http的多路复用器
- 在我们编写原生http服务端时,首先要编写处理器也就是对外的业务接口,然后通过http内置的HandleFunc(“/test”,处理器函数)进行路由注册,查看用来注册路由的HandleFunc()函数源码,该函数内默认会调用DefaultServeMux的HandleFunc()方法,将请求的接口路径与处理器函数进行绑定, 此处的DefaultServeMux就是http默认使用的多路复用器
//src/net/http/server.go 下源码
//1.http内部默认创建了DefaultServeMux变量
var DefaultServeMux = &defaultServeMux
//2.这个DefaultServeMux是一个ServeMux类型
var defaultServeMux ServeMux
//3.进行路由注册时底层执行的函数
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
- DefaultServeMux 实际是ServeMux的实现类, ServeMux就是http中的多路复用器类型
- 注意虽然默认的多路复⽤器使⽤起来很⽅便,但是在⽣产环境中不建议使⽤。因为 DefaultServeMux 是⼀个全局变量,所有代码,包括第三⽅代码都可以修改它。 有些第三⽅代码会在 DefaultServeMux 注册⼀些处理器,这可能与我们注册的处理器冲突
2. 了解一下 ServeMux
- DefaultServeMux是ServeMux类型, ServeMux就是指HTTP请求多路复用器,在启动服务时,会将pattern与对应的处理器封装为muxEntry结构,维护路由关系,添加到ServeMux的m属性(也就是map容器)中, 在接收请求时,会通过传入的URL,与pattern列表进行匹配获取对应的handler处理器执行
- 查看ServeMux结构
- mu: sync.RWMutex 是一个并发控制的读写锁
- m: map[string]muxEntry 用来存储路由关系的map容器,用于路由的精确匹配
- es: []muxEntry 是muxEntry切片,存储了以 “/” 结尾注册的路由关系,用于路由的部分匹配,例如在浏览器输入请求路径 “/test/aaa” 和 “/test/bbb” 都会被路由到 “/test/” 这个路由的处理函数上
- hosts: bool //标记路由中是否带有主机名
type ServeMux struct {
mu sync.RWMutex //是一个并发控制的读写锁
m map[string]muxEntry //用来存储路由关系的map容器,用于路由的精确匹配
es []muxEntry //是muxEntry切片,注册路由时存储 "/" 结尾的,用于路由的部分匹配
hosts bool //标记路由中是否带有主机名
}
type muxEntry struct {
h Handler // 对应的请求处理函数
pattern string //路由表达式,也就是请求路径
}
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
h, _ := mux.Handler(r)
h.ServeHTTP(w, r) // 调用的是HandlerFunc的ServeHTTP
}
// 实现三:serverHandler
// serverHandler是对Server的封装,实现了ServeHTTP方法,并在其内执行ServeMux的ServeHTTP方法
type serverHandler struct {
srv *Server
}
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
...
handler.ServeHTTP(rw, req) // 调用的是ServerMux的ServeHTTP
}
- Handler定义如下,是一个接口,内部定义了一个ServeHTTP(ResponseWriter, *Request)方法, 也就是说任何结构只要实现了这个ServeHTTP方法,那么这个结构体就是一个Handler对象, 该方法会读取Request进行逻辑处理然后向ResponseWriter中写入响应
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
3. 通过DefaultServeMux了解路由的注册流程
- 先了解一下 HandlerFunc,HandlerFunc类型是一个适配器,并且实现了ServeHTTP方法,也就是说HandleFunc就是一个Handler
- 通过类型转换倒推,所有具有func(ResponseWriter, *Request)签名普通函数都可以看为HandlerFunc, HandleFunc又实现了ServeHTTP接口,是不是可以认为所有具有func(ResponseWriter, *Request)签名普通函数都可以转换成Handler
这里就是编写处理器时为什么时固定函数,实际就是实现HandlerFunc,将普通函数用作HTTP处理程序
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
- 查看注册路由时DefaultServeMux.HandleFunc(pattern, handler) 源码
//DefaultServeMux中的HandleFunc()源码
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
func (mux *ServeMux) Handle(pattern string, handler Handler) {
//1.加锁
mux.mu.Lock()
defer mux.mu.Unlock()
//2.校验路由地址与处理器是否为空
if pattern == "" {
panic("http: invalid pattern")
}
if handler == nil {
panic("http: nil handler")
}
//2.判断当前注册的pattern是否已经存在
if _, exist := mux.m[pattern]; exist {
panic("http: multiple registrations for " + pattern)
}
//3.判断DefaultServeMux中用来存储路由规则的map容器是否为空,如果是创建
if mux.m == nil {
//创建
mux.m = make(map[string]muxEntry)
}
//4.通过pattern与接收到的处理器封装muxEntry结构
e := muxEntry{h: handler, pattern: pattern}
//5.添加到DefaultServeMux的map容器中,设置路由关系
mux.m[pattern] = e
//6.判断接收到的pattern结尾字符是不是"/",如果是,将当前接收到的路由关系添加到DefaultServeMux的es数组中
if pattern[len(pattern)-1] == '/' {
mux.es = appendSorted(mux.es, e)
}
//7.判断接收到的pattern如果不以"/"开始,标记mux中有路由的路径带有主机名,设置hosts为true
if pattern[0] != '/' {
mux.hosts = true
}
}
- 至此,将pattern和handler保存在muxEntry结构中,并且pattern作为key,把muxEntry装入到DefaultServeMux的Map里面。简单来说就是保存当前路由和自己定义的那个处理函数
- 梳理一下调⽤Http.HandleFunc按顺序做了⼏件事:
1 调⽤了DefaultServeMux的HandleFunc
2 调⽤了DefaultServeMux的Handle
3 往DefaultServeMux的map[string]muxEntry中增加对应的handler和路由规则
二. 总结
- 在通过net/http编写服务端时, 首先调用NewServeMux()创建多路复用器,编写对外接收请求的接口函数也就是处理器,然后调用多路复用器上的HandleFunc()方法,将接口与接口路径进行绑定,注册路由, 最后调用ListenAndServe()函数在指定端口开启监听,启动服务
- 首先查看NewServeMux()会返回一个DefaultServeMux 类型变量,该变量是ServeMux的实现类,而ServeMux就是多路复用器,查看ServeMux内部结构:
- mu: sync.RWMutex 是一个并发控制的读写锁
- m: map[string]muxEntry 用来存储路由关系的map容器,用于路由的精确匹配
- es: []muxEntry 是muxEntry切片,存储了以 “/” 结尾注册的路由关系,用于路由的部分匹配,例如在浏览器输入请求路径 “/test/aaa” 和 “/test/bbb” 都会被路由到 “/test/” 这个路由的处理函数上
- hosts: bool //标记路由中是否带有主机名
- 在启动服务时,会将pattern与对应的处理器封装为muxEntry结构,维护路由关系,添加到ServeMux的m属性(也就是map容器)中, 在接收请求时,会通过传入的URL,与pattern列表进行匹配获取对应的handler处理器执行
- 查看多路复用器DefaultServeMux 上的HandleFunc()方法,该方法内会继续向下调用多路复用器上的Handle()方法:
- 先获取到DefaultServeMux上的锁
- 对接口处理器,接口路径进行非空校验
- 判断DefaultServeMux中用来存储路由规则的map容器是否为空,如果为空创建
- 将接口处理器,接口路径封装为muxEntry结构体变量,将接口路径作为key,muxEntry作为value存储到DefaultServeMux的Map容器中,接口路径与处理器进行绑定
- 自此接口路由注册成功