前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go:web框架 Gin 简介与实践

Go:web框架 Gin 简介与实践

作者头像
Freedom123
发布2024-03-29 14:13:00
1220
发布2024-03-29 14:13:00
举报
文章被收录于专栏:DevOpsDevOps

简介

gin是用go语言开发的一个web框架,简单易用,是一个轻量级框架。Gin是Golang的一个web框架,封装优雅API友好,源码注释比较明确。借助Gin框架开发web服务,不仅可以省去很多常用的封装带来的时间,也有助于团队的编码风格和形成规范。

功能特性
  • 1.快速:基于 Radix 树的路由,小内存占用。没有反射。可预测的 API 性能。
  • 2.支持中间件:传入的 HTTP 请求可以由一系列中间件和最终操作来处理。 例如:Logger,Authorization,GZIP,最终操作 DB。
  • 3.Crash 处理:Gin 可以 catch 一个发生在 HTTP 请求中的 panic 并 recover 它。这样,你的服务器将始终可用。例如,你可以向 Sentry 报告这个 panic!
  • 4.JSON 验证:Gin 可以解析并验证请求的 JSON,例如检查所需值的存在。
  • 5.路由组:更好地组织路由。是否需要授权,不同的 API 版本…… 此外,这些组可以无限制地嵌套而不会降低性能。
  • 6.错误管理:Gin 提供了一种方便的方法来收集 HTTP 请求期间发生的所有错误。最终,中间件可以将它们写入日志文件,数据库并通过网络发送。
  • 7.内置渲染:Gin 为 JSON,XML 和 HTML 渲染提供了易于使用的 API。
  • 8.可扩展性:新建一个中间件非常简单

一、示例

安装Gin

代码语言:javascript
复制
go get -u github.com/gin-gonic/gin

使用Gin

代码语言:javascript
复制
import "github.com/gin-gonic/gin"

示例代码:

代码语言:javascript
复制
import (
    "github.com/gin-gonic/gin"
    "net/http"
)

func main(){
    router := gin.Default()
    router.GET("/", func(c *gin.Context) {
        c.String(http.StatusOK, "Hello World")
    })
    router.Run(":8000")
}

二、高级功能

1. 分组功能

Gin提供了分组路由的功能,能够让代码更加模块化。

代码语言:javascript
复制
v1 := router.Group("/v1")
v1.GET("/login", func(c *gin.Context) {
     c.String(http.StatusOK, "v1 login")
})

v2 := router.Group("/v2")
v2.GET("/login", func(c *gin.Context) {
     c.String(http.StatusOK, "v2 login")
})
2. 中间件

Gin提供了中间件的功能,中间件分为全局中间件、单个路由中间件和分组中间件。 中间件只对注册过的路由函数起作用,对于分组路由使用中间件可以限定中间件的作用范围。

中间件的功能主要用于请求处理之前的鉴权、通用参数解析 以及在请求处理之后统一的响应构建等等。

① 全局中间件:在全局路由上注册中间件
代码语言:javascript
复制
func MiddleWare() gin.HandlerFunc {
    return func(c *gin.Context) {
        fmt.Println("before middleware")
        c.Set("request", "clinet_request")
        c.Next()
        fmt.Println("before middleware")
    }
}
代码语言:javascript
复制
router.Use(MiddleWare())
router.GET("/middleware", func(c *gin.Context) {
	request := c.MustGet("request").(string)
	req, _ := c.Get("request")
	c.JSON(http.StatusOK, gin.H{
		"middile_request": request,
        "request": req,
    })
})

router.Use(MiddleWare()) 表示所有的路由都会经过MiddleWare()函数处理。

② 单个路由中间件 : 在单个路由上注册中间件
代码语言:javascript
复制
func MiddleWare() gin.HandlerFunc {
    return func(c *gin.Context) {
        fmt.Println("before middleware")
        c.Set("request", "clinet_request")
        c.Next()
        fmt.Println("before middleware")
    }
}
代码语言:javascript
复制
router.GET("/before", MiddleWare(), func(c *gin.Context) {
	request := c.MustGet("request").(string)
	c.JSON(http.StatusOK, gin.H{
        "middile_request": request,
    })
})

