学习Go语言Web框架Gee总结--前缀树路由Router(三)

学习Go语言Web框架Gee总结--前缀树路由Router

  • router/gee/trie.go
  • router/gee/router.go
  • router/gee/context.go
  • router/main.go

学习网站来源:Gee

项目目录结构:
在这里插入图片描述

router/gee/trie.go

实现动态路由最常用的数据结构,被称为前缀树(Trie树)

关于前缀树:

  1. 根节点不包含字符,除根节点外每一个节点都只包含一个字符
  2. 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串
  3. 每个节点的所有子节点包含的字符都不相同
package gee

import (
	"fmt"
	"strings"
)

//node结构体表示路由节点,包含了待匹配的路由pattern、路由中的一部分part、子节点children和是否精确匹配isWild
type node struct {
	pattern  string // 待匹配路由,例如 /p/:lang
	part     string // 路由中的一部分,例如 :lang
	children []*node // 子节点,例如 [doc, tutorial, intro]
	isWild   bool // 是否精确匹配,part 含有 : 或 * 时为true
}

/* 
用于将节点的信息格式化为字符串的方法
该方法返回一个包含节点模式(pattern)、节点部分(part)和是否是通配符(isWild)的字符串
其中%t是用于格式化布尔值的占位符
例如,如果有一个节点n,它的pattern为"/user/:id",part为":id",isWild为true,那么调用n.String()会返回以下格式的字符串:
"node{pattern=/user/:id, part=:id, isWild=true}"
*/
func (n *node) String() string {
	return fmt.Sprintf("node{pattern=%s, part=%s, isWild=%t}", n.pattern, n.part, n.isWild)
}

/*
用于遍历路由树中的节点,并将具有路由模式的节点添加到传入的列表中
方法接收一个指向节点切片的指针作为参数,然后遍历当前节点及其子节点,将具有路由模式的节点添加到传入的节点切片中
首先,如果当前节点的pattern不为空(即具有路由模式),则将当前节点添加到传入的节点切片中。
然后,遍历当前节点的子节点,对每个子节点递归调用travel方法,将子节点及其子节点的路由模式节点添加到传入的节点切片中
*/
func (n *node) travel(list *[]*node) {
	if n.pattern != "" {
		*list = append(*list, n)
	}
	for _, child := range n.children {
		child.travel(list)
	}
}

/*
用于在子节点中找到第一个匹配成功的节点,如果子节点的part和传入的part相等,或者子节点是通配符(isWild为true)
则返回该子节点。如果没有匹配成功的子节点,则返回nil
*/
func (n *node) matchChild(part string) *node {
	for _, child := range n.children {
		if child.part == part || child.isWild {
			return child
		}
	}
	return nil
}

/*
用于在子节点中找到所有匹配成功的节点,如果子节点的part和传入的part相等,或者子节点是通配符(isWild为true)
则将该子节点加入到nodes切片中。最后返回nodes切片,其中包含了所有匹配成功的子节点
*/
func (n *node) matchChildren(part string) []*node {
	nodes := make([]*node, 0)
	for _, child := range n.children {
		if child.part == part || child.isWild {
			nodes = append(nodes, child)
		}
	}
	return nodes
}

/*
用于向路由树中插入路由。它接收三个参数:pattern表示要插入的路由模式,parts表示路由模式分割后的部分,height表示当前插入的层级
首先,它检查当前层级是否已经是最后一层(即parts的长度是否等于height),如果是,则将当前节点的pattern设置为传入的pattern,表示找到了对应的路由
否则,它从parts中取出当前层级的部分,然后调用matchChild方法查找是否已经存在对应的子节点
如果没有找到对应的子节点,说明需要创建一个新的子节点,然后将其插入到当前节点的children中
然后,递归调用insert方法,将pattern、parts和height+1传入新创建的子节点中,继续插入下一层级的路由
*/
func (n *node) insert(pattern string, parts []string, height int) {
	if len(parts) == height {
		n.pattern = pattern
		return
	}
	part := parts[height]
	child := n.matchChild(part)
	if child == nil {
		child = &node{part: part, isWild: part[0] == ':' || part[0] == '*'}
		n.children = append(n.children, child)
	}
	child.insert(pattern, parts, height+1)
}

