Gin框架学习 (一)

1.Gin是什么?

Gin是golang的轻量级Web框架,具有高性能的优点。

代码仓库地址:https://github.com/gin-gonic/gin
Gin中文说明文档:https://www.kancloud.cn/shuangdeyu/gin_book/949414

Gin的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
}

代码中的 gin.H是 map[string]interface{} 的一个快捷名称 ,写起来会更加简洁:

type H map[string]interface{
    
    }

2.Gin的优势?

  1. Gin是用Golang编写的Web框架。它是一个类似于martini但拥有更好性能的API框架,由于httprouter,速度提高了40倍,因此具有很高的性能和效率
  2. Gin当前是Go语言环境下最受欢迎的WEB框架

httprouter是一个高性能路由分发器,它负责将不同方法的多个路径分别注册到各个handle函数,当收到请求时,负责快速查找请求的路径是否有相对应的处理函数,并且进行下一步业务逻辑处理。

golang的gin框架采用了httprouter进行路由匹配,httprouter 是通过**基数树(radix tree)**来进行高效的路径查找;同时路径还支持两种通配符匹配。

前缀树和基数树(radix tree)的区别

拓展:
Go 语言最流行了两个轻量级 Web 框架分别是 Gin 和 Echo,这两个框架大同小异,都是插件式轻量级框架,背后都有一个开源小生态来提供各式各样的小插件,这两个框架的性能也都非常好,裸测起来跑的飞快。

https://zhuanlan.zhihu.com/p/148022541

golang的6个最佳web框架:
Beego: 一个Go语言下开源的,高性能Web框架

  • https://github.com/astaxie/beego
  • https://beego.me

Buffalo: 一个Go语言下快速Web开发框架

  • https://github.com/gobuffalo/buffalo
  • https://gobuffalo.io

Echo: 一个高性能,极简的Web框架

  • https://github.com/labstack/echo
  • https://echo.labstack.com

Gin: 一个Go语言写的HTTP Web框架。它提供了Martini风格的API并有更好的性能。

  • https://github.com/gin-gonic/gin
  • https://gin-gonic.github.io/gin

Iris: 目前发展最快的Go Web框架。提供完整的MVC功能并且面向未来。

  • https://github.com/kataras/iris
  • https://iris-go.com

Revel: 一个高生产率,全栈Go语言的Web框架。

  • https://github.com/revel/revel
  • https://revel.github.io

3.Gin的功能特性?

(1) gin.Engine

Engine 是 Gin 框架最重要的数据结构,它是框架的入口。我们通过 Engine 对象来定义服务路由信息、组装插件、运行服务。

Engine 对象很简单,因为引擎最重要的部分 —— 底层的 HTTP 服务器使用的是 Go 语言内置的 http server,Engine 的本质只是对内置的 HTTP 服务器的包装,让它使用起来更加便捷。

gin.Default() 函数会生成一个默认的 Engine 对象,里面包含了 2 个默认的常用插件,分别是 Logger 和 Recovery,Logger 用于输出请求日志,Recovery 确保单个请求发生 panic 时记录异常堆栈日志,输出统一的错误响应。

func Default() *Engine {
    
    
    engine := New()
    engine.Use(Logger(), Recovery())
    return engine
}

(2) 路由树

在 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)

(3) gin.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 方法将请求处理器挂接到路由树中。

RouterGroup 内部有一个前缀路径属性,它会将所有的子路径都加上这个前缀再放进路由树中。有了这个前缀路径,就可以实现 URL 分组功能。Engine 对象内嵌的 RouterGroup 对象的前缀路径是 /,它表示根路径。RouterGroup 支持分组嵌套,使用 Group 方法就可以让分组下面再挂分组,于是子子孙孙无穷尽也。

(4) gin.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 等。它会为每一种形式都单独定制一个渲染器。通常这些内置渲染器已经足够应付绝大多数场景,如果你觉得不够,还可以自定义渲染器。

渲染引擎 – 负责显示请求的内容。比如请求到HTML, 它会负责解析HTML、CSS并将结果显示到窗口中

所有的渲染器最终还是需要调用内置的 http.ResponseWriter(Context.Writer) 将响应对象转换成字节流写到套接字中。

(5) 插件与请求链

路由节点需要挂接一个函数链:

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 提供了插件,只有函数链的尾部是业务处理,前面的部分都是插件函数。在 Gin 中插件和业务处理函数形式是一样的,都是 func( *Context)。当我们定义路由时,Gin 会将插件函数和业务处理函数合并在一起形成一个链条结构。

Gin 在接收到客户端请求时,找到相应的处理链,构造一个 Context 对象,再调用它的 Next() 方法就正式进入了请求处理的全流程

