Gee教程4.实现路由分组控制

路由分组的意义

分组控制(Group Control)是 Web 框架应提供的基础功能之一。所谓分组,是指路由的分组。如果没有路由分组,我们需要针对每一个路由进行控制。但是真实的业务场景中,往往某一组路由需要相似的处理。例如:

  • /admin开头的路由需要鉴权。
  • /api开头的路由是 RESTful 接口,可以对接第三方平台,需要三方平台鉴权。

大部分情况下的路由分组,是以相同的前缀来区分的。因此,我们今天实现的分组控制也是以前缀来区分,并且支持分组的嵌套。例如/admin是一个分组,/admin/a/admin/b可以是该分组下的子分组。作用在/admin分组上的中间件(middleware),也都会作用在子分组,子分组还可以应用自己特有的中间件。 

中间件可以给框架提供无限的扩展能力,应用在分组上,可以使得分组控制的收益更为明显,而不是共享相同的路由前缀这么简单。

例如只给/admin的分组应用鉴权中间件,即是只有访问的前缀是/admin的请求才应用鉴权,而其他的请求都不使用。/分组应用日志中间件(middlewares),/是默认的最顶层的分组,也就意味着给所有的路由,即整个框架增加了记录日志的能力。

提供扩展能力支持中间件的内容,将在下一节中实现。

分组路由的用法

来看看如何使用路由分组,和gin是相似的