/*
用于在路由树中搜索路由。它接收两个参数:parts表示路由模式分割后的部分,height表示当前搜索的层级
首先,它检查当前层级是否已经是最后一层(即parts的长度是否等于height),或者当前节点是一个通配符节点(part以*开头)
如果是,则检查当前节点是否有对应的路由模式,如果有,则返回当前节点,表示找到了对应的路由
否则,它从parts中取出当前层级的部分,然后调用matchChildren方法查找所有匹配的子节点
然后,对于每一个匹配的子节点,递归调用search方法,将parts和height+1传入子节点中,继续搜索下一层级的路由
如果在递归过程中找到了对应的路由节点,则直接返回该节点;如果没有找到,则返回nil
*/
func (n *node) search(parts []string, height int) *node {
	if len(parts) == height || strings.HasPrefix(n.part, "*") {
		if n.pattern == "" {
			return nil
		}
		return n
	}
	part := parts[height]
	children := n.matchChildren(part)

	for _, child := range children {
		result := child.search(parts, height+1)
		if result != nil {
			return result
		}
	}
	return nil
}

router/gee/router.go

Trie 树的插入与查找都成功实现了,接下来我们将 Trie 树应用到路由中去吧。我们使用 roots 来存储每种请求方式的Trie 树根节点。使用 handlers 存储每种请求方式的 HandlerFunc 。getRoute 函数中,还解析了:和*两种匹配符的参数,返回一个 map

package gee

import (
	"net/http"
	"strings"
)

//用于存储路由树和处理函数
type router struct {
	roots    map[string]*node
	handlers map[string]HandlerFunc
}


func newRouter() *router {
	return &router{
		roots:    make(map[string]*node),
		handlers: make(map[string]HandlerFunc),
	}
}

//用于解析路由模式的函数
func parsePattern(pattern string) []string {
	vs := strings.Split(pattern, "/")

	parts := make([]string, 0)
	for _, item := range vs {
		if item != "" {
			parts = append(parts, item)
			if item[0] == '*' {
				break
			}
		}
	}
	return parts
}

/*
用于向路由器中添加路由
首先调用 parsePattern 函数解析路由模式,然后将方法和模式拼接成键,并检查 roots 中是否存在对应的方法
如果不存在,则将方法添加到 roots 中,然后调用 insert 方法将模式插入到路由树中,并将处理函数添加到 handlers 中
*/
func (r *router) addRoute(method string, pattern string, handler HandlerFunc) {
	parts := parsePattern(pattern)

	key := method + "-" + pattern
	_, ok := r.roots[method]
	if !ok {
		r.roots[method] = &node{}
	}
	r.roots[method].insert(pattern, parts, 0)
	r.handlers[key] = handler
}

/*
用于根据请求方法和路径获取路由
首先调用 parsePattern 函数解析路径,然后检查 roots 中是否存在对应的方法
如果存在,则调用 search 方法在路由树中查找匹配的节点,并将匹配的部分和参数存储到 params 中
最后返回匹配的节点和参数
*/
func (r *router) getRoute(method string, path string) (*node, map[string]string) {
	searchParts := parsePattern(path)
	params := make(map[string]string)
	root, ok := r.roots[method]

	if !ok {
		return nil, nil
	}

	n := root.search(searchParts, 0)

	if n != nil {
		parts := parsePattern(n.pattern)
		for index, part := range parts {
			if part[0] == ':' {
				params[part[1:]] = searchParts[index]
			}
			if part[0] == '*' && len(part) > 1 {
				params[part[1:]] = strings.Join(searchParts[index:], "/")
				break
			}
		}
		return n, params
	}

	return nil, nil
}

/*
用于获取指定请求方法下的所有路由节点
首先检查 roots 中是否存在对应的方法
如果存在,则调用 travel 方法遍历路由树并将节点存储到切片中,最后返回该切片
*/
func (r *router) getRoutes(method string) []*node {
	root, ok := r.roots[method]
	if !ok {
		return nil
	}
	nodes := make([]*node, 0)
	root.travel(&nodes)
	return nodes
}

/*
用于处理请求
首先调用 getRoute 方法获取匹配的路由节点和参数
然后根据匹配的节点和请求方法拼接键,从 handlers 中取出对应的处理函数并调用它
如果找不到匹配的路由节点,则返回 404 NOT FOUND
*/
func (r *router) handle(c *Context) {
	n, params := r.getRoute(c.Method, c.Path)
	if n != nil {
		c.Params = params
		key := c.Method + "-" + n.pattern
		r.handlers[key](c)
	} else {
		c.String(http.StatusNotFound, "404 NOT FOUND: %s\n", c.Path)
	}
}

router/gee/context.go

由于需要能够访问到解析的参数,需要对context对象增加关于param的属性与方法

type Context struct {
	// origin objects
	Writer http.ResponseWriter
	Req    *http.Request
	// request info
	Path   string
	Method string
	Params map[string]string
	// response info
	StatusCode int
}

func (c *Context) Param(key string) string {
	value, _ := c.Params[key]
	return value
}

