【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 *RouterGroup
	origin *Origin
}

prefix 是当前分组的前缀

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

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

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

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

type Origin struct {
	*RouterGroup // 用于将origin对象抽象成最顶层的RouterGroup,使得origin可以调用RouterGroup的方法
	router *router
	routerGroup []*RouterGroup // 路由分组切片,存放注册的路由分组实例
}
func New() *Origin {
	origin := &Origin{router: newRouter()} // 创建一个引擎对象实例
	origin.RouterGroup = &RouterGroup{origin: origin} // 使用引擎对象实例化RouterGroup
	origin.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.origin
	newGroup := &RouterGroup{
        parent: group, //将group作为父路由对象
		prefix: group.prefix + prefix, // 前缀为父路由对象的前缀加上当前设置的前缀	
		origin: origin, // 统一引擎对象
	}
	origin.groups = append(origin.groups, newGroup) // 将注册的路由分组存入分组切片中
	return newGroup
}

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

package main

import (
	"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) {
    part1
    c.Next()
    part2
}
func B(c *Context) {
    part3
    c.Next()
    part4
}

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

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

type Context struct {
	Writer     http.ResponseWriter
	Req        *http.Request
	Path       string
	Method     string
	Params     map[string]string
	StatusCode int
    // 添加index 与 handlers 属性
	index      int
	handlers   []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 = params
	if n != nil {
		key := ctx.Method + "-" + n.pattern
		ctx.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 kilon

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

最后在main.go中测试:

package main

import (
	"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可以看到控制台输出:

在这里插入图片描述

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

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

相关文章

Linux系统——Zookeeper集群

目录 一、Zookeeper概述 1.Zookeeper简介 2.Zookeeper工作机制 3.Zookeeper数据结构 4.Zookeeper应用场景 4.1统一命名服务 4.2统一配置管理 4.3统一集群管理 4.4服务器动态上下线 4.5软负载均衡 5.Zookeeper选举机制 5.1第一次启动选举机制 5.2非第一次启动选举机…

006Node.js cnpm的安装

百度搜索 cnpm,进入npmmirror 镜像站https://npmmirror.com/ cmd窗口输入 npm install -g cnpm --registryhttps://registry.npmmirror.com

ESP32系统监测(基于ESP-IDF)

主要参考资料&#xff1a; CSDN文章《ESP32 IDF开发调试奇技淫巧》: https://blog.csdn.net/qq_43332314/article/details/131859971 目录 查询系统剩余堆/最小堆大小查询线程剩余栈大小方法一方法二 查询CPU占用率 查询系统剩余堆/最小堆大小 查询系统剩余堆、最小堆大小的 A…

K8s下部署grafana

1. 系统要求 最小化的软硬件要求 最小化硬件要求 磁盘空间: 1 GB内存: 750 MiB (approx 750 MB)CPU: 250m (approx 2.5 cores) 2. k8s部署grafana步骤 1) 创建名字空间 kubectl create namespace my-grafana 2) 创建yaml vim grafana.yaml yaml包含如下三个资源对象 Ob…

AIGC 探究:人工智能生成内容的技术原理、广泛应用、创新应用、版权问题与未来挑战

AIGC&#xff08;Artificial Intelligence Generated Content&#xff09;即人工智能生成内容&#xff0c;其核心在于利用深度学习技术&#xff0c;尤其是基于神经网络的模型&#xff0c;来模拟人类创作过程&#xff0c;自主生成高质量的文本、图像、音频、视频等各类内容。神经…

test4141

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和…

软考高级架构师:随机函数模型

一、AI 讲解 随机函数模型是理解各种随机过程和算法的一个重要概念&#xff0c;在软件工程、算法设计以及系统分析中有着广泛的应用。简而言之&#xff0c;随机函数模型是一种用于描述具有随机性的系统或过程的数学模型&#xff0c;它能够帮助我们预测和分析在不确定性下的系统…

Day18_学点儿设计模式_MVC和三层架构

0 优质文章 MVC与三层架构 什么是MVC&#xff1f;什么是三层架构&#xff1f; 三层架构与MVC详细讲解 MVC三层架构&#xff08;详解&#xff09; 1 MVC MVC全名是Model View Controller&#xff0c;是模型(model)&#xff0d;视图(view)&#xff0d;控制器(controller)的缩写…

01、ArcGIS For JavaScript 4.29对3DTiles数据的支持

综述 Cesium从1.99版本开始支持I3S服务的加载&#xff0c;到目前位置&#xff0c;已经支持I3S的倾斜模型、3D Object模型以及属性查询的支持。Cesium1.115又对I3S标准的Building数据实现了加载支持。而ArcGIS之前一直没有跨越对3DTiles数据的支持&#xff0c;所以在一些开发过…

[附带黑子定制款鸽鸽版素材包]更改文件夹图标,更改系统音效,更改鼠标指针及样式。

更改文件夹图标 1.选择图片在线格式转换网站转换为ico格式 2.右键文件夹选择属性 3.点击自定义&#xff0c;点击更改图标超链接 4.点击预览选择生成的ico文件 5.点击打开&#xff0c;点击确定&#xff0c;点击应用 更改系统音效&#xff08;真爱粉强烈推荐&#xff09; 1…

【七 (4)FineBI FCP模拟试卷-电站数据分析】

目录 文章导航一、字段解释1、电站基础信息表2、电站事实表 二、需求三、操作步骤1、将新增一列日期12、以左关联的形式增加装机容量3、年度发电总量4、年度售电完成率4、发电量及发电效率5、年售电完成比、售电回款比、管理费用比、运维费用比5、总装机容量6、最近日期7、最近…

【PG-1】PostgreSQL体系结构概述

1. PostgreSQL体系结构概述 代码结构 其中&#xff0c;backend是后端核心代码&#xff0c;包括右边的几个dir: access&#xff1a;处理数据访问方法和索引的代码。 bootstrap&#xff1a;数据库初始化相关的代码。 catalog&#xff1a;系统目录&#xff08;如表和索引的元数据…

Golang | Leetcode Golang题解之第27题移除元素

题目&#xff1a; 题解&#xff1a; func removeElement(nums []int, val int) int {left, right : 0, len(nums)for left < right {if nums[left] val {nums[left] nums[right-1]right--} else {left}}return left }

人脸识别业务(基于腾讯人脸识别接口)

使用腾讯云人脸识别接口&#xff0c;基于优图祖母模型。 一、准备工作 人脸识别账号 申请腾讯云服务器账号&#xff0c;生成自己的秘钥。记录秘钥和秘钥ID。 创建人员库 记下人员库id 在配置文件application.yml中添加配置。 plateocr:SecretId: 秘钥IDSecretKey: 秘钥ser…

LeetCode34:在排序数组中查找元素的第一个和最后一个位置(Java)

目录 题目&#xff1a; 题解&#xff1a; 方法一&#xff1a; 方法二&#xff1a; 题目&#xff1a; 给你一个按照非递减顺序排列的整数数组 nums&#xff0c;和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。 如果数组中不存在目标值 target&…

软考高级架构师:运筹方法(线性规划和动态规划)

一、AI 讲解 运筹学是研究在给定的资源限制下如何进行有效决策的学问。其中&#xff0c;线性规划和动态规划是两种重要的运筹方法&#xff0c;它们在解决资源优化分配、成本最小化、收益最大化等问题上有着广泛的应用。 线性规划 线性规划是一种数学方法&#xff0c;用于在满…

C语言 | Leetcode C语言题解之第27题移除元素

题目&#xff1a; 题解&#xff1a; int removeElement(int* nums, int numsSize, int val) {int left 0, right numsSize;while (left < right) {if (nums[left] val) {nums[left] nums[right - 1];right--;} else {left;}}return left; }

一个巧用委托解决的问题(C#)

个人觉得是委托应用的一个很好的例子&#xff0c;故做一下分享&#xff0c;希望能帮助到您&#xff0c;内容比较简单&#xff0c;大佬可以跳过。我是做桌面医疗软件开发的&#xff0c;前段时间在做一个需求。在签发检验项目医嘱时&#xff0c;调用第三方接口&#xff0c;然后带…

「51媒体网」汽车类媒体有哪些?车展媒体宣传

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 汽车类媒体有很多&#xff0c;具体如下&#xff1a; 汽车之家&#xff1a;提供全面的汽车新闻、评测、导购等内容。 爱卡汽车&#xff1a;同样是一个综合性的汽车信息平台&#xff0c;涵…

安达发|电子行业智能生产排程计划的实施

随着科技的不断发展&#xff0c;电子行业正面临着巨大的变革。在这个过程中&#xff0c;智能生产排程计划的实施成为了提高生产效率、降低成本的关键因素。本文将详细介绍电子行业智能生产排程计划的实施方法、优势以及可能遇到的挑战。 一、实施方法 1. 数据采集与分析&#x…