r := gee.New()
v1 := r.Group("/v1")
v1.GET("/", func(c *gee.Context) {
	c.HTML(http.StatusOK, "

Hello group

") }) //不经过分组的 r.GET("/li", func(c *gee.Context) { c.HTML(http.StatusOK, "

Hello li

") })

路由组Group的结构

一个group对象需要具备哪些属性呢。首先是要有前缀(prefix),这样才能区分这个是哪个分组,例如/、/api。还有按照前面的分析,中间件是应用在分组上的,那路由组就还需要存储应用在该分组上的中间件。代码r.Group("v1")返回一个路由组,v1.GET的做法就和r.GET()做法相似。r.GET是调用了(*Engine).addRoute()来映射所有的路由规则和 Handler 。那么v1.GET也想实现这种效果,那么Group对象,还需要有访问Router的能力。

为了方便,我们可以再Group中保存一个指针,指向Enginx。

整个框架的所有资源都是由Enginx统一协调的,那么就可以通过Enginx间接地访问各种接口了。

那路由组Group的定义如下:

type RouterGroup struct {
	prefix      string		//前缀
	middlewares []HandlerFunc
	engine      *Engine //所有的路由组共享一个enginx实例
}

 和路由相关的函数,就可以交给RouterGroup去实现了。

结构体Enginx添加了[]*RouterGroup类型变量groups,其保存了所有的路由组。

(*RouterGroup).addRoute函数,调用了group.engine.router.addRoute来实现了路由的映射。

type Engine struct {
	router *router
	gorups []*RouterGroup
}

type RouterGroup struct {
	prefix      string //前缀
	middlewares []HandlerFunc
	engine      *Engine //所有的路由组共享一个enginx实例
}

func (group *RouterGroup) Group(prefix string) *RouterGroup {
	engine := group.engine
	newGroup := &RouterGroup{
		prefix: group.prefix + prefix,
		engine: engine,
	}
	engine.gorups = append(engine.gorups, newGroup)
	return newGroup
}
func (group *RouterGroup) addRoute(method string, pattern string, hander HandlerFunc) {
	path := group.prefix + pattern
	group.engine.router.addRoute(method, path, hander)
}

func (group *RouterGroup) GET(pattern string, handler HandlerFunc) {
	group.addRoute("GET", pattern, handler)
}

//Engine的
func (engine *Engine) addRoute(method string, pattern string, hander HandlerFunc) {
	engine.router.addRoute(method, pattern, hander)
	log.Printf("Route %4s - %s", method, pattern)
}

func (engine *Engine) GET(pattern string, handler HandlerFunc) {
	engine.addRoute("GET", pattern, handler)
}

代码繁琐部分,使用继承解决

addRoute、GET方法,RouterGroup和Enginx都需要实现,而且还有POST,DELETE等等HTTP请求方法,那这些都需要这两个结构体去实现。这样就出现了重复的代码。是很繁琐的。

那可以有什么方法去解决这样的问题吗?两个结构体的,都出现同样的代码。

那我们可以想到继承。例如Enginx继承RouterGroup,Enginx是子类

经过继承后,RouterGroup拥有的方法也是属于Enginx的了,那Enginx类就不需要再写这些方法了。

那么Enginx也是个路由组,其就是最顶层的路由分组,该group.prefix="",其前缀就是空字符串。

那可以有疑惑为什么不是RouterGroup继承Enginx,RouterGroup是子类呢?

首先,对外使用的是Enginx;第二,Enginx有Run,ServeHTTP等方法,RouterGroup是不需要这些方法的。所以Enginx继承RouterGroup,也就是说Engine拥有RouterGroup所有的能力。

gee.go文件代码改写如下

Enginx的Run、ServeHTTP方法没有改变,就没有展示。

在Go语言中实现继承操作,是在结构体中添加RouterGroup结构体,不需要写参数。写RouterGroup或者写*RouterGroup确实是有点不同的。gin中是写RouterGroup,不带指针的。但这在这节个不是重点,两种写法是会有些不同的,这个以后有机会再细说。(留坑以后填哈哈)

type Engine struct {
	*RouterGroup    //直接是RouterGroup也可以,写成routerGourp *RouterGroup就不是“继承”    
	router *router
	gorups []*RouterGroup //存储所有的路由组
}

type RouterGroup struct {
	prefix      string //前缀
	middlewares []HandlerFunc
	engine      *Engine //所有的路由组共享一个enginx实例
}

func (group *RouterGroup) Group(prefix string) *RouterGroup {
	engine := group.engine
	newGroup := &RouterGroup{
		prefix: group.prefix + prefix,
		engine: engine,
	}
	engine.gorups = append(engine.gorups, newGroup)
	return newGroup
}
func (group *RouterGroup) addRoute(method string, pattern string, hander HandlerFunc) {
	path := group.prefix + pattern
	group.engine.router.addRoute(method, path, hander)
}

// GET defines the method to add GET request
func (group *RouterGroup) GET(pattern string, handler HandlerFunc) {
	group.addRoute("GET", pattern, handler)
}

// POST defines the method to add POST request
func (group *RouterGroup) POST(pattern string, handler HandlerFunc) {
	group.addRoute("POST", pattern, handler)
}

// 创建enginx实例
func New() *Engine {
	engine := &Engine{router: newRouter()}
	engine.RouterGroup = &RouterGroup{engine: engine}
	engine.gorups = []*RouterGroup{engine.RouterGroup}
	return engine
}

这样实现,我们既可以像原来一样添加路由,也可以通过分组添加路由。

创建Enginx实例的方法New

这个函数有些困惑的地方。该函数实现是先创建出一个Enginx实例,之后再给该实例的属性进行赋值操作。

函数实现内的第二句代码,继承RouterGroup(Go中是没有继承这一说法),那么就要对父类进行赋值。RouterGroup有哪些属性是需要设置的。首先是engine,那就是要把自身engine赋值给父类中的engin。

而前面说过了,Enginx也是个路由组(最顶层的路由分组),该group.prefix="",string类型默认就是"",所以父类的prefix可以不用赋值。

接着就是groups,而自身engine也是个路由组,所以groups中头元素就是engine.RouterGroup。

// gee.go文件,创建enginx实例
func New() *Engine {
	engine := &Engine{router: newRouter()}        
	engine.RouterGroup = &RouterGroup{engine: engine}    //第二句
	engine.gorups = []*RouterGroup{engine.RouterGroup}
	return engine
}

测试

func main() {
	r := gee.New()
	r.GET("/index", func(c *gee.Context) {
		c.HTML(http.StatusOK, "

Index Page

") }) v1 := r.Group("/v1") { v1.GET("/", func(c *gee.Context) { c.HTML(http.StatusOK, "

Hello li

") }) v1.GET("/hello", func(c *gee.Context) { c.String(http.StatusOK, "hello %s, you're at %s\n", c.Query("name"), c.Path) }) } v2 := r.Group("/v2") { v2.GET("/hello/:name", func(c *gee.Context) { // expect /hello/geektutu c.String(http.StatusOK, "hello %s, you're at %s\n", c.Param("name"), c.Path) }) v2.POST("/login", func(c *gee.Context) { c.JSON(http.StatusOK, gee.H{ "username": c.PostForm("username"), "password": c.PostForm("password"), }) }) } r.Run("localhost:10000") }

完整代码:https://github.com/liwook/Go-projects/tree/main/gee-web/4-routerGroup

你可能感兴趣的:(网络,go语言,web)