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

news2024/9/22 19:40:42

文章目录

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

前情提示:
【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可以看到控制台输出:

在这里插入图片描述

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

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

相关文章

传销?链动2+1模式 合法合规的商业模式!

大家好&#xff0c;我是吴军&#xff0c;来自一家深耕于软件开发领域的公司&#xff0c;担任产品经理的职务。 今天&#xff0c;我希望与大家共同探讨一个具有深刻意义的话题——链动21模式&#xff0c;并探究其如何有效应对用户留存与复购的挑战。 或许有人会说&#xff0c;链…

UE5学习日记——实现自定义输入及监听输入,组合出不同的按键输入~

UE5的自定义按键和UE4有所不同&#xff0c;在这里记录一下。 本文主要是记录如何设置UE5的自定义按键&#xff0c;重点是学会原理&#xff0c;实际开发时结合实际情况操作。 输入映射 1. 创建输入操作 输入操作并不是具体的按键映射&#xff0c;而是按键的激活方式&#xff0…

如何落地一个FaaS平台?

简介&#xff1a; 函数即服务&#xff08;FaaS&#xff09;作为云计算 2.0 时代重要的发展方向&#xff0c;能够从工程效率、可靠性、性能、成本等方面给开发者带来巨大的价值&#xff0c;尤其是能够极大地提升研发效率。因此&#xff0c;拥抱FaaS成为开发者关心的重要技术领域…

01-Git 之快速入门操作本地仓库

https://learngitbranching.js.org/?localezh_CN在线练习git 1. Git 安装好Git以后, 先检查是否已经绑定了用户名和邮箱 git config --list1.1 为什么要使用版本控制&#xff1f; 从个人角度&#xff1a; 在做项目时&#xff0c;如果一点点去改代码会很乱&#xff0c;不利…

华为云开年采购季:企业级市场的火爆营销炼成记

作者 | 曾响铃 文 | 响铃说 销售火爆&#xff0c;一单又一单&#xff0c;“生意好个不停”。 这一幕&#xff0c;常常发生在C端消费品市场上&#xff0c;要么是线上爆单&#xff0c;要么是线下人头攒动。 而现在&#xff0c;在B端企业级市场上也发生了类似的情景。 自3月1…

ZJJ-2A直流绝缘监视继电器额定电流3.1mA额定电压110VDCJOSEF约瑟

系列型号 JJJ-1绝缘监视继电器&#xff1b; ZJJ-1/A绝缘监视继电器&#xff1b; ZJJ-1A绝缘监视继电器&#xff1b; ZJJ-2型直流绝缘监视继电器 ZJJ-2直流绝缘监视继电器&#xff1b; ZJJ-2B直流绝缘监视继电器&#xff1b; ZJJ-2AC直流绝缘监视继电器&#xff1b; 用途…

Day 41:动态规划 LeedCode 343. 整数拆分 96.不同的二叉搜索树

343. 整数拆分 给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff0c;并使这些整数的乘积最大化。 返回 你可以获得的最大乘积 。 示例 1: 输入: n 2 输出: 1 解释: 2 1 1, 1 1 1。 思路: 1.确定dp数组&#xff0…

《TinyLlama: An Open-Source Small Language Model》全文翻译

【Title】 TinyLlama&#xff1a;开源小语言模型 【Abstract】 我们推出了 TinyLlama&#xff0c;这是一个紧凑的 1.1B 语言模型&#xff0c;在大约 1 万亿个令牌上进行了大约 3 个时期的预训练。 TinyLlama 基于 Llama 2&#xff08;Touvron 等人&#xff0c;2023b&#xff…

自定义Centos的终端的命令提示符

背景 当我们使用终端登陆Centos时&#xff0c;就自动打开了ssh终端。这个终端的命令提示符一般是这样的&#xff1a; 这个以#号结束的一行字&#xff0c;就是我们说的命令提示符了。 这个是腾讯云的服务器的提示符&#xff0c;可以看到主机名是VM-4-7-centos。 但是这个看起…

Python-GEE遥感云大数据分析、管理与可视化及多领域案例实践应用

