Gin使用及源码简析
1. Gin简介
前面通过两篇文章分享了Golang HTTP编程的路由分发、请求/响应处理。
可以看出来Golang原生HTTP编程在路由分组、动态路由及参数读取/验证、构造String/Data/JSON/HTML响应的方法等存在优化的空间。
Gin是一个用Golang编写的高性能Web框架。
- 基于前缀树的路由,快速且支持动态路由
- 支持中间件及路由分组,将具有同一特性的路由划入统一组别、设置相同的中间件。
- 比如需要登录的一批接口接入登录权限认证中间件、而不需要登录一批接口则不需要接入
- ...
2. 快速使用
基于gin@v1.8.1,基本使用如下
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-Type为application/json的响应体,
这也是Gin对原生net/http编程的一个优化,对常用的响应类型进行封装,方便使用者使用。
当然,Gin对请求/响应参数的处理还有其它很多细微的优化,这里就不详细说明了。
5. 总结
Gin使用Map来实现路由匹配,而Gin使用路由树来实现路由匹配,支持动态路由,内存占用小且路由匹配快。
同时Gin使用缓存来优化请求参数的处理过程,提供了通用的响应参数处理等,方便用户使用。
Gin使用及源码简析的更多相关文章
- SpringMVC学习(一)——概念、流程图、源码简析
学习资料:开涛的<跟我学SpringMVC.pdf> 众所周知,springMVC是比较常用的web框架,通常整合spring使用.这里抛开spring,单纯的对springMVC做一下总 ...
- Flink源码阅读(一)——Flink on Yarn的Per-job模式源码简析
一.前言 个人感觉学习Flink其实最不应该错过的博文是Flink社区的博文系列,里面的文章是不会让人失望的.强烈安利:https://ververica.cn/developers-resource ...
- django-jwt token校验源码简析
一. jwt token校验源码简析 1.1 前言 之前使用jwt签发了token,里面的头部包含了加密的方式.是否有签名等,而载荷中包含用户名.用户主键.过期时间等信息,最后的签名还使用了摘要算法进 ...
- 0002 - Spring MVC 拦截器源码简析:拦截器加载与执行
1.概述 Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理.例如通过拦截器可以进行权限验证.记录请求信息的日 ...
- OpenStack之Glance源码简析
Glance简介 OpenStack镜像服务器是一套虚拟机镜像发现.注册.检索. glance架构图: Glance源码结构: glance/api:主要负责接收响应镜像管理命令的Restful请求, ...
- AFNetworking源码简析
AFNetworking基本是苹果开发中网络请求库的标配,它是一个轻量级的网络库,专门针对iOS和OS X的网络应用设计,具有模块化的架构和丰富的APIs接口,功能强大并且使用简单,深受苹果应用开发人 ...
- ElementUI 源码简析——源码结构篇
ElementUI 作为当前运用的最广的 Vue PC 端组件库,很多 Vue 组件库的架构都是参照 ElementUI 做的.作为一个有梦想的前端(咸鱼),当然需要好好学习一番这套比较成熟的架构. ...
- DRF之APIView源码简析
一. 安装djangorestframework 安装的方式有以下三种,注意,模块就叫djangorestframework. 方式一:pip3 install djangorestframework ...
- spring ioc源码简析
ClassPathXmlApplicationContext 首先我们先从平时启动spring常用的ClassPathXmlApplicationContext开始解析 ApplicationCont ...
- 并发系列(二)——FutureTask类源码简析
背景 本文基于JDK 11,主要介绍FutureTask类中的run().get()和cancel() 方法,没有过多解析相应interface中的注释,但阅读源码时建议先阅读注释,明白方法的主要的功 ...
随机推荐
- Visual Studio 2019注册码
最近在学习Visual Studio,但是晕斗士(筛子系统)提示需要注册码,否则只能试用30天,由于是学习购买就没必要了,找Google找到了一下两段注册码. 目前测试了专业版已经注册成功. Visu ...
- ubuntu 系统增加源和删除源文件
一.添加PPA源文件 语法格式:sudo add-apt-repository ppa:user/ppa-name 示例: sudo add-apt-repository ppa:sergiomeji ...
- Android studio学习第一期
下载工具 Android studio 参考博客https://blog.csdn.net/weixin_45406151/article/details/114531103 汉化完成 并创建了虚拟手 ...
- 逆向学习物联网-网关ESP8266-01硬件原理及平台搭建
1.系统原理 2.ESP8266网关的内部原理框图 1)STM32通过COM2以AT指令与ESP-01进行通讯,实现MQTT协议, 2)将COM3收到的JSON数据,透明传输到云端 3)通过COM2收 ...
- python写入sqlserver中文乱码问题
需求是python3开发,数据库是sqlserver,第一次用python操作sqlserver,写入数据时,中文全部变成了?? 试了pyodbc,但缺少sqlserver驱动 试了sqlStr.en ...
- if __name__ == '__main__':中的语句无法执行
在pycarm中我们用了pytest或unittest框架写测试用例,我们如果我们在最后加上if name == 'main':,如以下代码所示.最后我们右键点击运行的时候是不会执行**if name ...
- 2020年第11届蓝桥杯C/C++B组 第二轮省赛
# JJU-干干 试题A :门牌制作[问题描述]小蓝要为一条街的住户制作门牌号.这条街一共有 2020 位住户,门牌号从 1 到 2020 编号.小蓝制作门牌的方法是先制作 0 到 9 这几个数字字符 ...
- oracle建表和sqlserver建表
oracle declare num number;begin select count(1) into num from user_all_tables where Upper(Table_Name ...
- Python中RSA的PKCS#1、PKCS#8,MD5加密
一.Python-RSA RSA库只支持PKCS#1的密钥格式 需要安装第三方库rsa pip install rsa python-rsa官方地址:https://stuvel.eu/python- ...
- HDFS相关问题处理
机房搬迁后datanode启动失败,报错如下: 2022-10-21 10:28:40,551 INFO org.apache.hadoop.hdfs.server.common.Storage: L ...