首页 > 其他分享 >Mygin实现动态路由

Mygin实现动态路由

时间:2024-01-23 13:45:00浏览次数:26  
标签:string parts Mygin part func 动态 节点 路由 name

本篇是Mygin的第四篇

目的

  • 使用 Trie 树实现动态路由解析。
  • 参数绑定

前缀树

本篇比前几篇要复杂一点,原来的路由是用map实现,索引非常高效,但是有一个弊端,键值对的存储的方式,只能用来索引静态路由。遇到类似hello/:name这动态路由就无能为力了,实现动态路由最常用的数据结构,被称为前缀树。这种结构非常适用于路由匹配。比如我们定义了如下路由:

  • /a/b/c
  • /a/b
  • /a/c
  • /a/b/c/d
  • /a/:name/c
  • /a/:name/c/d
  • /a/b/:name/e

在前缀树中的结构体

HTTP请求的路径是由/分隔的字符串构成的,所以用/拆分URL字符串,得到不同的树节点,且有对应的层级关系。

  • Mygin/tree.go 首先看tree中node结构定义
type node struct {
	children  []*node //子节点
	part      string  //树节点
	wildChild bool    //是否是精确匹配
	handlers  HandlersChain //路由回调,实际的请求
	nType     nodeType  //节点类型 默认static params
	fullPath  string  //完整路径
}
  • Mygin/tree.go 具体实现
package mygin

import (
	"strings"
)

type nodeType uint8

// 路由的类型
const (
	static nodeType = iota
	root
	param
	catchAll
)

// 不同的method 对应不同的节点树 定义
type methodTree struct {
	method string
	root   *node
}

// Param 参数的类型key=> value
type Param struct {
	Key   string
	Value string
}

// Params 切片
type Params []Param

type methodTrees []methodTree

type node struct {
	children  []*node
	part      string
	wildChild bool
	handlers  HandlersChain
	nType     nodeType
	fullPath  string
}

// Get 获取 参数中的值
func (ps Params) Get(name string) (string, bool) {
	for _, entry := range ps {
		if entry.Key == name {
			return entry.Value, true
		}
	}
	return "", false
}

// ByName 通过ByName获取参数中的值 会忽略掉错误,默认返回 空字符串
func (ps Params) ByName(name string) (v string) {
	v, _ = ps.Get(name)
	return
}

// 根据method获取root
func (trees methodTrees) get(method string) *node {
	for _, tree := range trees {
		if tree.method == method {
			return tree.root
		}
	}
	return nil
}

// 添加路径时
func (n *node) addRoute(path string, handlers HandlersChain) {

	//根据请求路径按照'/'划分
	parts := n.parseFullPath(path)

	//将节点插入路由后,返回最后一个节点
	matchNode := n.insert(parts)

	//最后的节点,绑定执行链
	matchNode.handlers = handlers

	//最后的节点,绑定完全的URL,后续param时有用
	matchNode.fullPath = path

}

// 按照 "/" 拆分字符串
func (n *node) parseFullPath(fullPath string) []string {
	splits := strings.Split(fullPath, "/")
	parts := make([]string, 0)
	for _, part := range splits {
		if part != "" {
			parts = append(parts, part)
			if part == "*" {
				break
			}
		}
	}
	return parts
}

// 根据路径 生成节点树
func (n *node) insert(parts []string) *node {
	part := parts[0]
	//默认的字节类型为静态类型
	nt := static
	//根据前缀判断节点类型
	switch part[0] {
	case ':':
		nt = param
	case '*':
		nt = catchAll
	}

	//插入的节点查找
	var matchNode *node
	for _, childNode := range n.children {
		if childNode.part == part {
			matchNode = childNode
		}
	}

	//如果即将插入的节点没有找到,则新建一个
	if matchNode == nil {
		matchNode = &node{
			part:      part,
			wildChild: part[0] == '*' || part[0] == ':',
			nType:     nt,
		}
		//新子节点追加到当前的子节点中
		n.children = append(n.children, matchNode)
	}

	//当最后插入的节点时,类型赋值,且返回最后的节点
	if len(parts) == 1 {
		matchNode.nType = nt
		return matchNode
	}

	//匹配下一部分
	parts = parts[1:]
	//子节点继续插入剩余字部分
	return matchNode.insert(parts)
}

