阅读 Beego - 路由机制 (1)

beego 是国产著名的 Golang Web 框架,基于 MVC 模型,支持自动路由和Restful,并且有Config,Cache,Session,Orm等模块可以直接使用。MVC中最重要的就是路由。它实现从Web请求到对应控制器方法的映射。一般而言,路由包括三个部分:添加规则解析规则匹配规则。正好我分为三个部分,来分析beego的路由实现机制。

这一篇是添加路由规则的分析,从哪里入手呢?Beego有个开源论坛系统 Wetalk,也就是 golanghome 的实现,从他的路由开始看起。

Wetalk的路由设置很长,有很多的控制器需要注册。我从一段简单的看起,wetalk.go#L79

posts := new(post.PostListRouter)
beego.Router("/", posts, "get:Home")
beego.Router("/:slug(recent|best|cold|favs|follow)", posts, "get:Navs")
beego.Router("/category/:slug", posts, "get:Category")
beego.Router("/topic/:slug", posts, "get:Topic;post:TopicSubmit")

一般路由

路由的注册方法是 beego.Router(...),参数是URL规则,控制器对象和他内部对应的方法。具体这个方法怎么用,可以去参考官方文档。如何执行呢?继续看下去,beego.go

func Router(rootpath string, c ControllerInterface, mappingMethods ...string) *App {
	BeeApp.Handlers.Add(rootpath, c, mappingMethods...)
	return BeeApp
}

呵呵,方法落在BeeApp.Hanlders上。BeeApp.Hanlders*ControllerRegistor 实例,对应的Add方法在 router.go

func (p *ControllerRegistor) Add(pattern string, c ControllerInterface, mappingMethods ...string) {
	reflectVal := reflect.ValueOf(c)
	t := reflect.Indirect(reflectVal).Type()
	methods := make(map[string]string)
	if len(mappingMethods) > 0 {
		semi := strings.Split(mappingMethods[0], ";")
		for _, v := range semi {
			colon := strings.Split(v, ":")
			if len(colon) != 2 {
				panic("method mapping format is invalid")
			}
			comma := strings.Split(colon[0], ",")
			for _, m := range comma {
				if _, ok := HTTPMETHOD[strings.ToUpper(m)]; m == "*" || ok {
					if val := reflectVal.MethodByName(colon[1]); val.IsValid() {
						methods[strings.ToUpper(m)] = colon[1]
					} else {
						panic("'" + colon[1] + "' method doesn't exist in the controller " + t.Name())
					}
				} else {
					panic(v + " is an invalid method mapping. Method doesn't exist " + m)
				}
			}
		}
	}

	route := &controllerInfo{}
	route.pattern = pattern
	route.methods = methods
	route.routerType = routerTypeBeego
	route.controllerType = t
	if len(methods) == 0 {
		for _, m := range HTTPMETHOD {
			p.addToRouter(m, pattern, route)
		}
	} else {
		for k, _ := range methods {
			if k == "*" {
				for _, m := range HTTPMETHOD {
					p.addToRouter(m, pattern, route)
				}
			} else {
				p.addToRouter(k, pattern, route)
			}
		}
	}
}

比较长,一点一点看:

第一步,获取控制器的反射类型reflect.Type

第二步,解析mappingMethods,即上面代码beego.Router(...)的第三个参数,比如get:Topic;post:TopicSubmit。从字面猜就是HTTP请求方式对应的控制器方法名称,像 GET -> PostListRouter.Topic()。分号分割多种HTTP请求,冒号分割HTTP请求和对应控制器方法。HTTPMETHOD限制支持的HTTP请求方式,不正常的panic。*意味着匹配所有HTTPMETHOD. 用反射获取一次对应方法,判断是否有效。

第三步,生成controllerInfo{},并添加到路由中。pattern就是传入的URL规则,还没有解析。methods是解析好的路由参数中HTTP请求方式到控制器方法的映射。这里有个routerTypeBeego,标识controllerInfo{}是个一般的路由。还有routerTypeRESTFulrouterTypeHandler两种,会在下面说明。

接下来,就是看看p.addToRouter(...)是个啥啦!router.go:

func (p *ControllerRegistor) addToRouter(method, pattern string, r *controllerInfo) {
	if !RouterCaseSensitive {
		pattern = strings.ToLower(pattern)
	}
	if t, ok := p.routers[method]; ok {
		t.AddRouter(pattern, r)
	} else {
		t := NewTree()
		t.AddRouter(pattern, r)
		p.routers[method] = t
	}
}

终于看到了NewTree()——路由树。所有路由规则的集合其实是一个map[http_method]*Tree。关于路由树的实现,我们下一篇文章再来详细阅读。

RESTful 路由