随着航空、航天、近地空间遥感平台的持续发展&#xff0c;遥感技术近年来取得显著进步。遥感数据的空间、时间、光谱分辨率及数据量均大幅提升&#xff0c;呈现出大数据特征。这为相关研究带来了新机遇&#xff0c;但同时也带来巨大挑战。传统的工作站和服务器已无法满足大区域…

SD-WAN提升企业网络体验

在现代企业中&#xff0c;网络体验已成为提升工作效率与业务质量的关键因素。SD-WAN技术的出现&#xff0c;以其独特的优势&#xff0c;为企业提供了优化网络连接、加速数据传输、提升服务质量和应用访问体验&#xff0c;以及增强网络稳定性的解决方案。接下来&#xff0c;我们…

设计模式在芯片验证中的应用——策略

1. 策略模式 策略模式是一种行为设计模式&#xff0c; 它能让你定义一系列算法&#xff0c; 并将每种算法分别放入独立的类中&#xff0c; 以使算法的对象能够相互替换。 在RTL设计中可能包含了复杂的多个访问仲裁逻辑&#xff0c;使用了多种算法来确定访问内存优先级顺序&am…

plc远程监控系统

随着工业4.0时代的到来&#xff0c;智能制造成为了工业发展的核心驱动力。在这个信息化、网络化的新时代&#xff0c;PLC远程监控系统以其强大的监控与管理能力&#xff0c;正逐渐成为工业自动化领域的明星产品。今天&#xff0c;我们就来聊聊PLC远程监控系统以及HiWoo Cloud平…

网络协议——OSPF(开放式最短路径优先)详解

1.什么是OSPF 开放式最短路径优先OSPF 是一种动态的高度可靠和高度可扩展的路由协议&#xff0c;用于构建大型网络中的动态路由系统 2. OSPF的协议号为&#xff1a;89 3. OSPF的特点: OSPF是链路状态协议使用了区域概念&#xff1a;减少路由选择协议对路由器CPU&#xff0c;…

从零开始学习Linux(1)---基本命令(1)

1.学习准备 我学习Linux是使用xshell远程登录自己的云服务器来进行。 xshell是一个远程终端管理软件&#xff0c;下载官网&#xff1a; https://www.netsarang.com/products/xsh_overview.htm 下载安装的时候选择 "home/school"…

H.265网页无插件播放EasyPlayer.js流媒体播放器常见问题及解答

EasyPlayer属于一款高效、精炼、稳定且免费的流媒体播放器&#xff0c;可支持多种流媒体协议播放&#xff0c;无须安装任何插件&#xff0c;起播快、延迟低、兼容性强&#xff0c;使用非常便捷。 今天我们来汇总下用户常见的几个问题及解答。 1、EasyPlayer.js播放多路H.265视…

「每日跟读」英语常用句型公式 第12篇

「每日跟读」英语常用句型公式 第12篇 1. I’m having a hard time __ing 我现在不太能__&#xff08;遇到困难&#xff09; I’m having a hard time concentrating on my work.&#xff08;我现在不太能集中注意力在我的工作上&#xff09; I’m having a hard time slee…

UE5 把蓝图内的变量和事件暴露给序列使用

在蓝图变量内勾选Expose to Cinematics 事件: 在角色内添加自定义事件 在序列内对着角色的号添加Event,选择Trigger 添加关键帧,然后在关键帧右键添加class,在class下绑定事件

抖音小店新手玩家能做吗?跟着我的步骤做,爆单很简单

大家好&#xff0c;我是电商笨笨熊 抖店新手玩家能做吗&#xff1f; 当然可以。 抖音小店自推出以来已经吸引了无数的新手玩家&#xff0c;且不少玩家从个人走到了团队化。 新手做抖店&#xff0c;最重要的是掌握方法&#xff0c;只要方法对了&#xff0c;方向对了&#xf…

工业物联网网关

在数字化浪潮席卷全球的今天&#xff0c;工业物联网&#xff08;IIoT&#xff09;作为连接物理世界与数字世界的桥梁&#xff0c;正在逐渐改变传统工业的面貌。而作为IIoT的核心枢纽&#xff0c;工业物联网网关发挥着至关重要的作用。今天&#xff0c;我们就来深入了解一下工业…