curl 没有到主机的路由_七天系列第四弹:路由分组控制(Group)

edad97ce0d64f1f6777fb14e31f0c65f.png

作者:geektutu

链接:https://geektutu.com/post/gee-day4.html

今天这篇文章相对比较简单,没有太多好说的,重点在于关注 RouterGroupEngine 的结构设计。

4. Gee - 分组控制(Group)

  • 实现路由分组控制(Route Group Control),代码约50行

分组的意义

分组控制(Group Control)是 Web 框架应提供的基础功能之一。在真实的业务场景中,某些需要进行相同处理的路由可以归为一组,也就是按照路由分组。例如:

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

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

我们知道中间件可以给框架提供无限的扩展能力,而将中间件和分组结合,可以得到更加明显的收益。

  • /admin的分组,可以应用鉴权中间件;
  • /是默认的最顶层的分组,对其应用日志中间件,相当于所有的路由增加了记录日志的能力。

我们将在下一节介绍提供支持中间件的扩展能力。

分组嵌套

一个 Group 对象需要具备哪些属性呢?首先是前缀(prefix),比如/,或者/api;要支持分组嵌套,那么还需要知道当前分组的父亲(parent)是谁;当然,按照我们一开始的分析,中间件是应用在分组上的,我们还需要存储应用在该分组上的中间件(middlewares)。

我们之前调用函数(*Engine).addRoute()来映射所有的路由规则和 Handler 。如果Group对象需要直接映射路由规则的话,比如我们想在使用框架时,这么调用:

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

Hello Gee

")
})

那么Group对象,还需要有访问Router的能力,为了方便,我们可以在Group中,保存一个指针,指向Engine,整个框架的所有资源都是由Engine统一协调的,那么就可以通过Engine间接地访问各种接口了。

总的来说,最后的 Group 的定义是这样的:

// gee-web/gee/gee.go

type RouterGroup struct {
 prefix      string
 middleWares []HandlerFunc // 支持中间件
 parent      *RouterGroup  // 支持多级分组
 engine      *Engine       // 全局共用一个 engine 实例
}

我们还可以进一步地抽象,将Engine作为最顶层的分组,也就是说Engine拥有RouterGroup所有的能力。

// gee-web/gee/gee.go

type Engine struct {
 *RouterGroup
 router *router
 groups []*RouterGroup // 存储所有分组
}

那我们就可以将和路由有关的函数,都交给RouterGroup实现了。

// gee-web/gee/gee.go

// 初始化 engine
func New() *Engine {
 engine := &Engine{router: NewRouter()}
 engine.RouterGroup = &RouterGroup{engine: engine}
 engine.groups = []*RouterGroup{engine.RouterGroup}
 return engine
}

// 创建分组
func (group *RouterGroup) Group(prefix string) *RouterGroup {
 engine := group.engine
 newGroup := &RouterGroup{
  prefix: group.prefix + prefix,
  parent: group,
  engine: engine,
 }
 engine.groups = append(engine.groups, newGroup)
 return newGroup
}

func (group *RouterGroup) addRoute(method string, pattern string, handler HandlerFunc) {
 pattern = group.prefix + pattern
 log.Printf("Route %4s - %s", method, pattern)
 group.engine.router.addRoute(method, pattern, handler)
}

// 添加 Get 请求
func (group *RouterGroup) GET(pattern string, handler HandlerFunc) {
 group.addRoute("GET", pattern, handler)
}

// 添加 POST 请求
func (group *RouterGroup) POST(pattern string, handler HandlerFunc) {
 group.addRoute("POST", pattern, handler)
}

// 启动 HTTP 服务
func (engine *Engine) Run(addr string) (err error) {
 return http.ListenAndServe(addr, engine)
}

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 c := NewContext(w, req)
 engine.router.handle(c)
}

可以仔细观察下addRoute函数,调用了group.engine.router.addRoute来实现了路由的映射。由于Engine从某种意义上继承了RouterGroup的所有属性和方法,因为 (*Engine).engine 是指向自己的。这样实现,我们既可以像原来一样添加路由,也可以通过分组添加路由。

使用 Demo

测试框架的Demo就可以这样写了:

// gee-web/main.go

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 Gee

")
  })

  v1.GET("/hello", func(c *gee.Context) {
   // expect /hello?name=geektutu
   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(":9999")
}

通过 curl 简单测试:

$ curl "http://localhost:8080/api/speak?name=geek"
hello geek, you are at /api/speak

$ curl "http://localhost:8080/auth/assets/sixsixsix.jpg"
{"filepath":"sixsixsix.jpg"}

今天的文章相对轻松一些了,项目已经完成超过一半了,坚持就是胜利!

系列相关


1.七天用 Go 实现一个 Web 框架,敢来试试吗

2.七天系列第二弹:你了解 Context 吗?

3.七天系列第三弹:Trie 树实现动态路由

对于这个系列有什么想法,或者希望看哪方面的知识,可以下方留言告诉我,通通安排上哦~


留言区

写留言

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值