【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.mzph.cn/news/818027.shtml

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

相关文章

【算法刷题 | 回溯思想 01】4.11(回溯算法理论、组合、组合总和 ||| )

文章目录 回溯1.回溯算法理论基础1.1什么是回溯法&#xff1f;1.2回溯法的效率1.3回溯法解决的问题1.4如何理解回溯法&#xff1f;1.5回溯法模板 2.组合2.1问题2.2解法一&#xff1a;暴力解法&#xff08;循环次数不确定&#xff09;2.3解法二&#xff1a;回溯2.3.1回溯思路&am…

Unity Shader之数学篇

一、坐标系 1、二维笛卡尔坐标系 屏幕坐标系是二维笛卡尔坐标系&#xff0c;OpenGL的屏幕坐标系原点在左下角&#xff0c;DirectX的屏幕坐标系原点在左上角。 2、三维笛卡尔坐标系 三维笛卡尔坐标系要区分是左手坐标系还是右手坐标系。 左手坐标系&#xff1a;举起你的左手…

linnux文件服务

1.FTP:文件传输协议。 基础:控制端口(身份验证) command 21/tcp 数据端口: data 20/tcp FTP Server默认配置:yum -y install vsftpd (安装vsftpd&#xff09; touch /var/ftp/abc.txt(创建文件) systemctl start vsftpd(启动文件&#xff09; systemctl …

Word文档如何更改页面背景颜色?

在Microsoft Word中&#xff0c;设置页面颜色的方法有多种&#xff0c;以下为其中几种常用的方法&#xff1a;&#xff08;为office2016版本操作&#xff09; 方法一&#xff1a;使用主题颜色 1. 打开Word文档&#xff0c;在菜单栏中选择“设计”。 2. 在“设计”选项卡中&a…

泰山众筹:低门槛高回报的电商营销新模式

大家好&#xff0c;我是吴军&#xff0c;来自一家专注于软件开发的公司&#xff0c;担任产品经理一职。今天&#xff0c;我想与大家分享一种备受瞩目的商业模式——泰山众筹。 泰山众筹之所以能够在市场上迅速走红&#xff0c;其背后的原因值得我们深入探讨&#xff1a; 首先&…

idm线程越多越好吗 idm线程数多少合适

IDM&#xff08;Internet Download Manager&#xff09;是一款流行的下载管理软件&#xff0c;它支持多线程下载&#xff0c;这意味着它可以同时建立多个连接来下载文件的不同部分&#xff0c;从而提高下载速度。我们在使用IDM的时候总是有很多疑问&#xff0c;今天我们学习IDM…

游戏开发者必看:Perforce Helix Core 的功能特点及游戏开发中的常用工具、典型用例介绍

「不出海&#xff0c;即出局」随着全球化的加速发展&#xff0c;企业出海已成燎原之势。日前&#xff0c;2024 亚马逊云科技出海全球化论坛在深圳成功举办。龙智携手 Perforce 亮相游戏行业展区&#xff0c;展示了Perforce Helix Core如何与主流游戏开发引擎高效集成&#xff0…

Pytest精通指南(12)Parametrize源码拆解

文章目录 前言Parametrize 参数化Parametrize 源码分析Parametrize 使用说明一个参数的参数化多个参数的参数化验证类中有多个测试函数验证变量或函数传递参数化验证笛卡尔积拓展用法 前言 在 pytest 中&#xff0c;有两种常见的参数化方法&#xff1a; pytest.mark.parametriz…

哈希密码破解方法汇总

案例: 如何破译 254aa248acb47dd654ca3ea53f48c2c26 e93a1ec56258df7674c4 258df7674c4 该hash加密串的原文信息 步骤: 1)通过Hash Analyzer - TunnelsUP站点了解该hash加密串所使用的哈希加密算法类型。 可知,使用了 sha2-256 加密算法。 2) 访问example_hashes [hash…

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之十一 简单给视频添加水印图片效果

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之十一 简单给视频添加水印图片效果 目录 Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之十一 简单给视频添加水印图片效果 一、简单介绍 二、简单给视频添加水印图片效果实现…

【Linux学习】初识Linux指令(二)

文章标题 1.rm 指令2.man指令3.nano指令4.cp指令5.mv指令6.alias指令7. cat与8.echo指令 ⚶文章简介 ⚶本篇文章继上篇文章Linux指令讲解&#xff0c;本篇文章主要会涉及到的指令会有&#xff1a;rm指令与 *&#xff08;通配符&#xff09;的搭配使用&#xff0c;man指令&…

专业SEO优化指南:设置网站关键词的详细步骤

在网站SEO优化的过程中&#xff0c;关键词的设置是提升网站排名的关键步骤之一。那么&#xff0c;作为一名专业的SEO人员&#xff0c;如何有效地进行关键词设置呢&#xff1f;以下是一些详细的步骤&#xff1a; 1. 确定网站的核心关键词。 这需要深入理解网站的主题或产品。通…

整体性学习

整体性学习的顺序&#xff1a; 1.获取 2.理解&#xff08;明白&#xff09;3.拓展&#xff08;探究&#xff09;4.纠错&#xff08;调试&#xff09;5.应用 测试伴随每一个过程 例如&#xff1a; 吃饭&#xff08;去学习&#xff09;–>点菜&#xff08;学什么&#xff0c…

实时数据同步之Maxwell和Canal

文章目录 一、概述1、实时同步工具概述1.1 Maxwell 概述1.2 Canal概述 2、数据同步工作原理2.1 MySQL 主从复制过程2.2 两种工具工作原理 3、MySQL 的 binlog详解3.1 什么是 binlog3.2 binlog 的开启3.3 binlog 的分类设置 4、Maxwell和Canal对比5、环境安装 二、Maxwell 使用1…

日本极致产品力|一个战略符号打造年销售超4亿份的冰淇淋大单品

日本赤城乳业有一款冰棍——ガリガリ君(GariGarikun)&#xff0c;凭借着自己的“纯粹”打入市场&#xff0c;几十年来它成为许多日本人的夏日必备。他让人记忆最深刻的是战略符号——ガリガリ君&#xff0c;让赤城乳业打造出年销售4亿份的冰淇淋大单品。它是如何做到的呢? 石油…

Day55 动态规划 part15

Day55 动态规划 part15 392.判断子序列 我的思路&#xff1a; 自己还是只能想到双指针法 解答: class Solution {public boolean isSubsequence(String s, String t) {if(s.length() 0) {return true;}if(s.length() > t.length() || t.length() 0) {return false;}ch…

性能再升级!UNet+注意力机制,新SOTA分割准确率高达99%

UNet结合注意力机制能够有效提升图像分割任务的性能。 具体来说&#xff0c;通过将注意力模块集成到UNet的架构中&#xff0c;动态地重新分配网络的焦点&#xff0c;让其更集中在图像中对于分割任务关键的部分。这样UNet可以更有效地利用其跳跃连接特性&#xff0c;以精细的局…

VMware安装Linux虚拟机(rocky9)

软件准备&#xff1a; VMware虚拟机ISO系统镜像文件 选择创建虚拟机→典型→下一步→点击稍后安装操作系统 选择Linux系统和对应版本 输入虚拟机名称和选择保存位置 设置磁盘大小 根据需要自定义硬件配置→完成 然后点击编辑虚拟机设置→CD/DVD→选择ISO镜像 然后开启虚拟机→…

动态规划|343.整数拆分

力扣题目链接 class Solution { public:int integerBreak(int n) {vector<int> dp(n 1);dp[2] 1;for (int i 3; i < n ; i) {for (int j 1; j < i / 2; j) {dp[i] max(dp[i], max((i - j) * j, dp[i - j] * j));}}return dp[n];} }; 思路 看到这道题目&…

【GD32】 2.39 FR1002人脸识别模块

2.39 FR1002人脸识别模块 FR1002人脸识别模组解决方案以高性能应用处理器为硬件平台&#xff0c;配合双目传感器进行活体检测&#xff0c;具有启动速度快、金融级的识别能力、超低使用功耗等特点。凭借超低功耗、强大的运算速度&#xff0c;在多种应用领域中&#xff0c;为各行…