首页 > 编程语言 >Gin使用及源码简析

Gin使用及源码简析

时间:2023-03-11 12:12:28浏览次数:54  
标签:engine group handlers 简析 源码 func gin Gin 路由

1. Gin简介

前面通过两篇文章分享了Golang HTTP编程的路由分发、请求/响应处理。

可以看出来Golang原生HTTP编程在路由分组、动态路由及参数读取/验证、构造String/Data/JSON/HTML响应的方法等存在优化的空间。

Gin是一个用Golang编写的高性能Web框架。

  • 基于前缀树的路由,快速且支持动态路由
  • 支持中间件及路由分组,将具有同一特性的路由划入统一组别、设置相同的中间件。
    • 比如需要登录的一批接口接入登录权限认证中间件、而不需要登录一批接口则不需要接入
  • ...

 

2. 快速使用

基于[email protected],基本使用如下

func main() {
    // Creates a new blank Engine instance without any middleware attached
    engine := gin.New()
    // Global middleware
    // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release.
    // By default gin.DefaultWriter = os.Stdout
    engine.Use(gin.Logger())
    // Recovery middleware recovers from any panics and writes a 500 if there was one.
    engine.Use(gin.Recovery())
    v1Group := engine.Group("app/v1", accessHandler())
    v1Group.GET("user/info", userInfoLogic())
    engine.Run(":8019")
}

 终端运行go run main.go,输出如下

$ go run main.go
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /app/v1/user/info         --> main.userInfoLogic.func1 (4 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8019

通过打印可以看出注册了GET方法的路由/app/v1/user/info,对应处理函数为main.userInfoLogic

总共包括四个处理函数,按顺序为gin.Logger()gin.Recovery()accessHandler()以及userInfoLogic

最终在端口8019启动了HTTP监听服务。

 

2.1 创建Engine并使用gin.Logger()gin.Recovery() 两个全局中间件,对engine下的所有路由都生效

通过代码及注释,gin.Logger()gin.Recovery()放到了Engine.RouterGroup.Handlers切片中。

// 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()
}

 

2.2 创建路由分组v1Group,且该分组使用了accessHandler()accessHandler()v1Group分组路由均生效

// Group creates a new router group. You should add all the routes that have common middlewares or the same path prefix.
// For example, all the routes that use a common middleware for authorization could be grouped.
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
    return &RouterGroup{
        Handlers: group.combineHandlers(handlers),
        basePath: group.calculateAbsolutePath(relativePath),
        engine:   group.engine,
    }
}

从代码可以看出,返回了新的gin.RouterGroup,并且

v1Group.Handlers = append(group.Handlers, handlers),此时gin.RouterGroup.Handlers[gin.Logger(),gin.Recovery(),accessHandler()]

同时v1Group.basePath = "app/v1"

从代码同时可以得出,支持分组嵌套分组。即在v1Group都基础上在创建分组,比如v1Group.Group("north")

 

2.3 在v1Group下注册路由user/info,该路由的处理函数是userInfoLogic,方法为GET

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 (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)
    ...
}

将分组v1Group的路由前缀和当前user/info计算得到完整路由,即app/v1/user/info

合并处理函数,此时handlers = [gin.Logger(),gin.Recovery(),accessHandler(),userInfoLogic()]

最后将路由及处理函数按http method分组,加入到不同路由树中。

 

2.4 通过 engine.Run(":8019") 在启动HTTP服务

// 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) {
    ...
    address := resolveAddress(addr)
    debugPrint("Listening and serving HTTP on %s\n", address)
    err = http.ListenAndServe(address, engine.Handler())
    return
}

这里调用http.ListenAndServe启动HTTP监听服务,Engine实现了http.Handler接口,如果有客户端请求,会调用到Engine.ServeHTTP函数。

 

3. 路由过程

// gin.go
func (engine *Engine) handleHTTPRequest(c *Context) {
    httpMethod := c.Request.Method
    rPath := c.Request.URL.Path
    ...
    // Find root of the tree for the given HTTP method
    t := engine.trees
    for i, tl := 0, len(t); i < tl; i++ {
        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
    }
}
// context.go
func (c *Context) Next() {
    c.index++
    for c.index < int8(len(c.handlers)) {
        c.handlers[c.index](c)
        c.index++
    }
}

从上面代码可以看出,通过http method找到对应的路由树,再根据URL从路由树中查找对应的节点,

获取到处理函数切片,通过c.Next按通过顺序执行处理函数。

对于请求GET /app/v1/user/info,将依次执行[gin.Logger(),gin.Recovery(),accessHandler(),userInfoLogic()] 

 

4. 请求/响应参数处理