router/main.go

package main
import (
	"net/http"
	"gee"
)

func main() {
	r := gee.New()
	r.GET("/", func(c *gee.Context) {
		c.HTML(http.StatusOK, "<h1>Hello Gee</h1>")
	})

	r.GET("/hello", func(c *gee.Context) {
		c.String(http.StatusOK, "hello %s, you're at %s\n", c.Query("name"), c.Path)
	})

	r.GET("/hello/:name", func(c *gee.Context) {
		c.String(http.StatusOK, "hello %s, you're at %s\n", c.Param("name"), c.Path)
	})

	//添加了一个带有通配符的路由
	//当用户访问 /assets/some/file/path 路径时,会执行传入的匿名函数,该函数从路径参数中获取 filepath 的值
	//并将其包含在 JSON 响应中返回
	r.GET("/assets/*filepath", func(c *gee.Context) {
		c.JSON(http.StatusOK, gee.H{"filepath": c.Param("filepath")})
	})

	r.Run(":9999")
}

这样,动态路由就成功实现了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/291683.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL之CRUD,函数与union使用

目录 一.CRUD 1.1.SELECT(查询) 1.2.INSERT&#xff08;新增&#xff09; 1.3.UPDATE(修改) 1.4.DELETE&#xff08;删除&#xff09; 二.函数 2.1.常见函数 2.1.1.字符函数 2.1.2.数字函数 2.1.3.日期函数 2.2.流程控制函数 2.3.聚合函数 三.union与union all 四…

物流实时数仓:数仓搭建(DWS)一

系列文章目录 物流实时数仓&#xff1a;采集通道搭建 物流实时数仓&#xff1a;数仓搭建 物流实时数仓&#xff1a;数仓搭建&#xff08;DIM&#xff09; 物流实时数仓&#xff1a;数仓搭建&#xff08;DWD&#xff09;一 物流实时数仓&#xff1a;数仓搭建&#xff08;DWD&am…

JMeter CSV 参数文件的使用方法

.在 JMeter 测试中&#xff0c;参数化是非常重要的&#xff0c;参数化允许我们模拟真实世界中的各种情况。本文我们将探讨如何在 JMeter 中使用 CSV 参数文件。 创建 CSV 文件 首先&#xff0c;我们需要创建一个逗号分隔的值&#xff08;CSV&#xff09;文件&#xff0c;其中…

创建企业邮箱帐户指南:常见问题与解决方法分享

专业的电子邮件地址可以帮助客户识别商务人士&#xff0c;并了解公司给他们发邮件的目的。如果你从事管理、信息技术或人力资源工作&#xff0c;你可能会负责为一个企业建立一个企业邮箱帐户。了解如何为新员工和现有员工设置电子邮件帐户可以帮助您简化公司内部的沟通。 在这篇…

python小工具之弱密码检测工具

一、引用的python模块 Crypto&#xff1a; Python中一个强大的加密模块&#xff0c;提供了许多常见的加密算法和工具。它建立在pyc.ypodome或pyc.ypto等底层加密库之上&#xff0c;为Python程序员提供了简单易用的API&#xff0c;使其可以轻松地实现各种加密功能。 commands…

牵绳遛狗你我他文明家园每一天,助力共建文明社区,基于YOLOv5开发构建公共场景下未牵绳遛狗检测识别系统

遛狗是每天要打卡的事情&#xff0c;狗狗生性活泼爱动&#xff0c;一天不遛就浑身难受&#xff0c;遛狗最重要的就是要拴绳了&#xff0c;牵紧文明绳是养犬人的必修课。外出遛狗时&#xff0c;主人手上的牵引绳更多是狗狗生命健康的一道重要屏障。每天的社区生活中&#xff0c;…

秋招复习之数据结构

目录 前言 1 数据结构分类 2 基本数据类型 3 数字编码 4 字符编码 总结 前言 秋招复习之数据结构&#xff0c;数据结构分类、基本数据类型、字符编码、数字编码等基础知识。 1 数据结构分类 数据结构分为逻辑结构和物理结构。 逻辑结构分为线性数据结构&#xff08;数组链表…

在word文档中自制代码段样式

附&#xff1a; 在word中插入高亮代码的网站 HighlightCode&#xff1a;https://highlightcode.com/ CodeInWord&#xff1a;http://codeinword.com/ 一、新建代码段样式 点击下拉按钮&#xff0c;选择创建样式&#xff0c;命名为代码段&#xff0c;然后点击修改 点击格式&a…

CSS transition详解