router.GET(“/before”, MiddleWare(), func(c gin.Context) 只有*/before**这个路由会经过MiddleWare()函数处理。

③ 分组中间件 在分组路由上注册中间件
代码语言:javascript
复制
func MiddleWare() gin.HandlerFunc {
    return func(c *gin.Context) {
        fmt.Println("before middleware")
        c.Set("request", "clinet_request")
        c.Next()
        fmt.Println("before middleware")
    }
}
代码语言:javascript
复制
authorized := router.Group("/")
authorized.Use(MyMiddelware())
authorized.POST("/login", loginEndpoint)

authorized := router.Group(“/”) 和 authorized.Use(MyMiddelware()) 表示只在authorized这个分组下的路由会经过MiddleWare()函数处理。

3. 参数获取

web服务通常是客户端和服务端交互,其中客户端向服务器发送请求,请求参数无非两种,查询字符串query string和报文体body参数。

① query string参数

query string参数指**?以后连接的key1=value2&key2=value2形式的参数,默认content-type是x-www-form-urlencoded**。Gin参数获取支持提供默认值,例如下面这段代码

代码语言:javascript
复制
func main(){
    router := gin.Default()
    router.GET("/welcome", func(c *gin.Context) {
        firstname := c.DefaultQuery("firstname", "Guest")
        lastname := c.Query("lastname")
        c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
    })
  router.Run()
}
  • 使用c.DefaultQuery方法读取参数,当参数不存在的时候会提供一个默认值。
  • 使用c.Query方法读取参数,当参数不存在的时候返回空字串。
② body参数

HTTP报文体body传输格式常见的有以下4种

  • application/json 表示以json格式传输
  • application/x-www-form-urlencoded 表示把query string格式的参数放到body
  • application/xml 表示以xml格式传输
  • multipart/form-data 主要用于图片上传
代码语言:javascript
复制
func main(){
    router := gin.Default()
    router.POST("/form_post", func(c *gin.Context) {
        message := c.PostForm("message")
        nick := c.DefaultPostForm("nick", "anonymous")

        c.JSON(http.StatusOK, gin.H{
            "status":  gin.H{
                "status_code": http.StatusOK,
                "status":      "ok",
            },
            "message": message,
            "nick":    nick,
        })
    })
}
  • c.PostForm(“message”) 从body里获取message字段值,默认content-type是x-www-form-urlencoded。
  • c.DefaultPostForm(“nick”, “anonymous”) 从body里获取nick字段值,获取不到默认值为anonymous
  • c.JSON 调用c.JSON则返回json数据,其中gin.H封装了生成json的方式。
4. 参数绑定

常规的HTTP请求参数校验需要业务写大量的if else逻辑, Gin提供了参数bind功能支持参数校验。content-type无论是x-www-form-urlencoded 还是 **application/json **都支持。

代码语言:javascript
复制
type User struct {
    Username string `form:"username" json:"username" binding:"required"`
    Passwd   string `form:"passwd"   json:"passwd"   bdinding:"required"`
    Age      int    `form:"age"      json:"age"`
}

func main(){
    router := gin.Default()
    router.POST("/login", func(c *gin.Context) {
        var user User
        var err error
        contentType := c.Request.Header.Get("Content-Type")

        switch contentType {
        case "application/json":
            err = c.BindJSON(&user)
        case "application/x-www-form-urlencoded":
            err = c.BindWith(&user, binding.Form)
        }

        if err != nil {
            fmt.Println(err)
            log.Fatal(err)
        }

        c.JSON(http.StatusOK, gin.H{
            "user":   user.Username,
            "passwd": user.Passwd,
            "age":    user.Age,
        })
    })
}
  • application/json使用c.BindJSON进行参数校验
  • application/x-www-form-urlencoded使用c.BindWith进行参数校验

三、源码解析

创建 Gin 实例

代码语言:javascript
复制
r := gin.Default()  // 获取一个Gin的实例:Engine,gin的核心数据结构

这里获取了一个默认的 gin的实例

代码语言:javascript
复制
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
	debugPrintWARNINGDefault()
	engine := New() // 默认实例
	engine.Use(Logger(), Recovery()) // 注册中间建,中间件的是一个函数,最终只要返回一个 type HandlerFunc func(*Context) 就可以
	return engine
}
```go

 #### 路由注册
 ```go
r.GET("/", func(c *gin.Context) { // 注册路由, 实际上是调用了 RouterGroup 的Get方法, RouterGroup 实现了 IRoutes interface,RouterGroup 主要提供路有注册功能
		c.String(http.StatusOK, "Hello World!")
	})	

// 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) // 注意这个 combineHandlers,这里会把 r.Use(gin.Logger(), gin.Recovery()) 注册的中间键跟业务的处理函数组合在一起
	group.engine.addRoute(httpMethod, absolutePath, handlers)
	return group.returnObj()
}

func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
	finalSize := len(group.Handlers) + len(handlers)
	if finalSize >= int(abortIndex) {
		panic("too many handlers")
	}
	mergedHandlers := make(HandlersChain, finalSize)
	copy(mergedHandlers, group.Handlers) // 中间键
	copy(mergedHandlers[len(group.Handlers):], handlers) // 业务处理
	return mergedHandlers
}

// IRoutes defines all router handle interface.
type IRoutes interface {
	Use(...HandlerFunc) IRoutes

	Handle(string, string, ...HandlerFunc) IRoutes
	Any(string, ...HandlerFunc) IRoutes
	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

   // ... 省略代码

}

接收请求并响应

代码语言:javascript
复制
// 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 root of the tree for the given HTTP method
    // x
	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, 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
		}
        // ... 省略代码
}

func (c *Context) Next() {
	c.index++
	for c.index < int8(len(c.handlers)) {
		c.handlers[c.index](c) // 业务处理,也就是这里的 func HelloWorld(c *gin.Context), 也会跑中间键
		c.index++
	}
}

func HelloWorld(c *gin.Context) {
	c.String(http.StatusOK, "pong")
}

四、Gin 消息处理流程图

前面我们提到只要一个结构体只要实现了 Handler 接口就可以提供HTTP 服务,gin实现了 Handler 接口 。所以它可以和原生的 http 包结合起来。整个流程就会变成如下图:

最底层的 net.Listener 在监听 TCP 段口,net库将接收到的请求给 http.Server , http.Server0最终会调用服务器实现的 ServerHTTP 函数,因为 gin 实现了 ServerHTTP 函数,所以最终处理请求会交给 gin 来处理。

小结

参考地址:https://blog.csdn.net/chenguolinblog/article/details/90665140 https://blog.csdn.net/baidu_32452525/article/details/117003482

官方地址:https://github.com/gin-gonic/gin 详细文档地址:https://godoc.org/github.com/gin-gonic/gin

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-03-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简介
    • 功能特性
    • 一、示例
    • 二、高级功能
      • 1. 分组功能
        • 2. 中间件
          • ① 全局中间件:在全局路由上注册中间件
          • ② 单个路由中间件 : 在单个路由上注册中间件
          • ③ 分组中间件 在分组路由上注册中间件
        • 3. 参数获取
          • ① query string参数
          • ② body参数
        • 4. 参数绑定
        • 三、源码解析
        • 四、Gin 消息处理流程图
        • 小结
        相关产品与服务
        消息队列 TDMQ
        消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档