【博客414】Go gin框架
2021-05-03 10:33
791 查看
内容:golang 的gin框架是一个比原生更高性能的web开发框架
Hello world
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
g.H
gin.H 是 map[string]interface{} 的一个快捷名称 type H map[string]interface{}
g.Engine
Engine是框架的入口。通过Engine对象来定义服务路由信息、组装插件、运行服务。 正如Engine的中文意思「引擎」一样,它就是框架的核心发动机,整个Web服务的都是由它来驱动的。 Engine 的本质只是对内置的HTTP服务器的包装,让它使用起来更加便捷。 gin.Default() 函数会生成一个默认的 Engine 对象,里面包含了 2 个默认的常用插件, 分别是Logger和Recovery,Logger用于输出请求日志,Recovery确保单个请求发生panic时 记录异常堆栈日志,输出统一的错误响应。 func Default() *Engine { engine := New() engine.Use(Logger(), Recovery()) return engine }
route tree:
路由树: 在 Gin 框架中,路由规则被分成了最多9棵前缀树,每一个 HTTP Method对应一棵「前缀树」, 树的节点按照 URL 中的 / 符号进行层级划分,URL 支持 :name 形式的名称匹配, 还支持 *subpath 形式的路径通配符 。 每个节点都会挂接若干请求处理函数构成一个请求处理链 HandlersChain。 当一个请求到来时,在这棵树上找到请求 URL 对应的节点,拿到对应的请求处理链来执行 type Engine struct { ... trees methodTrees ... } type methodTrees []methodTree type methodTree struct { method string root *node // 树根 } type node struct { path string // 当前节点的路径 ... handlers HandlersChain // 请求处理链 ... } type HandlerFunc func(*Context) type HandlersChain []HandlerFunc Engine 对象包含一个 addRoute 方法用于添加URL请求处理器,它会将对应的路径和处理器 挂接到相应的请求树中: func (e *Engine) addRoute(method, path string, handlers HandlersChain)
g.RouterGroup
RouterGroup 是对路由树的包装,所有的路由规则最终都是由它来进行管理。 Engine结构体继承了RouterGroup ,所以Engine直接具备了RouterGroup所有的路由管理功能。 这是为什么在 Hello World 的例子中,可以直接使用 Engine 对象来定义路由规则。 同时 RouteGroup 对象里面还会包含一个 Engine 的指针,这样Engine和RouteGroup就成了 「你中有我我中有你」的关系。 type Engine struct { RouterGroup ... } type RouterGroup struct { ... engine *Engine ... } RouterGroup 实现了 IRouter 接口,暴露了一系列路由方法,这些方法最终都是通过调用 Engine.addRoute 方法将请求处理器挂接到路由树中。 GET(string, ...HandlerFunc) IRoutes POST(string, ...HandlerFunc) IRoutes DELETE(string, ...HandlerFunc) IRoutes PATCH(string, ...HandlerFunc) IRoutes PUT(string, ...HandlerFunc) IRoutes OPTIONS(string, ...HandlerFunc) IRoutes HEAD(string, ...HandlerFunc) IRoutes // 匹配所有 HTTP Method Any(string, ...HandlerFunc) IRoutes RouterGroup 内部有一个前缀路径属性,它会将所有的子路径都加上这个前缀再放进路由树中。 有了这个前缀路径,就可以实现URL分组功能。Engine 对象内嵌的RouterGroup对象的前缀路径是/, 它表示根路径。RouterGroup 支持分组嵌套,使用Group方法就可以让分组下面再挂分组 func main() { router := gin.Default() v1 := router.Group("/v1") { v1.POST("/login", loginEndpoint) v1.POST("/submit", submitEndpoint) v1.POST("/read", readEndpoint) } v2 := router.Group("/v2") { v2.POST("/login", loginEndpoint) v2.POST("/submit", submitEndpoint) v2.POST("/read", readEndpoint) } router.Run(":8080") } Engine对象里面的RouterGroup对象就是第一层分组,也就是根分组,v1和v2都是根分组的子分组。
g.Context
这个对象里保存了请求的上下文信息,它是所有请求处理器的入口参数。 type HandlerFunc func(*Context) type Context struct { ... Request *http.Request // 请求对象 Writer ResponseWriter // 响应对象 Params Params // URL匹配参数 ... Keys map[string]interface{} // 自定义上下文信息 ... } Context 对象提供了非常丰富的方法用于获取当前请求的上下文信息,如果需要获取请求中的URL参数、 Cookie、Header 都可以通过Context对象来获取。这一系列方法是对http.Request对象的包装。 // 获取 URL 匹配参数 /book/:id func (c *Context) Param(key string) string // 获取 URL 查询参数 /book?id=123&page=10 func (c *Context) Query(key string) string // 获取 POST 表单参数 func (c *Context) PostForm(key string) string // 获取上传的文件对象 func (c *Context) FormFile(name string) (*multipart.FileHeader, error) // 获取请求Cookie func (c *Context) Cookie(name string) (string, error) ... Context 对象提供了很多内置的响应形式,JSON、HTML、Protobuf 、MsgPack、Yaml 等。 它会为每一种形式都单独定制一个渲染器。通常这些内置渲染器已经足够应付绝大多数场景, 如果你觉得不够,还可以自定义渲染器。 func (c *Context) JSON(code int, obj interface{}) func (c *Context) Protobuf(code int, obj interface{}) func (c *Context) YAML(code int, obj interface{}) ... // 自定义渲染 func (c *Context) Render(code int, r render.Render) // 渲染器通用接口 type Render interface { Render(http.ResponseWriter) error WriteContentType(w http.ResponseWriter) } 所有的渲染器最终还是需要调用内置的http.ResponseWriter(Context.Writer)将响应对象 转换成字节流写到套接字中。 type ResponseWriter interface { // 容纳所有的响应头 Header() Header // 写Body Write([]byte) (int, error) // 写Header WriteHeader(statusCode int) }
插件与请求链:
我们编写业务代码时一般也就是一个处理函数,为什么路由节点需要挂接一个函数链呢? type node struct { path string // 当前节点的路径 ... handlers HandlersChain // 请求处理链 ... } type HandlerFunc func(*Context) type HandlersChain []HandlerFunc 这是因为Gin提供了插件,只有函数链的尾部是业务处理,前面的部分都是插件函数。 在Gin中插件和业务处理函数形式是一样的,都是 func(*Context)。 当我们定义路由时,Gin会将插件函数和业务处理函数合并在一起形成一个链条结构。 type Context struct { ... index uint8 // 当前的业务逻辑位于函数链的位置 handlers HandlersChain // 函数链 ... } // 挨个调用链条中的处理函数 func (c *Context) Next() { c.index++ for s := int8(len(c.handlers)); c.index < s; c.index++ { c.handlers[c.index](c) } } Gin 在接收到客户端请求时,找到相应的处理链,构造一个 Context 对象, 再调用它的 Next() 方法就正式进入了请求处理的全流程。
Gin还支持 Abort() 方法中断请求链的执行,它的原理是将 Context.index调整到一个比较大 的数字,这样 Next() 方法中的调用循环就会立即结束。 需要注意的 Abort() 方法并不是通过 panic 的方式中断执行流,执行 Abort() 方法之后, 当前函数内后面的代码逻辑还会继续执行。 const abortIndex = 127 func (c *Context) Abort() { c.index = abortIndex } func SomePlugin(c *Context) { ... if condition { c.Abort() // continue executing } ... } 如果在插件中显示调用 Next() 方法,那么它就改变了正常的顺序执行流。换个角度来理解, 正常的执行流就是后续的处理器是在前一个处理器的尾部执行,而嵌套执行流是让后续的处理器 在前一个处理器进行到一半时执行,待后续处理器完成执行后,再回到前一个处理器继续往下执行。
RouterGroup 提供了 Use() 方法来注册插件,因为 RouterGroup 是一层套一层, 不同层级的路由可能会注册不一样的插件,最终不同的路由节点挂接的处理函数链也不尽相同。 func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes { group.Handlers = append(group.Handlers, middleware...) return group.returnObj() } // 注册 Get 请求 func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes { return group.handle("GET", relativePath, handlers) } func (g *RouterGroup) handle(method, path string, handlers HandlersChain) IRoutes { // 合并URL (RouterGroup有URL前缀) absolutePath := group.calculateAbsolutePath(relativePath) // 合并处理链条 handlers = group.combineHandlers(handlers) // 注册路由树 group.engine.addRoute(httpMethod, absolutePath, handlers) return group.returnObj() }
HTTP ERROR:
当URL请求对应的路径不能在路由树里找到时,就需要处理404 NotFound错误。 当URL的请求路径在路由树里找到,但是Method不匹配,就需要处理405 MethodNotAllowed错误。 Engine 对象为这两个错误提供了处理器注册的入口 func (engine *Engine) NoMethod(handlers ...HandlerFunc) func (engine *Engine) NoRoute(handlers ...HandlerFunc) 异常处理器和普通处理器一样,也需要和插件函数组合在一起形成一个调用链。 如果没有提供异常处理器,Gin 就会使用内置的简易错误处理器。 注意这两个错误处理器是定义在 Engine 全局对象上,而不是 RouterGroup。 对于非 404 和 405 错误,需要用户自定义插件来处理。对于 panic 抛出来的异常需要也 需要使用插件来处理。
表单:
当请求参数数量比较多时,使用 Context.Query() 和 Context.PostForm() 方法来获取 参数就会显得比较繁琐。Gin 框架也支持表单处理,将表单参数和结构体字段进行直接映射。 package main import ( "github.com/gin-gonic/gin" ) type LoginForm struct { User string `form:"user" binding:"required"` Password string `form:"password" binding:"required"` } func main() { router := gin.Default() router.POST("/login", func(c *gin.Context) { var form LoginForm if c.ShouldBind(&form) == nil { if form.User == "user" && form.Password == "password" { c.JSON(200, gin.H{"status": "you are logged in"}) } else { c.JSON(401, gin.H{"status": "unauthorized"}) } } }) router.Run(":8080") } Context.ShouldBind 方法遇到校验不通过时,会返回一个错误对象告知调用者校验失败的原因。 它支持多种数据绑定类型,如 XML、JSON、Query、Uri、MsgPack、Protobuf等,根据请求的 Content-Type 头来决定使用何种数据绑定方法。 func (c *Context) ShouldBind(obj interface{}) error { // 获取绑定器 b := binding.Default(c.Request.Method, c.ContentType()) // 执行绑定 return c.ShouldBindWith(obj, b) } 默认内置的表单校验功能很强大,它通过结构体字段 tag 标注来选择相应的校验器进行校验。 Gin还提供了注册自定义校验器的入口,支持用户自定义一些通用的特殊校验逻辑。 Context.ShouldBind 是比较柔和的校验方法,它只负责校验,并将校验结果以返回值的形式 传递给上层。Context 还有另外一个比较暴力的校验方法 Context.Bind,它和ShouldBind 的调用形式一摸一样,区别是当校验错误发生时,它会调用 Abort() 方法中断调用链的执行, 向客户端返回一个 HTTP 400 Bad Request 错误。
相关文章推荐
- golang实战使用gin+xorm搭建go语言web框架restgo详解7 视图层V
- Go Gin 框架 curl -I 返回 404 的问题
- go gin框架获取参数信息
- go的gin框架的性能测试
- Go语言web框架 gin
- golang实战使用gin+xorm搭建go语言web框架restgo详解1.1 go语言的困境
- golang实战使用gin+xorm搭建go语言web框架restgo详解8 关于模板
- go的gin框架从请求中获取参数的方法
- go项目配置多开发环境 gin框架
- go服务端----使用gin框架搭建简易服务
- golang实战使用gin+xorm搭建go语言web框架restgo详解1.2 我要做什么
- golang实战使用gin+xorm搭建go语言web框架restgo详解9 session、日志、鉴权、验证码等
- [转]Go语言(Golang)的Web框架比较:gin VS echo
- golang实战使用gin+xorm搭建go语言web框架restgo详解2 框架基本架构
- Win系统使用docker部署go的gin框架简单应用
- Go语言 如何使用gin框架的中间件做身份验证~~~
- golang实战使用gin+xorm搭建go语言web框架restgo详解3 系统常用配置参数
- Go语言web框架学习—Gin
- GO语言web框架Gin之完全指南(二)
- go web框架gin介绍和使用(一)