go web框架 gin-gonic源码解读03————middleware

这篇具有很好参考价值的文章主要介绍了go web框架 gin-gonic源码解读03————middleware。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

go web框架 gin-gonic源码解读03————middleware(context)


今天打完游戏有空整理整理之前看的gin的中间件设计,go的中间件设计相较于前两站还是蛮简单,蛮容易看懂的,所以顺便把context也一起写一下。

中间件是现在web服务里统一化拓展最常用的功能,,他是为了在我们的web服务中实现一些可重复使用,可组合的功能方法、可以让我们的 web逻辑在执行之前或者之后进行预处理,后处理,验证等操作。 在说中间件之前,我们先回忆一下之前几张看过的代码的。
**gin.go** 文件中gin为了实现http.Handler接口,而实现的ServeHTTP()方法

// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// 从对象池获取Context对象(对象池这里就不讲了,大家可以搜博客去看,这里只要大家会用就可以了)
	c := engine.pool.Get().(*Context)
	// 这三行其实就context的构造,传入http.ResponseWriter,*http.Request
	c.writermem.reset(w)
	c.Request = req
	c.reset()

	// 去我们的路由中查找响应的url并执行响应的逻辑
	engine.handleHTTPRequest(c)

	engine.pool.Put(c)
}

这里再顺便给大家介绍一下context,context实际上就是gin框架为了统一的参数管理包装的数据结构,他除了包含了我们每次访问ServeHTTP的传参(w http.ResponseWriter, req *http.Request)还包含了一些封装的web Response方法(调用就返回,非常好用),和一些请求获取参数的方法,其基本上覆盖了开发者平时所以操作需求,有兴趣的同学可以自己看看 .\Go\gin\context.go 中的内容。

// 这里缩略一下,只讲今天会用到的几个参数
type Context struct {
	writermem responseWriter
	Request   *http.Request
	Writer    ResponseWriter

	handlers HandlersChain 	// 中间件执行链handlers 
	index    int8					// 执行下标
	fullPath string

	// 引擎的指针
	engine       *Engine
	```
	略
	```
}

我们回到正题,看看我们的**handleHTTPRequest()**方法

// 去我们的路由中查找响应的url并执行响应的逻辑
engine.handleHTTPRequest( c )

这就是我们上次手撕的那个前缀树的查找方法。而我们的目的是通过收到http请求的url来找到,客户端需要请求的逻辑接口。

// 由于篇幅所限,代码会有所缩略

func (engine *Engine) handleHTTPRequest(c *Context) {
	// 很显然就是把我们请求方法和路径从Context里拿出来,方便使用
	httpMethod := c.Request.Method
	rPath := c.Request.URL.Path
	unescape := false
	```
	略
	```
	// Find root of the tree for the given HTTP method
	// 这里就是我们的前缀树的
	t := engine.trees
	for i, tl := 0, len(t); i < tl; i++ {
		// 这里前面说过的引擎(engine)中存的是一个前缀树的切片([]tree)
		//  每个请求方法一棵树,这里是遍历切片找到对应的请求方法
		if t[i].method != httpMethod {
			continue
		}
		root := t[i].root
		// Find route in tree
		// 找到树了,去树里查找
		value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
		// 参数节点,拿参数
		if value.params != nil {
			c.Params = *value.params
		}
		// 关键点来了,这里就找到了我们要执行的逻辑方法
		if value.handlers != nil {
			c.handlers = value.handlers
			c.fullPath = value.fullPath
			c.Next()
			c.writermem.WriteHeaderNow()
			return
		}
	```
	略
	```
		}
		break
	}

	```
	略
	```
	serveError(c, http.StatusNotFound, default404Body)
}

为了讲的详细的点我们把我们的关键代码单独拿出来说

	c.handlers = value.handlers	// 1.将接口的handlers赋值给了Context的handlers
	c.fullPath = value.fullPath // 2.给fullPath 赋值
	c.Next()										// 3.执行Next()	方法
	c.writermem.WriteHeaderNow()// 4. 给HTTP response写入status code
	return

步骤1的赋值给的是handlers 而不是handler,这里大家可能会很奇怪,我业务逻辑其实一个函数就可以解决,这里为啥会缓存一个handlers呢,难道我的业务函数要拆分成好几个函数来写?其实这个handlers 存储的除了我们业务函数就是我们所有的中间件函数。

// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)

// HandlersChain defines a HandlerFunc slice.
type HandlersChain []HandlerFunc

gin的中间件调用的密码也都包含在步骤3里

