很早就写完了,貌似还没分享过。
之前阅读过 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 里面,有兴趣的可以看一下
这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。
V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。
V2EX is a community of developers, designers and creative people.