内容简介:最近在开发自己的 Web 框架例如,我希望获得一些而市面上最快的就是
概述
最近在开发自己的 Web 框架 Bingo , 也查看了一些市面上的路由 工具 包,但是都有些无法满足我的需求,
例如,我希望获得一些 Laravel
框架的特性:
-
快速的路由查找
-
动态路由支持
-
中间件支持
-
路由组支持
而市面上最快的就是 httprouter
,这里本来几个月前我改造过一次: 改造httprouter使其可以支持中间件
,但是那时是耦合在 bingo
框架中的,并且中间件不支持拦截,在这里我需要将其抽出来制作出一个第三方包,可以直接引用,无需依赖 Bingo
框架
所以我依旧选用了 httprouter
作为基础包,将其进行改造,使其支持以上特性。
仓库地址: bingo-router
用法在项目的 README
中已经将的很清楚了,这里不再赘述,有问题或者有什么需求可以给我提 issue
喔~
也建议先过一遍 README.md
再看这篇文章,不然可能会有地方看不懂...
改造主要分为两部分
-
第一部分是将
httprouter
的 路由树tree
上挂载的handle
方法改为我们自定义的结构体
httprouter
的原理可以看这篇5.2 router 请求路由
简单来讲,就是把所有接口的路径,共同构造一颗前缀树,将前缀相同的路径放在一棵树杈中,这样可以加速查找速度,而每片树叶都代表查找到了一个路由方法,挂载的就是一个方法,
但是这样的话这棵前缀树上就只能挂载 方法了,无法添加一些额外信息,所以第一步就要让前缀树上挂载一个我们自定义的结构体,让我们可以查找到挂载的中间件、路由 前缀等
-
第二部分是实现中间件功能,如果只是 遍历操作一个中间件数组,那么无法进行一些拦截操作,
比如,我们要实现一个中间件用来验证用户是否登陆 ,未登录用户将会返回错误信息,那么如果遍历执行一个中间件数组,最终还是将会执行到最终的路由
为了实现拦截功能,我参考了
Laravel
中的Pipeline
功能的实现原理,实现了一个管道对象,实现上述效果
开始改造
1. 第一部分
-
在我们的计划中,计划实现 路由组、中间件、路由前缀功能,所以我们需要自定义的结构体如下:
// 路由 type Route struct { path string // 路径 targetMethod TargetHandle // 要执行的方法 method string // 访问类型 是get post 或者其他 name string // 路由名 mount []*Route // 子路由 middleware []MiddlewareHandle // 挂载的中间件 prefix string // 路由前缀,该前缀仅对子路由有效 }
其中的
targetMethod
就是原本挂载在前缀树的handle
方法了,我们需要把原本tree.go
文件中的Node
结构体上挂载的handle
方法全部 改为Route
,改动较大,且没有什么需要特别注意的 ,就不在这里赘述了,具体可以看
tree.go
文件 -
在
README
中的路由注册操作,使用的是责任链模式,每个方法最后都返回一个当前对象的指针,就可以实现链式操作 其中的Get``Post
等方法,实际上是在向Route
对象中的属性赋值,没什么技术含量,感兴趣可以看 源码 -
实现路由组功能
通过路由组,我们可以给子路由设置公共的前缀和中间件,
Laravel
中是让路由成组来做的,多个路由组成了一个组对象,而这里 ,我直接用了子路由的方式,将组对象也变成了一个普通路由,组对象下 的路由就是当前路由的子路由写一个
Mount()
方法,让路由添加子路由:// 挂载子路由,这里只是将回调中的路由放入 func (r *Route) Mount(rr func(b *Builder)) *Route { builder := new(Builder) rr(builder) // 遍历这个路由下建立的所有子路由,将路由放入父路由上 for _, route := range builder.routes { r.mount = append(r.mount, route) } return r }
其中的
Builder
中包含了一个路由数组,通过建造者模式,给Builder
一个NewRoute
方法,让每一个通过这种方法创建的路由都在Builder
的routes
属性下:func (b *Builder) NewRoute() *Route { r := NewRoute() b.routes = append(b.routes, r) return r }
在创建的时候将指针放入
Builder
中即可这样,我们所建立的多个路由 就可以嵌套在一起了,那么如何利用
httprouter
的Handle
方法,将我们的Route
对象,注入到Router
中呢? -
将路由注入路由器
从
httprouter
源码可以看出,无论是Get
,Post
还是其他的方法,最终都是调用了router.Handle()
方法,传入访问方式,路径,和对应的方法,我们刚刚已经把对应的方法改为了路由所以这里就传入 访问方式,路径,和路由对象,并且在注入的时候,让中间件和路由前缀等都生效
编写一个注入的方法
Mount
:
``go var prefix []string // 当前路由前缀,每经过一层,前缀就会增加一个,最终将数组中的字符串连接起来就是最后的前缀了 var middlewares map[string][]MiddlewareHandle // 中间件,key标识了这是第几层路由的中间件,值就是对应的中间件数组了 var currentPointer int // 当前是第几层路由 // 挂载方法可以一次性传入多个路由对象 func (r *Router) Mount(routes ...*Route) { prefix = []string{} middlewares = make(map[string][]MiddlewareHandle) for _, route := range routes { // 挂载单个路由 r.MountRoute(route) } } // 向其中挂载路由 func (r *Router) MountRoute(route *Route) { // 将当前路径的中间件放入集合中 setMiddlewares(currentPointer, route) // 当前路径是所有前缀数组连接在一起,加上当前路由的path p := getPrefix(currentPointer) + route.path // 如果一个路由设置了前缀,则这个前缀会作用在所有的子路由上 prefix = append(prefix, route.prefix) if route.method != "" && p != "" { r.Handle(route.method, p, route) // 路由有效,注入路由器 Router中 } // 如果路由有子路由,则将子路由挂载进去,如果没有, if len(route.mount) > 0 { for _, subRoute := range route.mount { currentPointer += 1 // 添加一层,进入下一层路由 r.MountRoute(subRoute) } } else { if currentPointer > 0 { currentPointer -= 1 // 减小一层,退回上一层路由 } } } // 根据当前是第几层路由,获取前缀 func getPrefix(current int) string { if len(prefix) > current-1 && len(prefix) != 0 { return strings.Join(prefix[:current], "") } return "" } // 设置中间件,根据当前是第x层路由,将前面的路由放入当前路由中 func setMiddlewares(current int, route *Route) { key := "p" + strconv.Itoa(currentPointer) for _, v := range route.middleware { middlewares[key] = append(middlewares[key], v) } // 将当前路由的父路由的都放入当前路由中 for i := 0; i < currentPointer; i++ { key = "p" + strconv.Itoa(i) if list, ok := middlewares[key]; ok { for _, v := range list { route.middleware = append(route.middleware, v) } } } } ```
首先定义全局变量 :
-
prefix
记录每层路由的前缀,键就是路由层数,值就是路由前缀 -
middlewares
记录每层路由中间件,键可以标识路由层数,值就是该层中间件的所有集合 -
currentPointer
标识当前处在第几层路由,通过它从上面的两个变量中取出属于当前路由层的数据
然后每遍历一次,就把对应前缀和中间件组存入全局变量中,递归调用,再取出合适的数据,最终执行 Handle
方法注入路由器中
上面只是简略的介绍了一下如何制作,具体可以直接看代码,没有难点。
2. 第二部分
我们构建的 server
,都要实现 ServeHttp
方法,这样当请求进来的时候,就会走到我们定义的这个方法中,原本的 httprouter
所定义的 ServeHttp
可以在 这里
看到
过程就是将当前的 URL
,沿着前缀树寻找树叶,找到后直接执行,而我们上面将树叶更改成了 Route
结构体,这样当寻找到的时候,需要先执行它的中间件,再执行它的 targetMethod
方法
而这里的中间件,我们不能直接使用 for
循环去遍历执行,因为这样不能拦截请求,最终都会走到 targetMethod
中,并且没有后置效果,那么如何制作这种功能呢?
laravel
中用到了一种 Pipeline
的方法,也就是管道,让每一个 context
顺序经过每一个中间件,如果被拦截,则不往下传递
具体思路可以看 这里
我实现的源码在 这里
下面使用代码实现:
我们期待的效果是这样:
// 建立管道,执行中间件最终到达路由 new(Pipeline).Send(context).Through(route.middleware).Then(func(context *Context) { route.targetMethod(context) })
首先建立一个管道结构体:
type Pipeline struct { send *Context // 穿过管道的上下文 through []MiddlewareHandle // 中间件数组 current int // 当前执行到第几个中间件 }
Send()
, Through()
方法都是向其中注入内容的,这里就不多说了
主要是 Then
方法:
// 这里是路由的最后一站 func (p *Pipeline) Then(then func(context *Context)) { // 按照顺序执行 // 将then作为最后一站的中间件 var m MiddlewareHandle m = func(c *Context, next func(c *Context)) { then(c) next(c) } p.through = append(p.through, m) p.Exec() }
then
方法将最终要执行的那个方法也封装成了一个中间件,加入了管道的最后,然后执行 Exec
方法,开始从头让 send
中的对象穿过管道:
func (p *Pipeline) Exec() { if len(p.through) > p.current { m := p.through[p.current] p.current += 1 m(p.send, func(c *Context) { p.Exec() }) } }
取出当前指针指向的那个中间件,将当前指针移动到下一个中间件,并且执行刚刚取出的中间件,在其中传入的回调 next
,就是递归执行这个逻辑,执行下一个中间件,
这样在我们的代码中就可以通过 next()
方法的位置,来控制是前置中间件还是后置中间件了
代码不多,但是实现的效果很有趣,感谢 Laravel
我只是重写了一部分他人的东西,感谢开源,受益匪浅,另外 挂一下自己的 web 框架 Bingo ,求 star,欢迎 PR!
以上所述就是小编给大家介绍的《参考Laravel制作基于golang的路由包》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- [Vue 2.x Todo 教程练习参考答案] 标为完成练习参考答案
- [Vue 2.x Todo 教程练习参考答案] 添加todo练习参考答案
- MyOIDC v1.1.0 发布,基于 OIDC 协议的参考实现,根据各类库提供实现参考
- [Vue 2.x Todo 教程练习参考答案] 入门仪式_Hello_Vue练习参考答案
- 微服务参考架构实现
- 领域驱动设计参考
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Head First HTML与CSS(第2版)
Elisabeth Robson、Eric Freeman / 徐阳、丁小峰 / 中国电力出版社 / 2013-9 / 98.00元
是不是已经厌倦了那些深奥的HTML书?你可能在抱怨,只有成为专家之后才能读懂那些书。那么,找一本新修订的《Head First HTML与CSS(第2版)》吧,来真正学习HTML。你可能希望学会HTML和CSS来创建你想要的Web页面,从而能与朋友、家人、粉丝和狂热的顾客更有效地交流。你还希望使用最新的HTML5标准,能够保证随时间维护和扩展你的Web页面,使它们在所有浏览器和移动设备中都能正常工......一起来看看 《Head First HTML与CSS(第2版)》 这本书的介绍吧!
MD5 加密
MD5 加密工具
XML、JSON 在线转换
在线XML、JSON转换工具