func (c *Context) Next() {
	// 执行下标++
	c.index++
	// 一上来就自增是因为func (c *Context) reset()这个初始化方法里,会把下标初始化成-1,所以我们一上来就要++,让他变成0。
	// 这么做的目的就是因为我们的中间件函数中也会调用Next()
	for c.index < int8(len(c.handlers)) {
		// 通过下标去执行中间件
		c.handlers[c.index](c)
		// 显然执行完了++,不然就死循环了
		c.index++
	}
}

gin的中间件调用的秘密还是蛮简单的,接下来我们看看gin中间件的注册。
gin的中间件注册,大家都知道engine可以使用Use,路由组的RouterGroup也可以使用Use,实际上engine.Use()也是调用了RouterGroup.Use(),因为我们engine的路由包括了我们所有的RouterGroup的路由。

func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
	engine.RouterGroup.Use(middleware...)
	// 这边是初始化的时候顺便注册一些异常的处理方式
	// 当然为了避免重复的调用这两个rebuild40X函数,这个Use还是建议一次性调用到位
	engine.rebuild404Handlers()	
	engine.rebuild405Handlers()
	return engine
}

// Use adds middleware to the group, see example code in GitHub.
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
	// 非常的简单,我们的Handlers 是一个切片,他把所有的middleware插入到中间件的末端
	group.Handlers = append(group.Handlers, middleware...)
	return group.returnObj()
}

这样子就实现了我们功能强大的中间件调用链条,只说这些感觉内容少了点,再带大家看看gin自带的两个中间件把。

// 这是gin代码中获取默认Engine 的方法,大家一定都用过
func Default() *Engine {
	// 无关紧要的版本校验
	debugPrintWARNINGDefault()
	// New()第一篇应该说过
	engine := New()
	// 这里调用了两个中间件Logger(), Recovery()
	engine.Use(Logger(), Recovery())
	return engine
}

Logger() 顾名思义,很显然是打印日志的一个中间件,打印日志也是中间件在开发中最有用的几个用处之一

func Logger() HandlerFunc {
	return LoggerWithConfig(LoggerConfig{})
}

// LoggerWithConfig instance a Logger middleware with config.
func LoggerWithConfig(conf LoggerConfig) HandlerFunc {
	// LoggerConfig不是我们本文讲解的重点,大家可以自己去看看
	// 这下面的代码大意就是获取日志的打印格式和输出位置
	formatter := conf.Formatter
	if formatter == nil {
		formatter = defaultLogFormatter
	}

	out := conf.Output
	if out == nil {
		out = DefaultWriter
	}

	notlogged := conf.SkipPaths

	isTerm := true

	if w, ok := out.(*os.File); !ok || os.Getenv("TERM") == "dumb" ||
		(!isatty.IsTerminal(w.Fd()) && !isatty.IsCygwinTerminal(w.Fd())) {
		isTerm = false
	}

	// 知识点:struct{}不占内存空间
	// 在go语言map[string]struct{}的写法相当于创建了一个set结构体。
	var skip map[string]struct{}

	if length := len(notlogged); length > 0 {
		skip = make(map[string]struct{}, length)

		for _, path := range notlogged {
			skip[path] = struct{}{}
		}
	}
	// 正文开始
	return func(c *Context) {
		// Start timer
		start := time.Now()
		path := c.Request.URL.Path
		raw := c.Request.URL.RawQuery

		// Process request
		```
		// 可能很多人会好奇为什么在中间件的中间还要执行一个next
		// 这是一个中间件在逻辑函数执行之前还有逻辑函数执行之后,都有钩子逻辑可以执行
		// 大家可以把这个想象成一个套娃的结构
		// 例如:
		logger() 前半段
		logic()  业务逻辑代码
		logger() 后半段

		```
		c.Next()

		// Log only when path is not being skipped
		// 上面英文很简单大家自己看看
		if _, ok := skip[path]; !ok {
			param := LogFormatterParams{
				Request: c.Request,
				isTerm:  isTerm,
				Keys:    c.Keys,
			}

			// Stop timer
			// 结构化打印,然后输出到流中,没啥好说的
			param.TimeStamp = time.Now()
			param.Latency = param.TimeStamp.Sub(start)

			param.ClientIP = c.ClientIP()
			param.Method = c.Request.Method
			param.StatusCode = c.Writer.Status()
			param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String()

			param.BodySize = c.Writer.Size()

			if raw != "" {
				path = path + "?" + raw
			}

			param.Path = path
			// 输出完了
			fmt.Fprint(out, formatter(param))
		}
	}
}

不过一般大家的项目之中都不会用gin提供的logger中间件,因为大家都有自己的日志格式,不过你实在想用也可以,把自己的logger实例实现gin.logger的接口,然后在初始化engine的时候传给engine就好了