// 根据路由 查询符合条件的节点
func (n *node) search(parts []string, searchNode *[]*node) {
	part := parts[0] //a

	allChild := n.matchChild(part) //b c :name

	if len(parts) == 1 {
		// 如果到达路径末尾,将所有匹配的节点加入结果
		*searchNode = append(*searchNode, allChild...)
		return
	}

	parts = parts[1:] //b

	for _, n2 := range allChild {
		// 递归查找下一部分
		n2.search(parts, searchNode)
	}

}

// 根据part 返回匹配成功的子节点
func (n *node) matchChild(part string) []*node {

	allChild := make([]*node, 0)
	for _, child := range n.children {
		if child.wildChild || child.part == part {
			allChild = append(allChild, child)
		}
	}

	return allChild
}

上诉路由中,实现了插入insert和匹配search时的功能,插入时安装拆分后的子节点,递归查找每一层的节点,如果没有匹配到当前part的节点,则新建一个。查询功能,同样也是递归查询每一层的节点。

  • Mygin/router.go
package mygin

import (
	"net/http"
)

type Router struct {
	trees methodTrees
}

// 添加路由方法
func (r *Router) addRoute(method, path string, handlers HandlersChain) {
	//根据method获取root
	rootTree := r.trees.get(method)

	//如果root为空
	if rootTree == nil {
		//初始化一个root
		rootTree = &node{part: "/", nType: root}
		//将初始化后的root 加入tree树中
		r.trees = append(r.trees, methodTree{method: method, root: rootTree})

	}

	rootTree.addRoute(path, handlers)

}

// Get Get方法
func (r *Router) Get(path string, handlers ...HandlerFunc) {
	r.addRoute(http.MethodGet, path, handlers)
}

// Post  Post方法
func (e *Engine) Post(path string, handlers ...HandlerFunc) {
	e.addRoute(http.MethodPost, path, handlers)
}
  • router中修改不大

  • Mygin/engine.go

package mygin

import (
	"net/http"
)

// HandlerFunc 定义处理函数类型
type HandlerFunc func(*Context)
// HandlersChain 定义处理函数链类型
type HandlersChain []HandlerFunc

// Engine 定义引擎结构,包含路由器
type Engine struct {
	Router
}

// 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 {
	return &Engine{
		Router: Router{
			trees: make(methodTrees, 0, 9),
		},
	}
}

// Run 启动HTTP服务器的方法
func (e *Engine) Run(addr string) error {
	return http.ListenAndServe(addr, e)
}

package main

import (
	"gophp/mygin"
)

func main() {
	// 创建一个默认的 mygin 实例
	r := mygin.Default()

	// 定义路由处理函数
	handleABC := func(context *mygin.Context) {
		context.JSON(map[string]interface{}{
			"path": context.Request.URL.Path,
		})
	}

	// 注册路由
	r.Get("/a/b/c", handleABC)
	r.Get("/a/b", handleABC)
	r.Get("/a/c", handleABC)

	// 注册带参数的路由
	r.Get("/a/:name/c", func(context *mygin.Context) {
		name := context.Params.ByName("name")
		path := "/a/" + name + "/c"
		context.JSON(map[string]interface{}{
			"path": path,
		})
	})

	r.Get("/a/:name/c/d", func(context *mygin.Context) {
		name := context.Params.ByName("name")
		path := "/a/" + name + "/c/d"
		context.JSON(map[string]interface{}{
			"path": path,
		})
	})

	r.Get("/a/b/:name/e", func(context *mygin.Context) {
		name := context.Params.ByName("name")
		path := "/a/b" + name + "/e"
		context.JSON(map[string]interface{}{
			"path": path,
		})
	})

	r.Get("/a/b/c/d", handleABC)

	// 启动服务器并监听端口
	r.Run(":8088")
}

标签:string,parts,Mygin,part,func,动态,节点,路由,name
From: https://www.cnblogs.com/pengb/p/17980370

