• Mygin实现分组路由Group


    本篇是Mygin第五篇

    目的

    • 实现路由分组

    为什么要分组

    分组控制(Group Control)是 Web 框架应该提供的基础功能之一,对同一模块功能的开发,应该有相同的前缀。或者对一部分第三方接口,统一需要加解密等功能。分组后很方便。例如:

    • 对于任务模块,统一前缀为/task
    • 除去/user/login接口,都需要鉴权
    • 以/openapi 开头的接口,需要对接第三方平台,需要三方平台鉴权

    大多数分组都是统一前缀,正确的分组可以实现子分组,无限极往下分组,当然实际情况下也不会有太多层分组。每个分组有不同的中间件(middleware),分组与子分组就像洋葱一样,一层一层往内。要想往内,需要拨开最外层,也就是要执行外层的中间件才能往内。对于分组,也有后续扩展的好处,比如/task分组,现在要统一加上访问日志记录,有了分组后就可以在该分组上添加一个中间件就可以了。

    分组嵌套

    在实现分组嵌套之前,先看看gin中的分组嵌套是怎么实现的。gin中的分组中含有Engin的指针,其实很好理解,因为分组要有访问Router的能力。Engin中继承了RouterGroup,实例化Engin后就可以使用Group的功能。有了Engin的指针,整个框架的所有资源都是由Engine去统一调度的,因此通过Engine可以间接地拥有整个框架的功能。你中有我我中有你。

    • gin/routergroup.go
    package mygin
    
    import (
    	"net/http"
    	"path"
    	"regexp"
    )
    
    // IRoutes 定义了路由组的接口
    type IRoutes interface {
    	BasePath() string
    	GET(string, ...HandlerFunc) IRoutes
    	POST(string, ...HandlerFunc) IRoutes
    	DELETE(string, ...HandlerFunc) IRoutes
    	PATCH(string, ...HandlerFunc) IRoutes
    	PUT(string, ...HandlerFunc) IRoutes
    	OPTIONS(string, ...HandlerFunc) IRoutes
    	HEAD(string, ...HandlerFunc) IRoutes
    	Match([]string, string, ...HandlerFunc) IRoutes
    }
    
    // anyMethods 包含所有 HTTP 方法的字符串表示
    var anyMethods = []string{
    	http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch,
    	http.MethodHead, http.MethodOptions, http.MethodDelete, http.MethodConnect,
    	http.MethodTrace,
    }
    
    // RouterGroup 定义了路由组的结构体
    type RouterGroup struct {
    	Handlers HandlersChain // 路由组的中间件处理函数链
    	basePath string        // 路由组的基础路径
    	engine   *Engine       // 路由组所属的引擎
    	root     bool          // 是否是根路由组
    }
    
    // Group 创建一个新的路由组
    func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
    	return &RouterGroup{
    		Handlers: append(group.Handlers, handlers...),
    		basePath: path.Join(group.basePath, relativePath),
    		engine:   group.engine,
    	}
    }
    
    // BasePath 返回路由组的基础路径
    func (group *RouterGroup) BasePath() string {
    	return group.basePath
    }
    
    // handle 处理路由,将路由信息添加到引擎中
    func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
    	absolutePath := path.Join(group.basePath, relativePath)
        //处理函数,Group组定义的函数先执行,自定义的函数后执行
    	handlers = append(group.Handlers, handlers...)
    	group.engine.addRoute(httpMethod, absolutePath, handlers)
    
    	if group.root {
    		return group.engine
    	}
    	return group
    }
    
    // Handle 校验 HTTP 方法的有效性,并处理路由
    func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...HandlerFunc) IRoutes {
    	// 检查 HTTP 方法的有效性
    	if match := regexp.MustCompile("^[A-Z]+$").MatchString(httpMethod); !match {
    		panic("http method " + httpMethod + " is not valid")
    	}
    	// 处理路由
    	return group.handle(httpMethod, relativePath, handlers)
    }
    
    // GET 注册 GET 方法的路由
    func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
    	return group.handle(http.MethodGet, relativePath, handlers)
    }
    
    // POST 注册 POST 方法的路由
    func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
    	return group.handle(http.MethodPost, relativePath, handlers)
    }
    
    // DELETE 注册 DELETE 方法的路由
    func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes {
    	return group.handle(http.MethodDelete, relativePath, handlers)
    }
    
    // PATCH 注册 PATCH 方法的路由
    func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes {
    	return group.handle(http.MethodPatch, relativePath, handlers)
    }
    
    // PUT 注册 PUT 方法的路由
    func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes {
    	return group.handle(http.MethodPut, relativePath, handlers)
    }
    
    // OPTIONS 注册 OPTIONS 方法的路由
    func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes {
    	return group.handle(http.MethodOptions, relativePath, handlers)
    }
    
    // HEAD 注册 HEAD 方法的路由
    func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes {
    	return group.handle(http.MethodHead, relativePath, handlers)
    }
    
    // Match 注册多个方法的路由
    func (group *RouterGroup) Match(methods []string, relativePath string, handlers ...HandlerFunc) IRoutes {
    	for _, method := range methods {
    		group.handle(method, relativePath, handlers)
    	}
    
    	if group.root {
    		return group.engine
    	}
    	return group
    }
    
    // Any 注册所有方法的路由
    func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {
    	for _, method := range anyMethods {
    		group.handle(method, relativePath, handlers)
    	}
    
    	if group.root {
    		return group.engine
    	}
    	return group
    }
    
    

    可以看到group.engine.addRoute方法,实际调用的仍然是mygin/router.go中的addRoute方法。当group.root为true,也就是为根时,相当于没有调用Group方法,返回group.engine,也就是相当于直接调用的engine,只有当调用Group方法后才会返回group。
    接下来看engine的修改

    • mygin/engine.go
    package mygin
    
    import (
    	"net/http"
    )
    
    // HandlerFunc 定义处理函数类型
    type HandlerFunc func(*Context)
    
    // HandlersChain 定义处理函数链类型
    type HandlersChain []HandlerFunc
    
    // Engine 定义引擎结构,包含路由器
    type Engine struct {
    	Router
    	RouterGroup
    }
    
    // ServeHTTP 实现http.Handler接口的方法,用于处理HTTP请求
    func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    	// 获取对应HTTP方法的路由树的根节点
    	root := e.trees.get(r.Method)
    	// 解析请求路径
    	parts := root.parseFullPath(r.URL.Path)
    
    	// 查找符合条件的节点
    	searchNode := make([]*node, 0)
    	root.search(parts, &searchNode)
    
    	// 没有匹配到路由
    	if len(searchNode) == 0 {
    		w.Write([]byte("404 Not found!\n"))
    		return
    	}
    
    	// 参数赋值
    	params := make([]Param, 0)
    	searchPath := root.parseFullPath(searchNode[0].fullPath)
    	for i, sp := range searchPath {
    		if sp[0] == ':' {
    			params = append(params, Param{
    				Key:   sp[1:],
    				Value: parts[i],
    			})
    		}
    	}
    
    	// 获取处理函数链
    	handlers := searchNode[0].handlers
    	if handlers == nil {
    		w.Write([]byte("404 Not found!\n"))
    		return
    	}
    
    	// 执行处理函数链
    	for _, handler := range handlers {
    		handler(&Context{
    			Request: r,
    			Writer:  w,
    			Params:  params,
    		})
    	}
    }
    
    // Default 返回一个默认的引擎实例
    func Default() *Engine {
    	engine := &Engine{
    		Router: Router{
    			trees: make(methodTrees, 0, 9),
    		},
    		RouterGroup: RouterGroup{
    			Handlers: nil,
    			basePath: "/",
    			root:     true,
    		},
    	}
    
    	// Group 保存 engine 的指针
    	engine.RouterGroup.engine = engine
    
    	return engine
    }
    
    // Run 启动HTTP服务器的方法
    func (e *Engine) Run(addr string) error {
    	return http.ListenAndServe(addr, e)
    }
    

    最后来测试一下

    • main.go
    package main
    
    import (
    	"gophp/mygin"
    	"path"
    )
    
    func main() {
    	// 创建一个默认的 mygin 实例
    	r := mygin.Default()
    
    	group := r.Group("/api", func(context *mygin.Context) {
    		context.String("api Group ....\n")
    	})
    
    	group.GET("/hello/:name", func(context *mygin.Context) {
    		name := context.Params.ByName("name")
    		context.String(path.Join("hello ", name, "!"))
    	})
    
    	group.GET("/hello2/:name", func(context *mygin.Context) {
    		name := context.Params.ByName("name")
    		context.String(path.Join("hello2 ", name, "!\n"))
    	})
    
    	// 启动服务器并监听端口
    	r.Run(":8088")
    }
    

    curl请求测试

     curl -i http://localhost:8088/api/hello/scott
    HTTP/1.1 200 OK
    Content-Type: text/plain; charset=utf-8
    Date: Wed, 24 Jan 2024 05:16:45 GMT
    Content-Length: 29
    
    api Group ....
    hello /scott/!
    ~ curl -i http://localhost:8088/api/hello2/scott
    HTTP/1.1 200 OK
    Content-Type: text/plain; charset=utf-8
    Date: Wed, 24 Jan 2024 05:16:54 GMT
    Content-Length: 31
    
    api Group ....
    hello2 /scott/!
    

    可以看到两个路由都安装预定的返回了,即先返回Group定义的,再返回路由定义的。


    __EOF__

  • 本文作者: pengb
  • 本文链接: https://www.cnblogs.com/pengb/p/17982530
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    机器学习算法:支持向量机(SVM)
    网络传输中的重要参数-简单的网络画像
    为什么 ArrayList的 初始容量为10?每次扩容1.5倍?
    PVT v2: Improved Baselines with Pyramid Vision Transformer
    Windows修改中文用户名为英文
    【斑梨】世界最小?linux开发板?价格只要39元 Luckfox Pico Mini 超越树莓派PICO ESP32 Arduino
    手机怎么把几个PDF文件合并到一起?教你一分钟搞定
    I.MX6ull UART
    Apache log4j漏洞总结
    MongoDB综合实战篇(超容易)
  • 原文地址:https://www.cnblogs.com/pengb/p/17982530