文章来源地址https://www.toymoban.com/news/detail-678059.html

到了这里,关于go web框架 gin-gonic源码解读03————middleware的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Go语言Web框架Gin常见用法

    Gin是目前Go语言最为常用的Web框架,日常工作中也少不了使用此框架,编写此使用总结文档以备后用。 此文档参考官方文档编写,仅用于自我学习总结和参考。 我一直认为编写文档的意义一方面是给其他人提供了些许帮助,另一方面则是让自己加深了对知识的理解并为自己提

    2024年02月03日
    浏览(35)
  • 基于go语言gin框架的web项目骨架

    节省时间与精力,更高效地打造稳定可靠的Web项目:基于Go语言和Gin框架的完善Web项目骨架。无需从零开始,直接利用这个骨架,快速搭建一个功能齐全、性能优异的Web应用。充分发挥Go语言和Gin框架的优势,轻松处理高并发、大流量的请求。构建可扩展性强、易于维护的代码

    2024年02月08日
    浏览(32)
  • 初始化一个Gin框架的Go-Web项目

    使用到的第三方库 gin Gin 框架 viper 配置文件管理 cors 跨域资源请求配置 gorm ORM 库 zap 日志记录 Go 语言程序的入口点 main.go 文件 使用 flag 读取配置文件路径参数,默认当前目录下 使用 viper 读取 config.ini 配置文件初始化初始数据 初始化随机数种子 初始化数据库 声明启动程序

    2024年02月09日
    浏览(40)
  • 【Go Web开发】Web初识、RESTful架构和RESTful API详解、Gin框架的安装和简单使用

    博主简介: 努力学习的大一在校计算机专业学生,热爱学习和创作。目前在学习和分享:数据结构、Go,Java等相关知识。 博主主页: @是瑶瑶子啦 所属专栏: Go语言核心编程 近期目标: 写好专栏的每一篇文章 全称: “万维网”(World Wide Web) 本质: 系统(基于互联网).

    2024年02月03日
    浏览(39)
  • Go(四)gin框架

    1.1、下载和安装gin 下载包:go get github.com/gin-gonic/gin 使用go mod管理包: 1)初始化 Go Modules :go mod init your_module_name,这将创建一个 go.mod 文件,记录你的项目的模块信息和当前依赖关系; 2)复制依赖包到vendor目录 :\\\"go mod vendor\\\" 会将项目的所有包复制到vendor目录中。这包括

    2024年01月25日
    浏览(27)
  • Go-Gin框架

    Gin是一个用Go编写的HTTPweb框架。它是一个类似于martini但拥有更好性能的API框架, 优于httprouter,速度提高了近 40 倍。 点击此处访问Gin官方中文文档。 新建文件main.go,内容如下: 运行后访问: http://localhost:8000/ Gin支持加载HTML模板, 然后根据模板参数进行配置并返回相应的数

    2024年02月20日
    浏览(30)
  • Go -【gin】框架搭建基本使用

    Gin是一个快速的Golang web框架,它使用了httprouter来处理路由和速度,而不是使用内置的Go路由。以下是Gin框架的搭建和使用: 这将从Gin GitHub仓库中安装最新版本的Gin框架。 在搭建一个Gin应用程序之前,让我们了解一下Gin的基本架构: Router :它是Gin应用程序的核心部分,它接

    2024年02月16日
    浏览(28)
  • GO学习之 微框架(Gin)

    1、GO学习之Hello World 2、GO学习之入门语法 3、GO学习之切片操作 4、GO学习之 Map 操作 5、GO学习之 结构体 操作 6、GO学习之 通道(Channel) 7、GO学习之 多线程(goroutine) 8、GO学习之 函数(Function) 9、GO学习之 接口(Interface) 10、GO学习之 网络通信(Net/Http) 11、GO学习之 微框架(Gin) 12、GO学习

    2024年02月13日
    浏览(29)
  • go语言Gin框架常见面试题(1)

    Gin框架是一种基于Go语言的轻量级Web框架,具有高效、快速、易用等优点。Gin采用了类似于Expres

    2024年02月08日
    浏览(52)
  • Go新项目-为何选Gin框架?(0)

    先说结论:我们选型Gin框架 早在大概在2019年下旬,由于内部一个多线程上传的需求,考虑到Go协程的优势; 内部采用Gin框架编写了内部的数据上传平台BAP,采用Gin+Vue开发,但前期没考虑到工程化思维,导致代码后期维护程度变得很复杂,硬编码内容过多,重复内容过多;

    2024年01月17日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包