【Golang学习笔记】从零开始搭建一个Web框架(三)

文章目录

  • 分组控制
    • 分组嵌套
    • 中间件

前情提示:
【Golang学习笔记】从零开始搭建一个Web框架(一)-CSDN博客
【Golang学习笔记】从零开始搭建一个Web框架(二)-CSDN博客

分组控制

分组控制(Group Control)是 Web 框架应提供的基础功能之一。分组指路由分组,将路由分成不同的组别,然后对每个组别应用特定的策略和规则来实现管理和控制。这些策略和规则由用户通过中间件定义。

分组嵌套

通常情况下,分组路由以前缀作为区分,现在需要实现的分组控制也以前缀区分,并且支持分组的嵌套。例如/post是一个分组,/post/a/post/b可以是该分组下的子分组。作用在/post分组上的中间件(middleware),也都会作用在子分组,子分组还可以应用自己特有的中间件。

打开kilon.go添加一个路由分组的结构体:

type RouterGroup struct {prefix string // 前缀middlewares []HandlerFunc // 中间件函数,后续中间件的实现需要用到parent *RouterGrouporigin *Origin
}

prefix 是当前分组的前缀

middleware 中间件函数,用于中间件的实现

parent 指向父路由分组,用于支持嵌套分组

origin是引擎对象,所有的RouterGroup指向同一个引擎实例,可以让RouterGroup也调用引擎的方法

接下来在引擎中添加路由分组对象:

type Origin struct {*RouterGroup // 用于将origin对象抽象成最顶层的RouterGroup,使得origin可以调用RouterGroup的方法router *routerrouterGroup []*RouterGroup // 路由分组切片,存放注册的路由分组实例
}
func New() *Origin {origin := &Origin{router: newRouter()} // 创建一个引擎对象实例origin.RouterGroup = &RouterGroup{origin: origin} // 使用引擎对象实例化RouterGrouporigin.groups = []*RouterGroup{origin.RouterGroup} // 将origin.RouterGroup作为所有分组的父分组return origin
}

将路由都交给路由分组对象进行管理:

func (group *RouterGroup) addRoute(method string, comp string, handler HandlerFunc) {pattern := group.prefix + comp // pattern为分组前缀prefix加上当前注册的路径log.Printf("Route %4s - %s",method, pattern) group.origin.router.addRoute(method, pattern, handler)
}func (group *RouterGroup) GET(pattern string, hander HandlerFunc) {group.addRoute("GET", pattern, hander) 
} // 修改func (group *RouterGroup) POST(pattern string, hander HandlerFunc) {group.addRoute("POST", pattern, hander) 
} // 修改

接下来需要编写分组注册的方法:

func (group *RouterGroup) Group(prefix string) *RouterGroup {origin := group.originnewGroup := &RouterGroup{parent: group, //将group作为父路由对象prefix: group.prefix + prefix, // 前缀为父路由对象的前缀加上当前设置的前缀	origin: origin, // 统一引擎对象}origin.groups = append(origin.groups, newGroup) // 将注册的路由分组存入分组切片中return newGroup
}

至此分组嵌套已经实现,接下来在main.go中测试:

package mainimport ("fmt""kilon""net/http"
)func main() {r := kilon.New()group1 := r.Group("/hello")group1.GET("/:username", func(ctx *kilon.Context) {ctx.JSON(http.StatusOK, kilon.H{"message": fmt.Sprintf("Hello %s", ctx.Param("username")),})})group2 := r.Group("/file")group2.GET("/:filename", func(ctx *kilon.Context) {ctx.JSON(http.StatusOK, kilon.H{"file": fmt.Sprintf("zhangsan's %s", ctx.Param("filename")),})})r.Run(":8080")
}

浏览器分别访问:127.0.0.1:8080/hello/zhangsan 与 127.0.0.1:8080/file/photo.png

可以看到返回的JSON数据

中间件

在Web框架中,中间件用于处理HTTP请求和响应的过程中,对请求进行预处理、后处理或者进行一些额外的操作。中间件提供了一种灵活的方式来扩展和定制Web应用程序的功能。

这里的中间件设计参考了Gin框架的实现。在gin框架的context.go中(gin/context.go),中间件的实现主要与上下文对象中index与handlers两个属性以及Next方法有关:

// type HandlerFunc func(*Context)
// type HandlersChain []HandlerFunc
type Context struct {...handlers HandlersChain // HandlerFunc 的切片,用于按顺序执行多个中间件函数。index    int8          // 表示当前需要执行哪个中间处理函数,与下面的next方法关联...
}

当调用Next方法时,c.index++,将控制权交给下一个中间件函数。(循环的好处在于,前置中间件可以不调用Next方法,减少代码重复)

func (c *Context) Next() {c.index++for c.index < int8(len(c.handlers)) {c.handlers[c.index](c) c.index++}
}

handlers 最后会放入用户路由注册的函数handler,基本的处理流程是这样的:当有一个请求到来时,服务器会创建一个 Context 对象来存储请求的相关信息,然后依次调用存储在 handlers 字段中的中间件函数(按照添加的顺序),并将当前的 Context 对象传递给这些函数。中间件函数函数调用Next方法后,会将控制权交给下一个中间件函数,直到所有中间件函数都执行完毕,最终处理请求的函数会被调用。如注册了下面两个中间件:

func A(c *Context) {part1c.Next()part2
}
func B(c *Context) {part3c.Next()part4
}

此时c.handlers是这样的[A, B, Handler],接下来的流程是这样的:part1 -> part3 -> Handler -> part 4 -> part2

在context中模仿gin框架,改造Contex结构体:

type Context struct {Writer     http.ResponseWriterReq        *http.RequestPath       stringMethod     stringParams     map[string]stringStatusCode int// 添加index 与 handlers 属性index      inthandlers   []HandlerFunc
}func newContext(w http.ResponseWriter, req *http.Request) *Context {return &Context{Writer: w,Req:    req,Path:   req.URL.Path,Method: req.Method,index:  -1, // 初始化为-1}
}
// 定义Next方法
func (c *Context) Next() {c.index++for c.index < len(c.handlers){c.handlers[c.index](c) // 调用中间件函数c.index++}
}

在kilon中添加分组路由对象绑定中间件的方法:

func (group *RouterGroup) Use(middleware ...HandlerFunc){group.middleware = append(group.middleware, middleware...)
}

修改ServeHTTP接口的实现,将中间件赋予上下文对象:

func (origin *Origin) ServeHTTP(w http.ResponseWriter, req *http.Request) {    var middlewares []HandlerFunc// 寻找所属路由分组for _, group := range origin.groups {// 将该路由分组的中间件取出if strings.HasPrefix(req.URL.Path, group.prefix) {middlewares = append(middlewares, group.middlewares...)}}	ctx := newContext(w, req)  // 创建上下文对象ctx.handlers = middlewares // 将中间件赋予上下文对象origin.router.handle(ctx)  // 在handle中将用户的路由注册的函数放入上下文对象的handlers中
}

在router.go的handle方法中,将路由映射的函数放入上下文对象的handlers中:

func (r *router) handle(ctx *Context) {n, params := r.getRoute(ctx.Method, ctx.Path)ctx.Params = paramsif n != nil {key := ctx.Method + "-" + n.patternctx.handlers = append(ctx.handlers, r.Handlers[key]) // 将路由映射的函数放入上下文对象的handlers最后} else {ctx.handlers = append(ctx.handlers, func(c *Context) {c.String(http.StatusNotFound, "404 NOT FOUND: %s\n", c.Path)})}ctx.Next() // 中间件,启动!
}

此外,还需要定义一个方法,当请求不符合要求时,中件间可以直接跳过之后的所有处理函数,并返回错误信息:

func (c *Context) Fail(code int, err string) {c.index = len(c.handlers)c.JSON(code, H{"message": err})
}

下面实现通用的Logger中间件,能够记录请求到响应所花费的时间。

新建文件klogger.go,当前目录结构如下:

myframe/├── kilon/│   ├── context.go│   ├── go.mod      [1]│   ├── kilon.go│   ├── klogger.go│   ├── router.go│   ├── tire.go├── go.mod          [2]├── main.go

向klogger.go中写入:

package kilonfunc Logger() HandlerFunc {return func(c *Context) {// Start timert := time.Now()// Process requestc.Next()// Calculate resolution timelog.Printf("[%d] %s in %v", c.StatusCode, c.Req.RequestURI, time.Since(t))}
}

最后在main.go中测试:

package mainimport ("fmt""kilon""net/http"
)func A() kilon.HandlerFunc{return func (c *kilon.Context) {fmt.Println("part1")c.Next()fmt.Println("part2")}
}func B() kilon.HandlerFunc{return func (c *kilon.Context) {fmt.Println("part3")c.Next()fmt.Println("part4")}
}func main() {r := kilon.New()group := r.Group("/hello")group.Use(kilon.Logger())group.Use(A(),B())group.GET("/:username", func(ctx *kilon.Context) {ctx.JSON(http.StatusOK, kilon.H{"message": fmt.Sprintf("Hello %s", ctx.Param("username")),})})r.Run(":8080")
}

访问127.0.0.1:8080/hello/zhangsan可以看到控制台输出:

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

粒子群优化算法PSO与鹈鹕优化算法(POA)求解无人机三维路径规划(MATLAB代码)

一、无人机路径规划模型介绍 二、算法介绍 close all clear clc dbstop if all error warning (off) global model model CreateModel(); % 创建模型 FF1; [Xmin,Xmax,dim,fobj] fun_info(F);%获取函数信息 pop100;%种群大小(可以自己修改) maxgen100;%最大迭代次数(可以自己…

JVM虚拟机(三)垃圾回收简介、垃圾回收算法、分代回收、垃圾回收器种类、G1垃圾回收器

目录 一、什么是垃圾回收&#xff1f;1.1 什么是垃圾回收&#xff1f;1.2 什么对象能被垃圾回收&#xff1f;1&#xff09;引用计数法2&#xff09;可达性分析算法 二、JVM 垃圾回收算法2.1 标记清除算法2.2 标记整理算法&#xff08;标记压缩算法&#xff09;2.3 复制算法2.4 …

如何在Linux部署MeterSphere并实现公网访问进行远程测试工作

文章目录 前言1. 安装MeterSphere2. 本地访问MeterSphere3. 安装 cpolar内网穿透软件4. 配置MeterSphere公网访问地址5. 公网远程访问MeterSphere6. 固定MeterSphere公网地址 前言 MeterSphere 是一站式开源持续测试平台, 涵盖测试跟踪、接口测试、UI 测试和性能测试等功能&am…

「Python」数据分析师需要掌握到什么程度?4条告诉你

前言 最近经常收到小伙伴们的留言&#xff1a;做数据分析要精通Python吗&#xff1f; 今天们就来好好盘一盘这个话题。 0基础想入门的小伙伴&#xff0c;如果你决定学习数据分析&#xff0c;却没有编程经验&#xff0c;那么这篇内容会非常适合你&#xff0c;让你的困惑得以解…

Singleton 单例

意图 保证一类仅有一个实例&#xff0c;并提供一个访问他的全局访问点 结构 其中&#xff1a; Singleton指定一个Instance操作&#xff0c;允许客户访问它的唯一实例&#xff0c;Instance是一个类操作&#xff1b;可能负责创建他自己的唯一实例。 适应性 当类只能有一个实…

【网络安全】WebPack源码(前端源码)泄露 + jsmap文件还原

前言 webpack是一个JavaScript应用程序的静态资源打包器。它构建一个依赖关系图&#xff0c;其中包含应用程序需要的每个模块&#xff0c;然后将所有这些模块打包成一个或多个bundle。大部分Vue等项目应用会使用webpack进行打包&#xff0c;使用webpack打包应用程序会在网站js…

进程与线程的区别?

并发和并行 在聊进程和线程的概念之前&#xff0c;首先了解一下操作系统相关概念&#xff0c;大部分操作系统&#xff08;如Windos、Linux&#xff09;的任务调度是采用时间片轮转的抢占式调度方式&#xff0c;也就是一个任务执行一小段时间后强制暂停去执行下一个任务&#x…

机器学习周记(第三十四周:文献阅读[GNet-LS])2024.4.8~2024.4.14

目录 摘要 ABSTRACT 1 论文信息 1.1 论文标题 1.2 论文摘要 1.3 论文模型 1.3.1 数据处理 1.3.2 GNet-LS 2 相关代码 摘要 本周阅读了一篇时间序列预测论文。论文模型为GNet-LS&#xff0c;主要包含四个模块&#xff1a;粒度划分模块&#xff08;GD&#xff09;&…

软件需求设计方法学全程实例剖析幻灯片04-系统用例图和用例规约

pdf文件已上传至本账号CSDN资源&#xff0c;也可以到以下地址下载&#xff1a;http://umlchina.com/training/umlchina_04_req.pdf

MYSQL08_页的概述、内部结构、文件头、文件尾、最大最小记录、页目录、区段表

文章目录 ①. 页的概述、大小②. 页的内部结构③. 第一部分 - 文件头④. 第一部分 - 文件尾⑤. 第二部分 - 空闲、用户记录、最大最小⑥. 第三部分 - 页目录⑦. 第三部分 - 页面头部⑧. 从数据页角度看B树⑨. 区、段和表、碎片区 ①. 页的概述、大小 ①. 数据库的存储结构&…

智慧能耗预付费系统解决方案——用户侧能源计量及收费

安科瑞电气股份有限公司 祁洁 15000363176 一、方案组织架构 二、方案特点 &#xff08;1&#xff09;多样组网&#xff0c;多样设备接入&#xff0c;多样部署&#xff1b; &#xff08;2&#xff09;集团管理、项目分级、分层拓扑&#xff1b; &#xff08;3&#xff09…

深度学习体系结构——CNN, RNN, GAN, Transformers, Encoder-Decoder Architectures算法原理与应用

1. 卷积神经网络 卷积神经网络&#xff08;CNN&#xff09;是一种特别适用于处理具有网格结构的数据&#xff0c;如图像和视频的人工神经网络。可以将其视作一个由多层过滤器构成的系统&#xff0c;这些过滤器能够处理图像并从中提取出有助于进行预测的有意义特征。 设想你手…