相关文章

  • 编辑距离 动态规划又一题型
    这种题目一般是给两个字符串,找一些特性或进行一些操作。根据题目意思设置二维数组,行列长度为两个字数组的长度加1。之所以加一是为了留空间对空字符串进行讨论。递推式就是两个数组中如果元素相等怎么样,不相等又怎么样。最长公共子序列:点击查看代码if(text1[i-1]==text......
  • rocketmq--如何做路由发现、注册、剔除的
    RocketMQ的NameServer是一个轻量级的服务,负责维护关于Broker的路由信息和提供路由查询服务。以下是NameServer在Broker管理、路由发现、路由注册和路由剔除方面的工作机制:Broker管理:Broker在启动时会向所有的NameServer发送注册请求,包含自己的地址、存储的队列......
  • 动态 DP
    序列——线段树维护矩阵转移首先,我们需要普通DP方程改为矩阵乘法或广义矩阵乘法形式,可能会增加一些状态,然后用线段树维护矩阵乘法即可。这个模型推荐使用横向量乘方阵的形式,可以直接从左往右乘。树——重链剖分原理首先,记录每个节点的轻儿子对这个节点DP数组的贡献,然后用......
  • VUE框架CLI组件化配置Router路由局部守卫path和componet和router完整项目实现------VU
    <template><div><!--组件分为普通组件和路由组件--><divclass="s2"><h2>县区</h2><ul><!--query形式接收--><!--<li>{{$route.......
  • 路由器固件模拟环境搭建
    路由器固件模拟环境搭建binwalk安装参考参考链接https://xz.aliyun.com/t/5697?time__1311=n4%2BxnD07Dti%3D0%3DDk8GCDlhjm5fcQQeiKN4D&alichlgref=https%3A%2F%2Fwww.google.com%2Fhttp://zeroisone.cc/2018/03/20/固件模拟调试环境搭建/但是他们都有一个问题,在按他们的步......
  • 【动态规划】正则表达式
    目录1.题目2.应用2.1.Leetcode10.正则表达式匹配题目解题思路代码实现1.题目题目列表:序号题目难度110.正则表达式匹配困难2.应用2.1.Leetcode10.正则表达式匹配题目10.正则表达式匹配解题思路设\(dp[i][j]\)表示\(s\)的前\(i\)个字符与......
  • 深入分析若依数据权限@datascope (注解+AOP+动态sql拼接) 【循序渐进,附分析过程】
    除了我们平时都知道的路由权限(即对接口的访问权限外),在日常生产开发中,我们还应该有对数据的访问权限。在若依这个框架中,通过角色中的数据范围这个属性来对数据权限进行控制。对应实体类:深入分析一个用户肯定是有一种角色的,也肯定是隶属于一个部门的。这里咱们就以用户在......
  • 动态规划(9) 编辑距离最终版
    目录115不同的子序列583.两个字符串的删除操作编辑距离115不同的子序列dp数组的含义:dp[i][j]以i-1结尾的s中包含有多少个以j-1为结尾的tdp初始化:dp[0][0]空字符串中有多少个空字符串,显然1个dp[i][0]以i-1为结尾的s中包含有多少个空字符串,也是1个递推公式:显然需要考虑s[i......
  • 动态规划(8) 编辑距离
    目录1035不相交的线53最大子数组和392判断子序列1035不相交的线弄清楚在求什么,实际上是在求最长公共子序列classSolution{public:intmaxUncrossedLines(vector<int>&nums1,vector<int>&nums2){intn=nums1.size();intm=nums2.size();......
  • 5G穿墙王!TP-LINK发布BE5100 Wi-Fi 7路由器:2.5G网口 279元
    1月14日消息,日前,TP-LINK发布BE5100Wi-Fi7路由器,到手价279元,支持MLO、4KQAM、MRU、前导打孔等Wi-Fi7新特性。据介绍,在MLO多链路技术加持下,2.4G+5G叠加快至7.3倍,4KQAM高阶调制,速率提升至120%。MRU技术允许将多个资源块分配给单个用户,提升传输效率,降低延迟。TP-LINKBE5100还是“......