func accessHandler() func(*gin.Context) {
    return func(c *gin.Context) {
        // 不允许crul访问
        if strings.Contains(c.GetHeader("user-agent"), "curl") {
            c.JSON(http.StatusBadRequest, "cant't not visited by curl")
            c.Abort() // 直接退出,避免执行后续处理函数
        }
    }
}
func userInfoLogic() func(*gin.Context) {
    return func(c *gin.Context) {
        id := c.Query("id")
        c.JSON(http.StatusOK, map[string]interface{}{"id": id, "name": "bob", "age": 18})
	}
}

v1Group的通用处理函数accessHandler,达到v1Group下注册的路由无法用curl访问的效果。

通过c.Query("id") 获取URL查询参数,

通过以下代码可以看出,第一次获取URL查询时会缓存所有URL查询参数,这减少了内存的分配,节省了计算资源。

因为每次调用url.ParseQuery都会重新申请缓存,重复解析URL。

func (c *Context) Query(key string) (value string) {
    value, _ = c.GetQuery(key)
    return
}
func (c *Context) initQueryCache() {
    if c.queryCache == nil {
        if c.Request != nil {
            c.queryCache = c.Request.URL.Query()
        } else {
            c.queryCache = url.Values{}
        }
    }
}
func (c *Context) GetQueryArray(key string) (values []string, ok bool) {
    c.initQueryCache()
    values, ok = c.queryCache[key]
    return
}

通过c.JSON返回Content-Typeapplication/json的响应体,

这也是Gin对原生net/http编程的一个优化,对常用的响应类型进行封装,方便使用者使用。

当然,Gin对请求/响应参数的处理还有其它很多细微的优化,这里就不详细说明了。

 

5. 总结

Gin使用Map来实现路由匹配,而Gin使用路由树来实现路由匹配,支持动态路由,内存占用小且路由匹配快。

同时Gin使用缓存来优化请求参数的处理过程,提供了通用的响应参数处理等,方便用户使用。

标签:engine,group,handlers,简析,源码,func,gin,Gin,路由
From: https://www.cnblogs.com/amos01/p/17125045.html

相关文章

  • [Go语言Web02]Gin框架Web开发
    1.Gin框架初识1.1Gin的安装直接参考官方的文档1.1.1安装过程下载并安装gin:$goget-ugithub.com/gin-gonic/gin将gin引入到代码中:import"github.com/......
  • Nginx 负载均衡反向代理 Windows安装部署教程
     一、Nginx简介   Nginx(enginex)是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP服务器。它也是一种轻量级的Web服务器,可以作为独立的服务器部署网站......
  • vue+leaflet示例:地图分屏对比展示(附源码下载)
    demo源码运行环境以及配置运行环境:依赖Node安装环境,demo本地Node版本:14.19.1。运行工具:vscode或者其他工具。配置方式:下载demo源码,vscode打开,然后顺序执行以下命令:(1)......
  • Vue——Vue v2.7.14 源码阅读之代码目录结构【一】
    前言这里主要说一些vue2.7.14源码的目录结构,其实这块有些目录并不重要,不过我还是想全面的描述下,详细的一些文件说明会随着源码解读来补充完善,其中描述如果有错的地方还......
  • Ubuntu(Linux)系统源码编译、移植SQLite
    1.编译后的文件2.配置lib路径3.可执行文件配置路径测试:XX@XXX:etc$sqlite3SQLiteversion3.17.02017-02-1316:02:40Enter".help"forusagehints.Connectedtoat......
  • how to write a webpack plugin by using webpack 5 and TypeScript All In One
    howtowriteawebpackpluginbyusingwebpack5andTypeScriptAllInOne如何使用webpack5和TypeScript编写一个webpack插件(......
  • BLOG begins!!!记录挂
     由于看到一篇写自己十年程序之路的编程员的文章,突然很有感触,虽然还是小菜鸟一枚,但是敲击键盘、在不同链接间旋转跳跃还是给我带来了不少乐趣!所以我突然也想开通自己的BL......
  • Android源码下载
      最近在做Monkey二次开发的工作,边弄边在这里记录下(多平台发布),顺便可以和大家一起讨论下; Monkey的编译依赖于Android源码,所以要修改Monkey后打新jar包,需要完整的Andr......
  • 后台分页-jqPaginator使用
    一,引入<!--分页插件--><scripttype="text/javascript"src="/static/admin/js/jqPaginator.js"></script><scripttype="text/javascript"src="/static/admin/bootst......
  • Nginx 入门
    Nginx是一款高性能、高可靠性的Web服务器,它能够处理大量并发请求,并且可以作为反向代理、负载均衡器、HTTP缓存和安全性代理等多种用途。下面是一个简单的Nginx......