基于 Go net/http 开发的轻量路由框架-forest

2022-11-15 00:00:11 +08:00
 honmaple

很早就写完了,貌似还没分享过。

之前阅读过 Gin ,Echo 的源码,觉得 Go 里面基于net/http写一个自己的路由框架还挺简单的,然后就动手了,没想到看起来容易,部分细节的处理还是挺麻烦的

与 Gin 和 Echo 的区别

Gin 和 Echo 我比较喜欢 Echo 的设计,所以很多地方都借鉴了 Echo ,比如 Context 是接口类型,每一个路由处理函数都会返回 error ,而 Gin 里借鉴里中间件的实现,使用一个列表变量+索引的方式实现,未使用闭包

路由

使用了自己写的 radix tree ,动态路由性能上可能比 Gin 和 Echo 弱了一丢丢,静态路由性能要高一丢丢,测试可见 https://github.com/honmaple/forest/tree/master/examples/benchmark

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 里面,有兴趣的可以看一下

地址: https://github.com/honmaple/forest

1948 次点击
所在节点    分享创造
7 条回复
sunorg
2022-11-15 00:03:06 +08:00
来个特别大的特点?
honmaple
2022-11-15 00:23:57 +08:00
@sunorg 正则路由和可扩展参数路由不知道算不算,还有就是路径参数可以放在 url 中间,我看很多路由框架都是只能放到末尾;以及路由分组可挂载,不受主路由影响,方便扩展;其它的一些扩展库 session ,swagger 也有,不过这应该属于常规功能 https://github.com/honmaple/forest-contrib

web 路由框架基本的功能都差不多,重量级的特点还未曾想到,如果有什么建议我可以看看能否实现
murongxdb
2022-11-15 08:47:56 +08:00
先来一个 star
spatxos
2022-11-15 08:49:42 +08:00
已 star
yanzhiling2001
2022-11-15 09:24:26 +08:00
第三个 star
sunorg
2022-11-15 13:47:04 +08:00
@honmaple 不算,但必须来个 star 支持下。
EZVIK
2022-11-18 16:17:48 +08:00
最近也在学习写路由解析,star 学习一下

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/895265

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX