很早就写完了,貌似还没分享过。
之前阅读过 Gin ,Echo 的源码,觉得 Go 里面基于net/http
写一个自己的路由框架还挺简单的,然后就动手了,没想到看起来容易,部分细节的处理还是挺麻烦的
Gin 和 Echo 我比较喜欢 Echo 的设计,所以很多地方都借鉴了 Echo ,比如 Context 是接口类型,每一个路由处理函数都会返回 error ,而 Gin 里借鉴里中间件的实现,使用一个列表变量+索引的方式实现,未使用闭包
使用了自己写的 radix tree ,动态路由性能上可能比 Gin 和 Echo 弱了一丢丢,静态路由性能要高一丢丢,测试可见 https://github.com/honmaple/forest/tree/master/examples/benchmark
/api/users/:id
或者/api/files/*filename
/api/users/{id:int}/friends/{avatar:path}
,甚至path在前也可以,不过路由查找性能会下降/api/users/{id:[0-9]+}
import (
"github.com/google/uuid"
)
type UUIDMatcher struct {
}
func (s *UUIDMatcher) Name() string {
return "uuid"
}
func (s *UUIDMatcher) Match(path string, index int, next bool) (int, bool) {
if index > 0 {
return 0, false
}
if len(path) < 18 || (!next && len(path) > 18) {
return 0, false
}
_, err := uuid.Parse(path[:18])
if err != nil {
return 0, false
}
return 18, true
}
func NewUUIDMatcher(rule string) forest.Matcher {
return &UUIDMatcher{}
}
forest.RegisterRule("uuid", NewUUIDMatcher)
router := forest.New()
router.GET("/api/v1/user/{pk:uuid}", handler)
type Params struct {
Text string `query:"text" json:"text" form:"text" param:"text"`
}
p := Params{}
// bind query, method: not POST, PUT, PATCH
// bind form or json or xml, method: POST, PUT, PATCH
c.Bind(&p)
// bind params, GET /test/:text
c.BindParams(&p)
// bind other params
c.BindWith(&p, bind.Query)
c.BindWith(&p, bind.Form)
c.BindWith(&p, bind.MultipartForm)
c.BindWith(&p, bind.JSON)
c.BindWith(&p, bind.XML)
c.BindWith(&p, bind.Params)
c.BindWith(&p, bind.Header)
// custom bind tag
c.BindWith(&p, bind.FormBinder{"json"})
c.BindWith(&p, bind.QueryBinder{"json"})
中间件借鉴了 Gin ,使用一个列表变量+索引,而不是 Echo 多个中间件嵌套闭包的方式
func MyMiddleware(c forest.Context) error {
// do something
// c.Next() is required, or else your handler will not execute
return c.Next()
}
router := forest.New()
// with root
router.Use(MyMiddleware)
// with group
group := router.Group(forest.WithPrefix("/api/v1"), forest.WithMiddlewares(MyMiddleware))
// with special handler
group.GET("/", MyMiddleware, func(c forest.Context) error {
return nil
})
我自己的需求就是开发后台管理系统配置路由权限时为什么要手动输入每一条路由以及它们的 unique name 和描述,所以内置了几个变量,可以在定义时就对路由进行命名
r := forest.New()
g1 := r.Group(forest.WithPrefix("/api"), forest.WithName("g1"))
g2 := g1.Group(forest.WithPrefix("/v1"), forest.WithName("g2"))
r1 := g2.GET("/posts").Named("list_posts", "some description")
r2 := g2.DELETE("/posts/:pk").Named("delete_post", "delete post with pk param")
// result
r.Route("g1.g2.list_posts") == r1
r.URL("g1.g2.list_posts") == r1.URL() == "/v1/api/posts"
r.Route("g1.g2.delete_post") == r2
r.URL("g1.g2.delete_post", "12") == r2.URL("12") == "/v1/api/posts/12"
想要获取全部路由,则可以遍历c.Forest().Routes()
routes := c.Forest().Routes()
ins := make([]forest.H, 0, len(routes))
for _, r := range routes {
ins = append(ins, forest.H{
"id": fmt.Sprintf("%s %s", r.Method(), r.Path()),
"name": r.Name,
"path": r.Path(),
"desc": r.Desc(),
"method": r.Method(),
})
}
除此之外,还有静态文件,自定义错误,自定义 Context ,自定义子域名匹配等功能,有很多功能都是我自己在开发时的需求,觉得有用就增加到 forest 里面,有兴趣的可以看一下
1
sunorg 2022-11-15 00:03:06 +08:00 via Android
来个特别大的特点?
|
2
honmaple OP @sunorg 正则路由和可扩展参数路由不知道算不算,还有就是路径参数可以放在 url 中间,我看很多路由框架都是只能放到末尾;以及路由分组可挂载,不受主路由影响,方便扩展;其它的一些扩展库 session ,swagger 也有,不过这应该属于常规功能 https://github.com/honmaple/forest-contrib
web 路由框架基本的功能都差不多,重量级的特点还未曾想到,如果有什么建议我可以看看能否实现 |
3
murongxdb 2022-11-15 08:47:56 +08:00
先来一个 star
|
4
spatxos 2022-11-15 08:49:42 +08:00
已 star
|
5
40EaE5uJO3Xt1VVa 2022-11-15 09:24:26 +08:00
第三个 star
|
7
EZVIK 2022-11-18 16:17:48 +08:00
最近也在学习写路由解析,star 学习一下
|