gin框架原理
1. gin框架概述
地址:https://github.com/gin-gonic/gin
gin作为web框架,在社区中拥有较高的活跃度,通过使用前缀树结构存储,减少内存消耗,同时加快了路由查询速度,并且在基本功能的基础上提供了各种middleware,可以在访问对应的url时回调对应的handler
也给大家推荐一个go技术选型参考
组件 | 主选 | 备选 |
选型主要考虑 |
对比 |
Web框架 | Gin[GitHub - gin-gonic/gin: Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.] | Echo、Fiber | 社区活跃度/成熟度/易用性/性能 |
Gin/Echo: 社区活跃度较高、性能较高 Fiber: 基于FastHttp,性能很高 |
Redis | go-redis[GitHub - go-redis/redis: Type-safe Redis client for Golang] | redigo | 社区活跃度/性能/功能 |
go-redis: 社区活跃度较高、性能较高、集群支持好 redigo: 社区活跃度较高 |
SQL | gendry[GitHub - didi/gendry: a golang library for sql builder] | Gorm、Xorm、sqlx | 性能/可维护性/功能 |
gendry: sql builder简单封装,易维护 Gorm/Xorm: 功能强大,社区活跃度高 sqlx: sql builder封装,社区活跃度高 |
日志 | zap[GitHub - uber-go/zap: Blazing fast, structured, leveled logging in Go.] | logrus、seelog | 性能/功能 |
zap: 性能较高,结构化日志首选 logrus: 性能适中,格式定义灵活 seelog: 性能一般,简单易用 |
配置 | go-toml[GitHub - pelletier/go-toml: Go library for the TOML file format] | viper | 易用性/功能 |
go-toml: toml格式处理,简单易用 viper: 支持多种格式配置、功能强大、依赖较复杂 |
2. 框架基本功能详解
2.1 一个简单的demo
-
import (
-
"fmt"
-
"github.com/gin-gonic/gin"
-
"os"
-
)
-
-
func main() {
-
//r := gin.Default()
-
r := gin.New()
-
-
//use must before register route
-
//注册gin自带中间件
-
r.Use(gin.Logger())
-
r.Use(gin.Recovery())
-
-
//注册一个路由和对应的handler
-
r.GET("/test", func(c *gin.Context) {
-
c.JSON(200, gin.H{
-
"message": "hello",
-
})
-
})
-
-
//在/test2的路由组中注册中间件,不影响/test
-
test := r.Group("/test2")
-
test.Use(MyLogger)
-
test.Use(MyLogger2)
-
test.GET("/hello", func(c *gin.Context) {
-
c.JSON(200, gin.H{
-
"message": "hello gin",
-
})
-
})
-
-
r.Run("127.0.0.1:8899")
-
}
-
-
func MyLogger(c *gin.Context) {
-
fmt.Fprintf(os.Stdout, "%s|%s mylogger attach", c.Request.Method, c.Request.URL)
-
}
-
-
func MyLogger2(c *gin.Context) {
-
fmt.Fprintf(os.Stdout, "%s|%s mylogger222222 attach", c.Request.Method, c.Request.URL)
-
}
这个demo包含了gin框架的一些常用用法,包括自己实现一个简单的middleware,启动后,通过访问localhost:8899/test和localhost:8899/test2/hello可以看到对应的返回,并且可以在控制台看到注册的middleware被调用
可以看到gin不仅提供了middleware功能,还提供了根据url path注册不同的middleware功能,middleware可以是全局的也可以只针对某个path
2.2 gin.Use
-
// Use attaches a global middleware to the router. i.e. the middleware attached through Use() will be
-
// included in the handlers chain for every single request. Even 404, 405, static files...
-
// For example, this is the right place for a logger or error management middleware.
-
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
-
engine.RouterGroup.Use(middleware...)
-
engine.rebuild404Handlers()
-
engine.rebuild405Handlers()
-
return engine
-
}
-
-
-
// Use adds middleware to the group, see example code in GitHub.
-
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
-
group.Handlers = append(group.Handlers, middleware...)
-
return group.returnObj()
-
}
-
-
// Engine is the framework's instance, it contains the muxer, middleware and configuration settings.
-
// Create an instance of Engine, by using New() or Default()
-
type Engine struct {
-
RouterGroup
-
......
-
}
-
-
// RouterGroup is used internally to configure router, a RouterGroup is associated with
-
// a prefix and an array of handlers (middleware).
-
type RouterGroup struct {
-
Handlers HandlersChain
-
basePath string
-
engine *Engine
-
root bool
-
}
engine就是gin.New()或者gin.Default()创建出来的全局实例,其中比较重要的属性之一就是RouterGroup,路由组中包括了注册路由需要的基本信息
Use注册中间件流程:
1. 先看透本质,一个middleware其实就是一个handlerFunc,和注册到path上的api方法签名相同
2. 通过路由组进行注册,将中间件放到RouterGroup路由组的handlers切片中
3. 返回对应的RouterGroup,所以Use函数还可以采用链式调用:
-
test.Use(MyLogger).Use(MyLogger2)
-
test.Use(MyLogger, MyLogger2)
2.3 gin.Get
-
// GET is a shortcut for router.Handle("GET", path, handle).
-
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
-
return group.handle(http.MethodGet, relativePath, handlers)
-
}
-
-
-
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
-
absolutePath := group.calculateAbsolutePath(relativePath)
-
handlers = group.combineHandlers(handlers)
-
group.engine.addRoute(httpMethod, absolutePath, handlers)
-
return group.returnObj()
-
}
-
-
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
-
finalSize := len(group.Handlers) len(handlers)
-
assert1(finalSize < int(abortIndex), "too many handlers")
-
mergedHandlers := make(HandlersChain, finalSize)
-
copy(mergedHandlers, group.Handlers)
-
copy(mergedHandlers[len(group.Handlers):], handlers)
-
return mergedHandlers
-
}
-
-
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
-
......
-
-
root := engine.trees.get(method)
-
if root == nil {
-
root = new(node)
-
root.fullPath = "/"
-
engine.trees = append(engine.trees, methodTree{method: method, root: root})
-
}
-
root.addRoute(path, handlers)
-
-
......
-
}
从2.2中可以知道engine使用了组合的特性,将RouterGroup组合使用,所以RouterGroup实现的方法,engine也可以调用
Get、DELETE、POST等方法都调用了handle
handle注册api主要流程:
1. 通过combineHandlers方法,将之前在RouterGroup中的中间件和对应的api方法进行合并,并且api方法放到后面,关系到之后的调用顺序,可以从这里看出,先注册的中间件先调用,然后才到api的handler
2. 从前缀树中找到get对应的树,并把path和合并后的handlers放到树中(前缀树的代码会在后续的文章中为大家详解)
3. 这里也可以理解,handler最开始都是放到RouterGroup的切片中,并不会生效,只有通过handle方法,将合并后的handlers放到前缀树中才算生效,所以在Get、Post等方法后使用use是不符合预期的
2.4 Engine.Run
-
// Run attaches the router to a http.Server and starts listening and serving HTTP requests.
-
// It is a shortcut for http.ListenAndServe(addr, router)
-
// Note: this method will block the calling goroutine indefinitely unless an error happens.
-
func (engine *Engine) Run(addr ...string) (err error) {
-
......
-
-
err = http.ListenAndServe(address, engine.Handler())
-
return
-
}
-
-
func (engine *Engine) Handler() http.Handler {
-
if !engine.UseH2C {
-
return engine
-
}
-
-
h2s := &http2.Server{}
-
return h2c.NewHandler(engine, h2s)
-
}
-
-
// ServeHTTP conforms to the http.Handler interface.
-
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
-
c := engine.pool.Get().(*Context)
-
c.writermem.reset(w)
-
c.Request = req
-
c.reset()
-
-
engine.handleHTTPRequest(c)
-
-
engine.pool.Put(c)
-
}
-
-
func (engine *Engine) handleHTTPRequest(c *Context) {
-
// 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
-
}
-
}
-
-
// Next should be used only inside middleware.
-
// It executes the pending handlers in the chain inside the calling handler.
-
// See example in GitHub.
-
func (c *Context) Next() {
-
c.index
-
for c.index < int8(len(c.handlers)) {
-
c.handlers[c.index](c)
-
c.index
-
}
-
}
Run主要流程:
1. 调用golang原生http库,指定一个http.handler,开启监听
2. engine.handler是gin为了支持H2C后来支持的,原来的代码直接传入的是engine,H2C需要了解的可以自行百度,但是一般是不推荐用的,还是推荐用golang默认支持的H2
3. engine作为一个handler实现了ServeHTTP方法,当访问对应的api时,会走到该方法中
4. serveHTTP主要调用的handleHTTPRequest,其他步骤是通过sync.Pool来操作context,用于减少context的内存分配和初始化次数来增加效率减少GC
5. handleHTTPRequest通过访问的httpMethod和uri,getValue找到前缀树中对应的节点,返回的nodeValue对象就是2.3中添加到树中的节点,其中保存了中间件和api对应的handler
6. 通过Next()函数依次调用handler
3. 最后
还有很多功能未在本文中涉及,但是基本的思想和功能已经涵盖,如需继续拓展可以到第一章中的gin框架链接中学习
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhgfhkii
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
怎样阻止微信小程序自动打开
PHP中文网 06-13 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01