手把手和你一起实现一个Web框架实战——EzWeb框架(三)[Go语言笔记]Go项目实战
手把手和你一起实现一个Web框架实战——EzWeb框架(三)[Go语言笔记]Go项目实战
代码仓库:
github
gitee
中文注释,非常详尽,可以配合食用
本篇代码,请选择demo3
这一篇文章我们进行动态路由解析功能的设计,
如xxx/:id/xxx,xxx/xxx/*mrxuexi.md
实现这处理这两类模式的简单小功能,实现起来不简单,原有的map[path]HandlerFunc数据结构只能存储静态路由与方法对应,而无法处理动态路由,我们使用一种树结构来进行路由表的存储。
一、设计这个数据结构
1、节点结构体设计
type node struct {
path string /* 需要匹配的整体路由 */
part string /* 路由中的一部分,例如 :lang */
children []*node /* 存储子节点们 */
isBlurry bool /* 如果模糊匹配则为true */
}
2、一个传入part后,通过遍历该节点的全部子节点们,找到拥有相同part的子节点的方法(返回首个)
func (n *node) matchChild(part string) *node {
//遍历子节点们,对比子节点的part和part是否相同,是或者遍历到的子节点支持模糊匹配则返回该子节点
for _, child := range n.children {
if child.part == part || child.isBlurry {
return child
}
}
return nil
}
3、一个返回匹配的子节点们的方法(返回全部,包括动态路由的存储的部分)
func (n *node) matchChildren(part string) []*node {
nodes := make([]*node, 0)
//遍历选择满足条件的子节点,加入到nodes中,然后返回
for _, child := range n.children {
if child.part == part || child.isBlurry {
nodes = append(nodes, child)
}
}
return nodes
}
4、构造路由表的插入方法,parts[]
存储的是根据路由path分解出来的part们,我们拿到part则取检索子节点是否存在这个part,不存在则新建一个子节点,不停的在这个树上深入,直到遍历完我们的全部part,然后递归返回。
//插入方法,用一个递归实现,找匹配的路径直到找不到匹配当前part的节点,新建
func (n *node) insert(path string, parts []string, height int) {
//如果遍历到底部了,则将我们的path存入节点,开始返回。递归的归来条件。
if len(parts) == height{
n.path = path
return
}
//获取这一节的part,并进行搜索
part := parts[height]
child := n.matchChild(part)
//若没有搜索到匹配的子节点,则根据目前的part构造一个子节点
if child == nil {
child = &node{
part: part,
isBlurry: part[0] == ':' || part[0] == '*',
}
n.children = append(n.children, child)
}
child.insert(path, parts, height+1)
}
5、我们带着part们一个个在存储路由表的树中查找,我们拿到某个节点的全部子节点,找到满足part相同或者isBlurry:true
的节点。通过递归再往深处挖,挖下去直到发现某一级节点的子节点们,没有对应匹配的part,又返回来,再去上一层的子节点看,这就是一个深度优先遍历的情况。
//搜索方法
func (n *node) search(parts []string, height int) *node {
//如果节点到头,或者存在*前缀的节点,开始返回
if len(parts) == height || strings.HasPrefix(n.part,"*") {
//如果此时遍历到的n没有存储对应的path,说明未到目标最底层,则返回空
if n.path == "" {
return nil
}
return n
}
//搜索找到满足part的子节点们放入children
part := parts[height]
children := n.matchChildren(part)
//接着遍历子节点们,递归调用获得下一级的子节点们,要走到头的同时,找到了对应的节点,才返回最终我们找到的result
//这里为什么要遍历子节点们进行深入搜索,因为它还存在满足isBlurry:true的节点,我们也需要在其中深入搜索。
for _, child := range children {
result := child.search(parts, height+1)
if result != nil {
//返回满足要求的节点
return result
}
}
return nil
}
二、更新路由表的存储结构和处理方法
1、其中roots
中的第一层是roots[method]*node
type router struct {
//用于存储相关方法
handlers map[string]HandlerFunc
//用于存储每种请求方式的树的根节点
roots map[string]*node
}
2、设计一个parsePath
方法,对外部传入的路由根据"/"
进行分割,存入parts
// parsePath 用于处理传入的url,先将其分开存储到parts中,当然出现*前缀的部分就可以结束
func parsePath(path string) []string {
vs := strings.Split(path, "/")
parts := make([]string, 0)
for _, v := range vs {
if v != "" {
parts = append(parts, v)
if v[0] == '*' {
break
}
}
}
return parts
}
3、router
中 addRoute
方法,在 handlers map[string]HandlerFunc
中存入路由对应处理方法,进行路由注册。存入形式为例如:{ "GET-/index" : 定义的处理方法 }
注意这里的path使我们用来构造路由表要存入的目标path
// router 中 addRoute 方法,在 handlers map[string]HandlerFunc 中存入路由对应处理方法
//存入形式为例如:{ "GET-/index" : 定义的处理方法 }
func (r *router) addRoute(method string, path string, handler HandlerFunc) {
parts := parsePath(path)
log.Printf("Route %4s - %s",method,path)
key := method + "-" + path
_, ok := r.roots[method]
//roots中不存在对应的方法入口则注册相应方法入口
if !ok {
r.roots[method] = &node{}
}
//调用路由表插入方法,在该数据结构中插入该路由
r.roots[method].insert(path, parts, 0)
//把method-path作为key,以及handler方法作为value注入数据结构
r.handlers[key] = handler
}
4、做一个getRoute
方法,进入到对应路由树,找到我们的路由,通过哈希表存入处理动态路由拿到param
和找到的*node
一起返回。
注意代码中的n.path是我们注册在路由表中的路由,path是外部传入的!
func (r *router) getRoute(method string, path string) (*node, map[string]string) {
searchParts := parsePath(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 := parsePath(n.path) //n.path包含了完整的路由
for i, part := range parts {//遍历这一条路径
//拿到:的参数,存入params,方法中的part作为key,外面传入的path中的数据作为value存入
if part[0] == ':' {
params[part[1:]] = searchParts[i]
}
//拿到*,此时路由表中的存入的part作为key,外面传入的path中的数据作为value传入params,之后也再没有了
if part[0] == '*' && len(part) > 1{
params[part[1:]] = strings.Join(searchParts[i:],"/")
break
}
}
return n, params
}
return nil, nil
}
5.同时我们的hanle
方法和上一篇文章不同的是,不是直接拿外部传入的path
直接在 handlers map[string]HandlerFunc
找对应的方法,因为我们外部传入的path是动态的。我们是先通过getRoute
方法拿到参数和对应的找到存储节点,用这个节点中存储的path(它是静态的,是我们之前注入的),再在 handlers map[string]HandlerFunc
找到对应的方法。
//根据context中存储的 c.Method 和 c.Path 拿到对应的处理方法,进行执行,如果拿到的路由没有注册,则返回404
func (r *router) handle(c *Context) {
//获取匹配到的节点,同时也拿到两类动态路由中参数
n, params := r.getRoute(c.Method, c.Path)
if n != nil {
c.Params = params
//拿目的节点中的path做key来找handlers
key := c.Method + "-" + n.path
r.handlers[key](c)
}else {
c.String(http.StatusNotFound,"404 NOT FOUND")
}
}
三、Context变更
1、修改Context结构体,构造Params来存放处理动态路由拿到的参数
// Context 结构体,内部封装了 http.ResponseWriter, *http.Request
type Context struct {
Writer http.ResponseWriter
Req *http.Request
//请求的信息,包括路由和方法
Path string
Method string
Params map[string]string /*用于存储外面拿到的参数 ":xxx" or "*xxx" */
//响应的状态码
StatusCode int
}
2、设计Param方法,拿到处理动态路由的获取参数
// Param 是c的Param的value的获取方法
func (c *Context) Param(key string) string {
value, _ := c.Params[key]
return value
}
随便做个测试:
/*
@Time : 2021/8/16 下午4:01
@Author : mrxuexi
@File : main
@Software: GoLand
*/
package main
import (
"Ez"
"net/http"
)
func main() {
r := Ez.New()
r.POST("/hello/:id/*filepath", func(c *Ez.Context) {
c.JSON(http.StatusOK,Ez.H{
"name" : c.PostForm("name"),
"age" : c.PostForm("age"),
"id" : c.Param("id"),
"filepath" : c.Param("filepath"),
})
})
r.Run(":9090")
}
成功!
参考:
[1]: https://github.com/geektutu/7days-golang/tree/master/gee-web ""gee""
[2]: https://github.com/gin-gonic/gin ""gin""
手把手和你一起实现一个Web框架实战——EzWeb框架(三)[Go语言笔记]Go项目实战的更多相关文章
- 手把手和你一起实现一个Web框架实战——EzWeb框架(二)[Go语言笔记]Go项目实战
手把手和你一起实现一个Web框架实战--EzWeb框架(二)[Go语言笔记]Go项目实战 代码仓库: github gitee 中文注释,非常详尽,可以配合食用 上一篇文章我们实现了框架的雏形,基本地 ...
- 手把手和你一起实现一个Web框架实战——EzWeb框架(四)[Go语言笔记]Go项目实战
手把手和你一起实现一个Web框架实战--EzWeb框架(四)[Go语言笔记]Go项目实战 代码仓库: github gitee 中文注释,非常详尽,可以配合食用 这一篇文章主要实现路由组功能.实现路由 ...
- 手把手和你一起实现一个Web框架实战——EzWeb框架(五)[Go语言笔记]Go项目实战
手把手和你一起实现一个Web框架实战--EzWeb框架(五)[Go语言笔记]Go项目实战 代码仓库: github gitee 中文注释,非常详尽,可以配合食用 本篇代码,请选择demo5 中间件实现 ...
- Go语言笔记[实现一个Web框架实战]——EzWeb框架(一)
Go语言笔记[实现一个Web框架实战]--EzWeb框架(一) 一.Golang中的net/http标准库如何处理一个请求 func main() { http.HandleFunc("/& ...
- 潭州课堂25班:Ph201805201 WEB 之 页面编写 第三课 (课堂笔记)
index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset=&quo ...
- JAVA Web day03--- Android小白的第三天学习笔记
3.5.6.Math对象(了解) 无需创建,直接Math.方法来进行使用.(内置对象) Math方法 random() 随机生成0~1数字 round(x) 对X进行四舍五入 3.5.7.RegExp ...
- Python之路,Day18 - 开发一个WEB聊天来撩妹吧
Python之路,Day18 - 开发一个WEB聊天来撩妹吧 本节内容: 项目实战:开发一个WEB聊天室 功能需求: 用户可以与好友一对一聊天 可以搜索.添加某人为好友 用户可以搜索和添加群 每个 ...
- 【转载】ASP.NET MVC Web API 学习笔记---第一个Web API程序
1. Web API简单说明 近来很多大型的平台都公开了Web API.比如百度地图 Web API,做过地图相关的人都熟悉.公开服务这种方式可以使它易于与各种各样的设备和客户端平台集成功能,以及通过 ...
- ASP.NET MVC Web API 学习笔记---第一个Web API程序
http://www.cnblogs.com/qingyuan/archive/2012/10/12/2720824.html GetListAll /api/Contact GetListBySex ...
随机推荐
- 解决mount.nfs: access denied by server while mounting
在linux下进行挂载时突然出现: mount.nfs: access denied by server while mounting 第一感觉是读取文件权限不够,准备去更改一下挂载点的权限,但又考 ...
- php安裝7.3版本
CentOS 安装 EPEL 源: yum install epel-release 安装 REMI 源: CentOS 7: yum install http://rpms.remirepo.net ...
- 从三道题目入门frida
偶然从看雪看到了一篇入门frida的题目,正好苦于没练手的东西,直接上手一波 1.第一题jadx打开,也没有壳和混淆,整体非常清晰,判断的逻辑也很简单 发现其实就是两个输入框,一个用户名一个密码,先拼 ...
- Django 基础05篇 上下文管理和前端代码复用
一.上下文管理器 在views中重复使用的代码,可以通过上下文管理器(在setting.py文件中的TEMPLATES中配置)中实现,减少代码冗余 上下文管理器的处理流程如下: 1.先走完views里 ...
- python 判断是否在指定时间段
import datetime,time def jsyx(aa1,aa2): jssj=datetime.datetime.strptime(str(datetime.date.today()+da ...
- [刘阳Java]_美团点评2018届校招面试总结_Java后台开发【转载】
美团喜欢一口气把三轮技术面和HR面一起面完,虽然身心比较累(每一面差不多一个小时),不过也算是一个好事,不像某些公司一天就一面然后让回去等消息,等面试通知也等得让人很焦虑,而且还容易出现面试时间冲突. ...
- 队列Queue:任务间的消息读写,安排起来~
摘要:本文通过分析鸿蒙轻内核队列模块的源码,掌握队列使用上的差异. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列十三 消息队列Queue>,作者:zhushy . 队列(Queue)是 ...
- pip3 pip 安装包 临时更换镜像地址
在使用pip3或者pip安装某些第三方包的时候,可能会遇到网络原因导致的安装失败. 可以在安装第三方包的时候临时指定镜像地址. 命令: pip3 install 库名 -i 镜像地址 例如:# pip ...
- vue3.0使用ant-design-vue进行按需加载原来这么简单
下载 ui库 yarn add ant-design-vue 默认是全局引入,打包后体积很大, 非常影响首屏加载速度, 按需加载 下载按需加载的插件;推荐使用cnpm cnpm install bab ...
- spring第三章
第三章 实现AOP AOP:面向方面编程,AOP能够使您将所有模块共有的特性与应用程序的主要业务逻辑隔离开 一.AOP介绍 横切关注点:在Web应用程序中,有一些服务(如登录.安全和事务管理)不是应用 ...