一.ServeMux
上一章讲到go接收请求之后的工作流程,但未提到将访问路径与对应的方法匹配
type ServeMux struct {
mu sync.RWMutex//同步锁
m map[string]muxEntry
hosts bool // whether any patterns contain hostnames
}
type muxEntry struct {
explicit bool
h Handler
pattern string
}
在ServeMux中,有一个map,装载了muxEntry
muxEntry中有pattern记录路径,h记录对应处理请求的Handler
ServeMux实现了ServeHTTP接口,其中调用了Handler方法,根据请求取到对应的handler然后再调用该handler的ServeHTTP方法
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)//根据request获得对应的handler
h.ServeHTTP(w, r)
}
Handler方法调用了handler,handler再调用match,通过遍历mux实体map获取与路径匹配的handler
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
var n = 0
for k, v := range mux.m {
if !pathMatch(k, path) {
continue
}
if h == nil || len(k) > n {
n = len(k)
h = v.h
pattern = v.pattern
}
}
return
}
go就是通过这种方式匹配路径与处理方法的
二.支持正则匹配路径的路由
根据上节的分析可以看到go自带的路由是不支持正则匹配的,且不支持GET,POST等不同请求方法的匹配
现在试着DIY一个支持正则匹配的路由
定义一个controllerInfo类型,用于存放正则表达式以及对应的controller的类型
type controllerInfo struct {
regex *regexp.Regexp
controllerType reflect.Type
}
type ControllerRegister struct {
routers []*controllerInfo
}
定义Controller并使Controller类实现ControllerInterface
ControllerInterface定义了一些常用的方法
type Controller struct {
RW http.ResponseWriter
Request *http.Request
Name string
}
type ControllerInterface interface {
Init(rw http.ResponseWriter, r *http.Request, controllerName string)
Prepare()
Get()
Post()
Delete()
Put()
Head()
Patch()
Options()
Finish()
Render() error
}
前面定义的ControllerRegister增加两个方法
1.将正则字符串和对应的Controller注册
func (p *ControllerRegister) Add(pattern string, c ControllerInterface) {
regex, regexErr := regexp.Compile(pattern)
if regexErr != nil {
//TODO add error handling here to avoid panic
panic(regexErr)
return
}
//now create the Route
t := reflect.Indirect(reflect.ValueOf(c)).Type()
route := &controllerInfo{}
route.regex = regex
route.controllerType = t
p.routers = append(p.routers, ro.......
te)
}
2.ServeHTTP用于分发请求,通过正则匹配路径,调用匹配的controller对应的方法
func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
//TODO dispatch request to controller
url := r.URL
for _, router := range p.routers {
reg := router.regex
if m := reg.MatchString(url.Path); m {
t := router.controllerType
v := reflect.New(t)
ci, ok := v.Interface().(ControllerInterface)
if !ok {
panic("controller is not ControllerInterface")
}
ci.Init(rw, r, t.Name())
ci.Prepare()
switch r.Method {
case http.MethodGet:
ci.Get()
case http.MethodPost:
ci.Post()
case http.MethodDelete:
ci.Delete()
case http.MethodPut:
ci.Put()
case http.MethodHead:
ci.Head()
case http.MethodPatch:
ci.Patch()
case http.MethodOptions:
ci.Options()
default:
ci.Get()
}
ci.Finish()
break
}
}
}
定义App,用于存放ContrllerRegister,之后我们还会用它存放Session管理器等工具
type App struct {
Handlers *ControllerRegister
Server *http.Server
}
var (
LinApp *App
)
func init() {
LinApp = NewApp()
}
func NewApp() *App {
app := &App{Handlers: &ControllerRegister{}, Server: &http.Server{}}
return app
}
func Router(rootpath string, c ControllerInterface) *App {
LinApp.Handlers.Add(rootpath, c)
return LinApp
}
func Run() {
http.HandleFunc("/", LinApp.Handlers.ServeHTTP)
http.ListenAndServe(":1234", nil) //设置监听的端口
}
现在只需创建一个Controller的子类,并复写相关的方法
例如:增加一个MainController处理Get方法,打印请求的表单,并返回123到客户端
type MainController struct {
lin.Controller
}
func (this *MainController) Get() {
fmt.Println("----------")
this.Request.ParseForm()
fmt.Println(this.Request.Form)
fmt.Fprintf(this.RW, "123")
}
最后在main方法中添加路由,并启动服务
我们DIY的路由就工作起来了
func main(){
lin.Router("/",&controllers.MainController{})
lin.Run();
}