文章目录 属性transition-propertytransition-durationtransition-timing-functiontransition-delaytransition 简写属性 方法Element&#xff1a;transitionrun 事件Element&#xff1a;transitionstart 事件Element&#xff1a;transitionend 事件Element&#xff1a;transit…

数据表示和进制转换

输入计算机的数字、字符、符号等信息必须转换成0、1组合的数据形式才能被计算机接收、存储并进行运算。能够进行算术运算并且得到明确的数值的数据概念的信息叫数值数据&#xff0c;其余的信息成为非数值数据。 权&#xff1a;每位数的数值。 基数&#xff1a;指该进位制中允…

【C初阶——指针2】鹏哥C语言系列文章,基本语法知识全面讲解——指针(2)

崩刃的剑&#xff0c;依旧致命&#xff0c;锈蚀的盾&#xff0c;屹立如初&#xff08;王者荣耀李信&#xff09; 本文由睡觉待开机原创&#xff0c;转载请注明出处。 本内容在csdn网站首发 欢迎各位点赞—评论—收藏 如果存在不足之处请评论留言&#xff0c;共同进步&#xff0…

Elasticsearch:结合 ELSER 和 BM25 文本查询的相关搜索

Elastic Learned Spare EncodeR (ELSER) 允许你执行语义搜索以获得更相关的搜索结果。 然而&#xff0c;有时&#xff0c;将语义搜索结果与常规关键字搜索结果相结合以获得最佳结果会更有用。 问题是&#xff0c;如何结合文本和语义搜索结果&#xff1f; 首先&#xff0c;让我…

揭秘Linux软链接:如何轻松创建、删除和修改

揭秘Linux软链接&#xff1a;如何轻松创建、删除和修改 一、简介二、创建软链接三、删除软链接四、修改软链接五、Linux软链接的高级用法六、总结 一、简介 在Linux中&#xff0c;软链接&#xff08;Symbolic Link&#xff09;是一种特殊的文件类型&#xff0c;它是一个指向另…

初识对抗生成网络(GAN)

在研究语义通信的时候&#xff0c;发现解码端很多都是用GAN或基于GAN来完成的。带着对GAN的好奇&#xff0c;对GAN进行了一个初步学习。这篇文章介绍一下和GAN相关的一些常识吧~   本文围绕以下几个内容展开&#xff1a;     1.什么是GAN&#xff1f;     2.为什么要…

03-微服务-Ribbon负载均衡

Ribbon负载均衡 1.1.负载均衡原理 SpringCloud底层其实是利用了一个名为Ribbon的组件&#xff0c;来实现负载均衡功能的。 那么我们发出的请求明明是http://userservice/user/1&#xff0c;怎么变成了http://localhost:8081的呢&#xff1f; 1.2.源码跟踪 为什么我们只输入…

FOURANDSIX:2.01

靶场下载 FourAndSix: 2.01 ~ VulnHub 信息收集 # nmap -sn 192.168.1.0/24 -oN live.nmap Starting Nmap 7.94 ( https://nmap.org ) at 2024-01-02 10:42 CST Nmap scan report for 192.168.1.1 Host is up (0.00030s latency). MAC Address: 00…

JavaScript 基础三part1.数组

JavaScript 基础三part1.数组 2.1 数组是什么2.2 数组的基本使用&#xff08;1&#xff09;声明&#xff08;2&#xff09;取值&#xff08;3&#xff09;一些术语&#xff08;4&#xff09;遍历数组 2.3 操作数组&#xff08;1&#xff09;修改&#xff08;2&#xff09;新增&…

SpringBoot日志打印Logback详解【子节点详解】【附案例】

笑小枫的专属目录 1. 背景2. 什么是Logback3. SpringBoot使用logback介绍4. 自定义logback配置5. 如何把日志同步到ES中6. logback配置属性详解根节点< configuration>子节点:< property>子节点:< appender>filetargetappendprudentlayout和encoderlayout和e…

Spring的bean的生命周期!!!

一.单例模式 单例&#xff1a;[启动容器]--->通过构造方法&#xff08;创建对象&#xff09;---->调用set方法&#xff08;注入&#xff09;--->调用init方法&#xff08;初始化&#xff09;----[容器关闭]----->调用destroy方法&#xff08;销毁&#xff09; app…

任务需求分析中的流程图、用例图、er图、类图、时序图线段、图形的作用意义

任务需求分析中的流程图、用例图、er图、类图、时序图线段、图形的作用意义 流程图 流程图中各种图形的含义及用法解析 连接线符号 连接各要素&#xff0c;表示流程的顺序或过程的方向。 批注符号 批注或说明&#xff0c;也可以做条件叙述。 子流程 流程中一部分图形的逻辑…