基于 Go 的泛型快速实现一个功能完备的路由

xnotepad · · 602 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

原文地址:<https://caixw.io/posts/2022/build-go-router-with-generics.html> Go 语言的路由库有很多,功能上都大同小异,最大的差异应该是路由函数的签名,官方采用了 `http.Handler` 接口,而大部分非官方路由都将 `http.ResponseWriter` 和 `http.Request` 合并成了一个对象。本文介绍的库 <https://github.com/issue9/mux> 利用 go1.18 对泛型的支持,实现了用户自定义该功能的需求,仅需要几步即可实现一个完善的路由,适用于快速开发一个 web 框架。 ## 如何使用 以实现一个兼容 `http.Handler` 接口的路由为例,仅需以下几个步骤即可。 ### 定义路由处理类型 可以是接口,也可以是函数,我们以 `http.Handler` 为例,那么该类型就是 `http.Handler` 接口,此步可以省略。 ### 定义泛型对应的类型 以 `http.Handler` 为参数实例化泛型类型: ```go package custom_router import "github.com/issue9/mux/v6" type ( Router = mux.RouterOf[http.Handler] Prefix = mux.PrefixOf[http.Handler] Resource = mux.ResourceOf[http.Handler] Options = mux.OptionsOf[http.Handler] Middleware = mux.MiddlewareOf[http.Handler] ) ``` ### 定义 New 函数 我们需要一个 `CallOf` 函数,用于将给定的参数转换成调用 `http.Handler` 的方法。其原型如下: ```go CallOf[T any] func(http.ResponseWriter, *http.Request, Params, T) ``` `New` 可以直接调用 `NewRouterOf` 方法,给出 `CallOf` 的实例化方法即可。 ```go package custom_router import "github.com/issue9/mux/v6" func call(w http.ResponseWriter, r *http.Request, ps Params, h http.Handler) { h.ServeHTTP(w, WithValue(r, ps)) } func New(name string, o *Options) *Router { return NewRouterOf[http.Handler](name, call, o) } ``` ### 辅助函数 然后定义一些辅助函数,比如将参数写入到 `http.Request` 和从 `http.Request` 中获取参数。 ```go package custom_router import "github.com/issue9/mux/v6" type contextKey int const contextKeyParams contextKey = 0 // GetParams 获取当前请求实例上的参数列表 func GetParams(r *http.Request) mux.Params { if ps := r.Context().Value(contextKeyParams); ps != nil { return ps.(Params) } return nil } // WithValue 将参数 ps 附加在 r 上 func WithValue(r *http.Request, ps mux.Params) *http.Request { if ps == nil || ps.Count() == 0 { return r } if ps2 := GetParams(r); ps2 != nil && ps2.Count() > 0 { ps2.Range(func(k, v string) { ps.Set(k, v) }) } return r.WithContext(context.WithValue(r.Context(), contextKeyParams, ps)) } ``` 这样一个兼容 `http.Handler` 的路由就完成了,之后就可以正常使用路由。 它支持普通字符串匹配,也支持以 `{name:rule}` 形式的匹配,其中 rule 可以是正则表达式或空值。具体的语法可以参考 <https://github.com/issue9/mux>。 ```go package custom_router func main() { r := New("", &Options{}) r.Get("/users/{id:\\d+}", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){ // TODO })) } ``` ## 一个更复杂的例子 下面定义了一个新路由,摒弃了官方的 `http.ResponseWriter` 和 `http.Request`,采用 `Context` 对象传递上下文所需的内容。所以我们自定义了一个 `Handler` 接口用以代替官方的 `http.Handler` 接口,其 `Handle` 方法的参数只接收 `Context` 对象。 对于 `New` 方法也不再是直接传递 `*options` 对象,而是以可选的函数方法的形式传递。 ```go package custom_router import "github.com/issue9/mux/v6" type ( Context struct { R *http.Request W http.ResponseWriter P mux.Params } Handler interface { Handle(*Context) } HandlerFunc func(*Context) Router = mux.RouterOf[Handler] Prefix = mux.PrefixOf[Handler] Resource = mux.ResourceOf[Handler] Middleware = mux.MiddlewareOf[Handler] options = mux.OptionsOf[Handler] Option func(o *options) ) func (f HandlerFunc) Handle(c *Context) { f(c) } func call(w http.ResponseWriter, r *http.Request, ps mux.Params, h Handler) { h.Handle(&Context{R: r, W: w, P: ps}) } func New(name string, o ...Option) *Router { opt := &options{} for _, oo := range o { oo(opt) } return NewRouterOf[Handler](name, call, opt) } // 一些实现 Option 的函数,整个 options 的内容都可以采用此方式设置。 func Lock(o *options) { o.Lock = true } func Unlock(o *options) { o.Lock = false } func NotFound(f http.HandlerFunc) Option { if f == nil { f = http.NotFound } return func(o *Options) { o.NotFound = f } } ``` 之后就可以正常使用路由: ```go package custom_router func main() { r := New("") r.Get("/users/{id:\\d+}", HandlerFunc(func(ctx *Context){ // TODO })) } ``` ## 性能 有关性能可以参考 <https://caixw.github.io/go-http-routers-testing/> 提供了基于 `http.Handler` 的性能测试。

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

602 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传