beego 有个方法 beego.RESTRouter(...)。我本来以为这个方法是 RESTful 类型的路由,看源码发现,还是调用了 beego.Router(...)。找了一下,routerTypeRESTFul类型的路由,原来是在 router.gobeego.AddMethod(...):

func (p *ControllerRegistor) AddMethod(method, pattern string, f FilterFunc) {
	if _, ok := HTTPMETHOD[strings.ToUpper(method)]; method != "*" && !ok {
		panic("not support http method: " + method)
	}
	route := &controllerInfo{}
	route.pattern = pattern
	route.routerType = routerTypeRESTFul
	route.runfunction = f
	methods := make(map[string]string)
	if method == "*" {
		for _, val := range HTTPMETHOD {
			methods[val] = val
		}
	} else {
		methods[strings.ToUpper(method)] = strings.ToUpper(method)
	}
	route.methods = methods
	for k, _ := range methods {
		if k == "*" {
			for _, m := range HTTPMETHOD {
				p.addToRouter(m, pattern, route)
			}
		} else {
			p.addToRouter(k, pattern, route)
		}
	}
}

也是生成一个controllerInfo{}提交给路由树。区别在router.runfunction,不是控制器的反射类型,是一个函数类型FilterFunc。那么这个RESTFul的路由在哪儿用的呢?

beego.Get(...) 就是 beego.AddMethod("get",...)。类似的beego.Post(...),beego.Put(...)等等。换句话说这是一类路由,用来接收一个函数作为路由规则对应的方法,而不是一个控制器。

HTTP Handler 路由

beego 还有routerTypeHandler类型的路由,添加的方法在beego.Handler(...) router.go

func (p *ControllerRegistor) Handler(pattern string, h http.Handler, options ...interface{}) {
	route := &controllerInfo{}
	route.pattern = pattern
	route.routerType = routerTypeHandler
	route.handler = h
	if len(options) > 0 {
		if _, ok := options[0].(bool); ok {
			pattern = path.Join(pattern, "?:all")
		}
	}
	for _, m := range HTTPMETHOD {
		p.addToRouter(m, pattern, route)
	}
}

生成controllerInfo{}的时候使用的是http.Handler,保存在router.handler字段。而且下面把这个路由的HTTP请求方式设置给所有支持的方式。

自动路由

自动路由是为了简化用控制器注册路由时,一个一个添加的麻烦。根据控制器的结构来自动添加规则,具体的地方在router.go:

func (p *ControllerRegistor) AddAutoPrefix(prefix string, c ControllerInterface) {
	reflectVal := reflect.ValueOf(c)
	rt := reflectVal.Type()
	ct := reflect.Indirect(reflectVal).Type()
	controllerName := strings.TrimSuffix(ct.Name(), "Controller")
	for i := 0; i < rt.NumMethod(); i++ {
		if !utils.InSlice(rt.Method(i).Name, exceptMethod) {
			route := &controllerInfo{}
			route.routerType = routerTypeBeego
			route.methods = map[string]string{"*": rt.Method(i).Name}
			route.controllerType = ct
			pattern := path.Join(prefix, strings.ToLower(controllerName), strings.ToLower(rt.Method(i).Name), "*")
			patternInit := path.Join(prefix, controllerName, rt.Method(i).Name, "*")
			patternfix := path.Join(prefix, strings.ToLower(controllerName), strings.ToLower(rt.Method(i).Name))
			patternfixInit := path.Join(prefix, controllerName, rt.Method(i).Name)
			route.pattern = pattern
			for _, m := range HTTPMETHOD {
				p.addToRouter(m, pattern, route)
				p.addToRouter(m, patternInit, route)
				p.addToRouter(m, patternfix, route)
				p.addToRouter(m, patternfixInit, route)
			}
		}
	}
}

这里根据控制器的方法来拼接pattern。首先不处理控制器内置的方法exceptMethod,然后根据控制器的名称和方法,区分大小写地注册prefix/controller/method/*prefix/controller/method两个规则到所有HTTP请求方式上。

Final

综合来看,添加路由的过程,总结起来是添加controllerInfo{}*Tree中。controllerInfo{}的结构是:

type controllerInfo struct {
	pattern        string
	controllerType reflect.Type
	methods        map[string]string
	handler        http.Handler
	runfunction    FilterFunc
	routerType     int
}

可以看出,此时pattern —— 路由规则 —— 还没有经过解析。只是在methods的map中记录了HTTP请求方式和对应调用的方法,或者是在handler或runfunction直接保存了调用的方法函数。

那么下一篇,我们来阅读以下controllerInfo{}添加到*Tree中的过程。既然是路由树,那么层级规则,节点内容,就是重要的细节。

Notice: 本文基于 beego v1.4.3

本文来自:傅小黑的小玩意儿

感谢作者:傅小黑

查看原文:阅读 Beego - 路由机制 (1)

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。