Gin 还支持 Abort() 方法中断请求链的执行,它的原理是将 Context.index 调整到一个比较大的数字,这样 Next() 方法中的调用循环就会立即结束。需要注意的 Abort() 方法并不是通过 panic 的方式中断执行流,执行 Abort() 方法之后,当前函数内后面的代码逻辑还会继续执行。

RouterGroup 提供了 Use() 方法来注册插件,因为 RouterGroup 是一层套一层,不同层级的路由可能会注册不一样的插件,最终不同的路由节点挂接的处理函数链也不尽相同。

func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
    
    
    group.Handlers = append(group.Handlers, middleware...)
    return group.returnObj()
}

(6) HTTP 错误

当 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 抛出来的异常需要也需要使用插件来处理。

(7) 静态文件服务

RouterGroup 对象里定义了下面三个用来服务静态文件的方法:

// 服务单个静态文件
StaticFile(relativePath, filePath string) IRoutes
// 服务静态文件目录
Static(relativePath, dirRoot string) IRoutes
// 服务虚拟静态文件系统
StaticFS(relativePath string, fs http.FileSystem) IRoutes

静态文件服务挂在 RouterGroup 上,支持嵌套。

这三个方法中 StaticFS 方法比较特别,它对文件系统进行了抽象,你可以提供一个基于网络的静态文件系统,也可以提供一个基于内存的静态文件系统。FileSystem 接口也很简单,提供一个路径参数返回一个实现了 File 接口的文件对象。不同的虚拟文件系统使用不同的代码来实现 File 接口。

type FileSystem interface {
    
    
 Open(path string) (File, error)
}

type File interface {
    
    
 io.Closer
 io.Reader
 io.Seeker
 Readdir(count int) ([]os.FileInfo, error)
 Stat() (os.FileInfo, error)
}

静态文件处理器和普通处理器一样,也需要经过插件的重重过滤。

(8) 表单处理

当请求参数数量比较多时,使用 Context.Query() 和 Context.PostForm() 方法来获取参数就会显得比较繁琐。

Gin 框架也支持表单处理,将表单参数和结构体字段进行直接映射。

type LoginForm struct {
    
    
    User     string `form:"user" binding:"required"`
    Password string `form:"password" binding:"required"`
}

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"})
     }
}

Context.ShouldBind 方法遇到校验不通过时,会返回一个错误对象告知调用者校验失败的原因。它支持多种数据绑定类型,如 XML、JSON、Query、Uri、MsgPack、Protobuf等,根据请求的 Content-Type 头来决定使用何种数据绑定方法

默认内置的表单校验功能很强大,它通过结构体字段 tag 标注来选择相应的校验器进行校验。Gin 还提供了注册自定义校验器的入口,支持用户自定义一些通用的特殊校验逻辑。

func (c *Context) ShouldBind(obj interface{
    
    }) error {
    
    
 // 获取绑定器
    b := binding.Default(c.Request.Method, c.ContentType())
    // 执行绑定
 return c.ShouldBindWith(obj, b)
}

Context.ShouldBind 是比较柔和的校验方法,它只负责校验,并将校验结果以返回值的形式传递给上层。Context 还有另外一个比较暴力的校验方法 Context.Bind,它和 ShouldBind 的调用形式一摸一样,区别是当校验错误发生时,它会调用 Abort() 方法中断调用链的执行,向客户端返回一个 HTTP 400 Bad Request 错误。

(9)HTTPS

Gin框架添加对HTTPS的配置步骤:

  1. 首先在阿里云搞定ICP域名备案
  2. 添加一个子域名
  3. 给子域名申请免费 SSL 证书, 然后下载证书对应的 pem 和 key 文件.

用 GIN 框架添加一个 github.com/unrolled/secure 中间件就可以了.

示例:

package main

import (
    "github.com/gin-gonic/gin"
    "github.com/unrolled/secure"
)

func main() {
    
    
    router := gin.Default()
    router.Use(TlsHandler())

    router.RunTLS(":8080", "ssl.pem", "ssl.key")
}

func TlsHandler() gin.HandlerFunc {
    
    
    return func(c *gin.Context) {
    
    
        secureMiddleware := secure.New(secure.Options{
    
    
            SSLRedirect: true,
            SSLHost:     "localhost:8080",
        })
        err := secureMiddleware.Process(c.Writer, c.Request)

        // If there was an error, do not continue.
        if err != nil {
    
    
            return
        }

        c.Next()
    }
}

https://mp.weixin.qq.com/s/suMyvEf7J87STbRt1Y3AEA

猜你喜欢

转载自blog.csdn.net/weixin_43202635/article/details/115215690