SinceNow.net

gin go

Gin的速看手册

phpangel   2024-06-11 16:59:07

一 gin框架初识

1.1 helloworld

gin框架中的路由是基于httprouter开发的。HelloWorld:

package main
import (
    "github.com/gin-gonic/gin"
    "fmt"
)
func main() {
    r := gin.Default()    //Default返回一个默认路由引擎
    r.GET("/", func(c *gin.Context) {
        username := c.Query("username")
        fmt.Println(username)
        c.JSON(200, gin.H{
            "msg":"hello world",
        })
    })
    r.Run()            //默认位于0.0.0.0:8080,可传入参数":3030";也可以绑定服务器
}

二 参数获取

2.1 get请求参数获取方式:

c.Query("username")
c.QueryDefault("username","lisi")       //如果username为空,则赋值为lisi
name := c.Param("name")                  //路由地址为:/user/:name/:pass,获取参数
reqPara := c.Request.URL.Query()    //获取Get的所有参数

2.2 post请求参数获取

name := c.PostForm("name")
price := c.DefaultPostForm("price", "100")
reqPost = c.Request.PostForm      //获取 Post 所有参数

2.3 参数绑定

参数绑定利用反射机制,自动提取querystring,form表单,json,xml等参数到结构体中,可以极大提升开发效率。

package main
import (
    "net/http"
    "github.com/gin-gonic/gin"
    "fmt"
)
type User struct {
    Username string `form:"username" json:"username" binding:"required"`
    Password string `form:"password" json:"password" binding:"required"`
}

func login(c *gin.Context) {
    var user User
    fmt.Println(c.PostForm("username"))
    fmt.Println(c.PostForm("password"))
    err := c.ShouldBind(&user)
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{
            "error": err.Error()
        })
    }

    c.JSON(http.StatusOK, gin.H{
        "username": user.Username,
        "password": user.Password,
    })
}

func main() {
    router := gin.Default()
    router.POST("/login", login)
    router.Run(":3000")
}

三 静态文件

静态化当前目录下static文件夹:

    router := gin.Default()
    router.Static("/static", "./static")
    router.Run(":3000")

注意:同样推荐使用go build,不要使用开发工具的run功能。

四 结果返回

4.1 返回JSON

c.JSON(200,gin.H{"msg":"OK"})
c.JSON(200,结构体)

4.2 返回模板

    router.LoadHTMLGlob("templates/**/*")
    router.GET("/test/index", func(c *gin.Context){
        c.HTML(http.StatusOK, "test/index.tmpl", gin.H{
            "msg": "test",
        })
    })

模板文件:index.tmpl

{{define "test/index.tmpl"}}
<html>

    <head>
    </head>

    <body>

        test...

        {{.}}
        -----
        {{.msg}}

    </body>

</html>

{{end}}

注意事项:不要使用编辑器的run功能,会出现路径错误,推荐使用命令build,项目路径分配如下:

gin-01.png

五 文件上传

5.1 单文件上传

 router.POST("/upload", func (c *gin.Context) {
    file, err := c.FormFile("file")
    if (err != nil) {
        c.JSON(http.StatusInternalServerError, gin.H{
            "msg": err.Error(),
        })
        return
    }
    dst := fmt.Sprintf("/uploads/&s", file.Filename)
    c.SavaeUpLoadedFile(file, dst)
    c.JSON(http.StatusOK, gin.H{
        "msg":"ok",
    })
 })

5.2 多文件上传

 router.POST("/upload", func(c *gin.Context) {
        // 多文件
        form, _ := c.MultipartForm()
        files := form.File["upload[]"]

        for _, file := range files {
            log.Println(file.Filename)

            // 上传文件到指定的路径
            // c.SaveUploadedFile(file, dst)
        }
        c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
    })

一 路由分组

访问路径是:/user/login/user/signin

package main

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

func login(c *gin.Context) {
    c.JSON(300, gin.H{
        "msg": "login",
    })
}

func logout(c *gin.Context) {
    c.JSON(300, gin.H{
        "msg": "logout",
    })
}

func main() {

    router := gin.Default()

    user := router.Group("/user")
    {
        user.GET("/login", login)
        user.GET("/logout", logout)
    }

    router.Run(":3000")
}

二 路由设计

2.0 项目结构

笔者自己的路由设计,仅供参考:

项目结构如图:

gin-02.png

2.1 main.go

main.go:

package main

import (
    "Demo1/router"
)

func main() {
    r := router.InitRouter()
    _ = r.Run()
}

2.2 路由模块化核心 routes.go

routes.go:

package router

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

func InitRouter() *gin.Engine {

    r := gin.Default()

    // 路由模块化
    userRouter(r)
    orderRouter(r)

    return r
}

2.3 业务处理

userRouter.go示例:

package router

import (
    "github.com/gin-gonic/gin"
    "net/http"
)

func userRouter(r *gin.Engine) {

    r.GET("/user/login", userLogin)

}

func userLogin(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
        "code": 10001,
        "msg": "登录成功",
        "data": nil,
    })
}

gin配合单元测试

https://github.com/stretchr/testify/assert 是个很好的单元测试框架。

在上一节中配置了笔者自己项目的路由模块化思路,下面是配套的单元测试demo:

userRouter_test.go

package test

import (
    "Demo1/router"
    "github.com/stretchr/testify/assert"
    "net/http"
    "net/http/httptest"
    "testing"
)

func TestUserRouter_userLogin(t *testing.T) {
    r := router.InitRouter()
    w := httptest.NewRecorder()
    req, _ := http.NewRequest(http.MethodGet, "/user/login", nil)
    r.ServeHTTP(w, req)
    assert.Equal(t, http.StatusOK, w.Code)
    assert.Equal(t, `{"code":10001,"data":null,"msg":"登录成功"}`, w.Body.String())
}

一 Gin中间件

1.1 中间件的概念

gin框架允许在处理请求时,加入用户自己的钩子函数,该钩子函数即中间件。他的作用与Java中的拦截器,Node中的中间件相似。

中间件需要返回gin.HandlerFunc函数,多个中间件通过Next函数来依次执行。

1.2 入门使用案例

现在设计一个中间件,在每次路由函数执行前打印一句话,在上一节的项目基础上新建middleware文件夹,新建一个中间件文件MyFmt.go

package middleware

import (
    "fmt"
    "github.com/gin-gonic/gin"
)

// 定义一个中间件
func MyFMT() gin.HandlerFunc {
    return func(c *gin.Context) {
        host := c.Request.Host
        fmt.Printf("Before: %s\n",host)
        c.Next()
        fmt.Println("Next: ...")
    }
}

在路由函数中使用中间件:

r.GET("/user/login", middleware.MyFMT(),  userLogin)

打印结果:

Before: localhost:8080
Next: ...
[GIN] 2019/07/28 - 16:28:16 | 200 |      266.33µs |             ::1 | GET      /user/login

1.2 中间件的详细使用方式

全局中间件:直接使用 gin.Engine结构体的Use()方法,中间件将会在项目的全局起作用。

func InitRouter() *gin.Engine {

    r := gin.Default()

    // 全局中间件
    r.Use(middleware.MyFMT())

    // 路由模块化
    userRouter(r)
    orderRouter(r)

    return r
}

路由分组中使用中间件:

router := gin.New()
user := router.Group("user", gin.Logger(),gin.Recovery())
{
    user.GET("info", func(context *gin.Context) {

    })
    user.GET("article", func(context *gin.Context) {

    })
}

单个路由使用中间件(支持多个中间件的使用):

router := gin.New()
router.GET("/test",gin.Recovery(),gin.Logger(),func(c *gin.Context){
    c.JSON(200,"test")
})

1.3 内置中间件

Gin也内置了一些中间件,可以直接使用:

func BasicAuth(accounts Accounts) HandlerFunc
func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc
func Bind(val interface{}) HandlerFunc //拦截请求参数并进行绑定
func ErrorLogger() HandlerFunc       //错误日志处理
func ErrorLoggerT(typ ErrorType) HandlerFunc //自定义类型的错误日志处理
func Logger() HandlerFunc //日志记录
func LoggerWithConfig(conf LoggerConfig) HandlerFunc
func LoggerWithFormatter(f LogFormatter) HandlerFunc
func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc
func Recovery() HandlerFunc
func RecoveryWithWriter(out io.Writer) HandlerFunc
func WrapF(f http.HandlerFunc) HandlerFunc //将http.HandlerFunc包装成中间件
func WrapH(h http.Handler) HandlerFunc //将http.Handler包装成中间件

二 请求的拦截与后置

中间件的最大作用就是拦截过滤请求,比如我们有些请求需要用户登录或者需要特定权限才能访问,这时候便可以中间件中做过滤拦截。

下面三个方法中断请求后,直接返回200,但响应的body中不会有数据:

func (c *Context) Abort()
func (c *Context) AbortWithError(code int, err error) *Error
func (c *Context) AbortWithStatus(code int)
func (c *Context) AbortWithStatusJSON(code int, jsonObj interface{})        // 中断后可以返回json数据

如果在中间件中调用gin.Context的Next()方法,则可以请求到达并完成业务处理后,再经过中间件后置拦截处理:

func MyMiddleware(c *gin.Context){
    //请求前
    c.Next()
    //请求后
}

一 gin.Engine

Engine是框架的入口,是gin框架的核心,通过Engine对象来定义服务路由信息、组装插件、运行服务。不过Engine的本质只是对内置HTTP服务的包装。

gin.Default() 函数会生成一个默认的 Engine 对象,包含2个默认常用插件

  • Logger:用于输出请求日志
  • Recovery:用于确保单个请求发生 panic 时记录异常堆栈日志,输出统一的错误响应。
func Default() *Engine {
    engine := New()
    engine.Use(Logger(), Recovery())
    return engine
}

二 gin的路由

2.1 路由树

在 Gin 框架中,路由规则被分成了最多 9 棵前缀树,每一个 HTTP Method对应一棵 前缀树 ,树的节点按照 URL 中的 / 符号进行层级划分,URL 支持 :name 形式的名称匹配,还支持 *subpath 形式的路径通配符:

// 匹配单节点 named
pattern = /book/:id
match /book/123
nomatch /book/123/10
nomatch /book/

// 匹配子节点 catchAll mode
/book/*subpath
match /book/
match /book/123
match /book/123/10

如图所示:

gin-03.jpeg

每个节点都会挂接若干请求处理函数构成一个请求处理链 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)

2.2 路由组

RouterGroup 是对路由树的包装,所有的路由规则最终都是由它来进行管理。Engine 结构体继承了 RouterGroup ,所以 Engine 直接具备了 RouterGroup 所有的路由管理功能,同时 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 方法就可以让分组下面再挂分组,依次类推。

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

2.4 HTTPS

Gin 不支持 HTTPS,官方建议是使用 Nginx 来转发 HTTPS 请求到 Gin。

三 gin.Context

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

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

gin go

0 Comments

message
沪ICP备2024072411号 © 2022 SinceNow.net - GitHub
Login