欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Gin框架介绍及使用

程序员文章站 2022-03-26 12:14:53
Gin框架介绍及使用 是一个用Go语言编写的web框架。它是一个类似于 但拥有更好性能的API框架, 由于使用了 ,速度提高了近40倍。 如果你是性能和高效的追求者, 你会爱上 。 Gin框架介绍 Go世界里最流行的Web框架, "Github" 上有 star。 基于 "httprouter" 开 ......

gin框架介绍及使用

gin是一个用go语言编写的web框架。它是一个类似于martini但拥有更好性能的api框架, 由于使用了httprouter,速度提高了近40倍。 如果你是性能和高效的追求者, 你会爱上gin

gin框架介绍

go世界里最流行的web框架,github上有24k+star。 基于开发的web框架。 齐全,简单易用的轻量级框架。

gin框架安装与使用

安装

下载并安装gin:

go get -u github.com/gin-gonic/gin

第一个gin示例:

package main

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

func main() {
    // 创建一个默认的路由引擎
    r := gin.default()
    // get:请求方式;/hello:请求的路径
    // 当客户端以get方法请求/hello路径时,会执行后面的匿名函数
    r.get("/hello", func(c *gin.context) {
        // c.json:返回json格式的数据
        c.json(200, gin.h{
            "message": "hello world!",
        })
    })
    // 启动http服务,默认在0.0.0.0:8080启动服务
    r.run()
}

将上面的代码保存并编译执行,然后使用浏览器打开127.0.0.1:8080/hello就能看到一串json字符串。

restful api

rest与技术无关,代表的是一种软件架构风格,rest是representational state transfer的简称,中文翻译为“表征状态转移”或“表现层状态转化”。

推荐阅读阮一峰 理解restful架构

简单来说,rest的含义就是客户端与web服务器之间进行交互的时候,使用http协议中的4个请求方法代表不同的动作。

  • get用来获取资源
  • post用来新建资源
  • put用来更新资源
  • delete用来删除资源。

只要api程序遵循了rest风格,那就可以称其为restful api。目前在前后端分离的架构中,前后端基本都是通过restful api来进行交互。

例如,我们现在要编写一个管理书籍的系统,我们可以查询对一本书进行查询、创建、更新和删除等操作,我们在编写程序的时候就要设计客户端浏览器与我们web服务端交互的方式和路径。按照经验我们通常会设计成如下模式:

请求方法 url 含义
get /book 查询书籍信息
post /create_book 创建书籍记录
post /update_book 更新书籍信息
post /delete_book 删除书籍信息

同样的需求我们按照restful api设计如下:

请求方法 url 含义
get /book 查询书籍信息
post /book 创建书籍记录
put /book 更新书籍信息
delete /book 删除书籍信息

gin框架支持开发restful api的开发。

func main() {
    r := gin.default()
    r.get("/book", func(c *gin.context) {
        c.json(200, gin.h{
            "message": "get",
        })
    })

    r.post("/book", func(c *gin.context) {
        c.json(200, gin.h{
            "message": "post",
        })
    })

    r.put("/book", func(c *gin.context) {
        c.json(200, gin.h{
            "message": "put",
        })
    })

    r.delete("/book", func(c *gin.context) {
        c.json(200, gin.h{
            "message": "delete",
        })
    })
}

开发restful api的时候我们通常使用postman来作为客户端的测试工具。

gin渲染

html渲染

我们首先定义一个存放模板文件的templates文件夹,然后在其内部按照业务分别定义一个posts文件夹和一个users文件夹。 posts/index.html文件的内容如下:

{{define "posts/index.html"}}
<!doctype html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
    <title>posts/index</title>
</head>
<body>

    {{.title}}

</body>
</html>
{{end}}

users/index.html文件的内容如下:

{{define "users/index.html"}}
<!doctype html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="x-ua-compatible" content="ie=edge">
    <title>users/index</title>
</head>

<body>
    {{.title}}
</body>
</html>
{{end}}

gin框架中使用loadhtmlglob()或者loadhtmlfiles()方法进行html模板渲染。

func main() {
    r := gin.default()
    r.loadhtmlglob("templates/**/*")
    //r.loadhtmlfiles("templates/posts/index.html", "templates/users/index.html")
    r.get("/posts/index", func(c *gin.context) {
        c.html(http.statusok, "posts/index.html", gin.h{
            "title": "posts/index",
        })
    })

    r.get("users/index", func(c *gin.context) {
        c.html(http.statusok, "users/index.html", gin.h{
            "title": "users/index",
        })
    })

    r.run(":8080")
}

静态文件处理

当我们渲染的html文件中引用了静态文件时,我们只需要按照以下方式在渲染页面前调用gin.static方法即可。

func main() {
    r := gin.default()
    r.static("/static", "./static")
    r.loadhtmlglob("templates/**/*")
   ...
    r.run(":8080")
}

补充文件路径处理

关于模板文件和静态文件的路径,我们需要根据公司/项目的要求进行设置。可以使用下面的函数获取当前执行程序的路径。

func getcurrentpath() string {
    if ex, err := os.executable(); err == nil {
        return filepath.dir(ex)
    }
    return "./"
}

json渲染

func main() {
    r := gin.default()

    // gin.h 是map[string]interface{}的缩写
    r.get("/somejson", func(c *gin.context) {
        // 方式一:自己拼接json
        c.json(http.statusok, gin.h{"message": "hello world!"})
    })
    r.get("/morejson", func(c *gin.context) {
        // 方法二:使用结构体
        var msg struct {
            name    string `json:"user"`
            message string
            age     int
        }
        msg.name = "小王子"
        msg.message = "hello world!"
        msg.age = 18
        c.json(http.statusok, msg)
    })
    r.run(":8080")
}

xml渲染

注意需要使用具名的结构体类型。

func main() {
    r := gin.default()
    // gin.h 是map[string]interface{}的缩写
    r.get("/somexml", func(c *gin.context) {
        // 方式一:自己拼接json
        c.xml(http.statusok, gin.h{"message": "hello world!"})
    })
    r.get("/morexml", func(c *gin.context) {
        // 方法二:使用结构体
        type messagerecord struct {
            name    string
            message string
            age     int
        }
        var msg messagerecord
        msg.name = "小王子"
        msg.message = "hello world!"
        msg.age = 18
        c.xml(http.statusok, msg)
    })
    r.run(":8080")
}

ymal渲染

r.get("/someyaml", func(c *gin.context) {
    c.yaml(http.statusok, gin.h{"message": "ok", "status": http.statusok})
})

protobuf渲染

r.get("/someprotobuf", func(c *gin.context) {
    reps := []int64{int64(1), int64(2)}
    label := "test"
    // protobuf 的具体定义写在 testdata/protoexample 文件中。
    data := &protoexample.test{
        label: &label,
        reps:  reps,
    }
    // 请注意,数据在响应中变为二进制数据
    // 将输出被 protoexample.test protobuf 序列化了的数据
    c.protobuf(http.statusok, data)
})

获取参数

获取querystring参数

querystring指的是url中?后面携带的参数,例如:/user/search?username=小王子&address=沙河。 获取请求的querystring参数的方法如下:

func main() {
    //default返回一个默认的路由引擎
    r := gin.default()
    r.get("/user/search", func(c *gin.context) {
        username := c.defaultquery("username", "小王子")
        //username := c.query("username")
        address := c.query("address")
        //输出json结果给调用方
        c.json(http.statusok, gin.h{
            "message":  "ok",
            "username": username,
            "address":  address,
        })
    })

    r.run()
}

获取form参数

请求的数据通过form表单来提交,例如向/user/search发送一个post请求,获取请求数据的方式如下:

func main() {
    //default返回一个默认的路由引擎
    r := gin.default()
    r.post("/user/search", func(c *gin.context) {
        // defaultpostform取不到值时会返回指定的默认值
        //username := c.defaultpostform("username", "小王子")
        username := c.postform("username")
        address := c.postform("address")
        //输出json结果给调用方
        c.json(http.statusok, gin.h{
            "message":  "ok",
            "username": username,
            "address":  address,
        })
    })
    r.run(":8080")
}

获取path参数

请求的参数通过url路径传递,例如:/user/search/小王子/沙河。 获取请求url路径中的参数的方式如下。

func main() {
    //default返回一个默认的路由引擎
    r := gin.default()
    r.get("/user/search/:username/:address", func(c *gin.context) {
        username := c.param("username")
        address := c.param("address")
        //输出json结果给调用方
        c.json(http.statusok, gin.h{
            "message":  "ok",
            "username": username,
            "address":  address,
        })
    })

    r.run(":8080")
}

参数绑定

为了能够更方便的获取请求相关参数,提高开发效率,我们可以基于请求的content-type识别请求数据类型并利用反射机制自动提取请求中querystring、form表单、json、xml等参数到结构体中。

// binding from json
type login struct {
    user     string `form:"user" json:"user" binding:"required"`
    password string `form:"password" json:"password" binding:"required"`
}

func main() {
    router := gin.default()

    // 绑定json的示例 ({"user": "q1mi", "password": "123456"})
    router.post("/loginjson", func(c *gin.context) {
        var login login

        if err := c.shouldbindjson(&login); err == nil {
            fmt.printf("login info:%#v\n", login)
            c.json(http.statusok, gin.h{
                "user":     login.user,
                "password": login.password,
            })
        } else {
            c.json(http.statusbadrequest, gin.h{"error": err.error()})
        }
    })

    // 绑定form表单示例 (user=q1mi&password=123456)
    router.post("/loginform", func(c *gin.context) {
        var login login
        // shouldbind()会根据请求的content-type自行选择绑定器
        if err := c.shouldbind(&login); err == nil {
            c.json(http.statusok, gin.h{
                "user":     login.user,
                "password": login.password,
            })
        } else {
            c.json(http.statusbadrequest, gin.h{"error": err.error()})
        }
    })

    // 绑定querystring示例 (user=q1mi&password=123456)
    router.get("/loginform", func(c *gin.context) {
        var login login
        // shouldbind()会根据请求的content-type自行选择绑定器
        if err := c.shouldbind(&login); err == nil {
            c.json(http.statusok, gin.h{
                "user":     login.user,
                "password": login.password,
            })
        } else {
            c.json(http.statusbadrequest, gin.h{"error": err.error()})
        }
    })

    // listen and serve on 0.0.0.0:8080
    router.run(":8080")
}

文件上传

单个文件上传

func main() {
    router := gin.default()
    // 处理multipart forms提交文件时默认的内存限制是32 mib
    // 可以通过下面的方式修改
    // router.maxmultipartmemory = 8 << 20  // 8 mib
    router.post("/upload", func(c *gin.context) {
        // 单个文件
        file, err := c.formfile("file")
        if err != nil {
            c.json(http.statusinternalservererror, gin.h{
                "message": err.error(),
            })
            return
        }

        log.println(file.filename)
        dst := fmt.sprintf("c:/tmp/%s", file.filename)
        // 上传文件到指定的目录
        c.saveuploadedfile(file, dst)
        c.json(http.statusok, gin.h{
            "message": fmt.sprintf("'%s' uploaded!", file.filename),
        })
    })
    router.run()
}

多个文件上传

func main() {
    router := gin.default()
    // 处理multipart forms提交文件时默认的内存限制是32 mib
    // 可以通过下面的方式修改
    // router.maxmultipartmemory = 8 << 20  // 8 mib
    router.post("/upload", func(c *gin.context) {
        // multipart form
        form, _ := c.multipartform()
        files := form.file["file"]

        for index, file := range files {
            log.println(file.filename)
            dst := fmt.sprintf("c:/tmp/%s_%d", file.filename, index)
            // 上传文件到指定的目录
            c.saveuploadedfile(file, dst)
        }
        c.json(http.statusok, gin.h{
            "message": fmt.sprintf("%d files uploaded!", len(files)),
        })
    })
    router.run()
}

gin中间件

gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录校验、日志打印、耗时统计等。

gin中的中间件必须是一个gin.handlerfunc类型。例如我们像下面的代码一样定义一个中间件。

// statcost 是一个统计耗时请求耗时的中间件
func statcost() gin.handlerfunc {
    return func(c *gin.context) {
        start := time.now()
        c.set("name", "小王子")
        // 执行其他中间件
        c.next()
        // 计算耗时
        cost := time.since(start)
        log.println(cost)
    }
}

然后注册中间件的时候,可以在全局注册。

func main() {
    // 新建一个没有任何默认中间件的路由
    r := gin.new()
    // 注册一个全局中间件
    r.use(statcost())
    
    r.get("/test", func(c *gin.context) {
        name := c.mustget("name").(string)
        log.println(name)
        c.json(http.statusok, gin.h{
            "message": "hello world!",
        })
    })
    r.run()
}

也可以给某个路由单独注册中间件。

// 给/test2路由单独注册中间件(可注册多个)
    r.get("/test2", statcost(), func(c *gin.context) {
        name := c.mustget("name").(string)
        log.println(name)
        c.json(http.statusok, gin.h{
            "message": "hello world!",
        })
    })

重定向

http重定向

http 重定向很容易。 内部、外部重定向均支持。

r.get("/test", func(c *gin.context) {
    c.redirect(http.statusmovedpermanently, "http://www.google.com/")
})

路由重定向

路由重定向,使用handlecontext

r.get("/test", func(c *gin.context) {
    // 指定重定向的url
    c.request.url.path = "/test2"
    r.handlecontext(c)
})
r.get("/test2", func(c *gin.context) {
    c.json(http.statusok, gin.h{"hello": "world"})
})

gin路由

普通路由

r.get("/index", func(c *gin.context) {...})
r.get("/login", func(c *gin.context) {...})
r.post("/login", func(c *gin.context) {...})

此外,还有一个可以匹配所有请求方法的any方法如下:

r.any("/test", func(c *gin.context) {...})

为没有配置处理函数的路由添加处理程序。默认情况下它返回404代码。

r.noroute(func(c *gin.context) {
        c.html(http.statusnotfound, "views/404.html", nil)
    })

路由组

我们可以将拥有共同url前缀的路由划分为一个路由组。

func main() {
    r := gin.default()
    usergroup := r.group("/user")
    {
        usergroup.get("/index", func(c *gin.context) {...})
        usergroup.get("/login", func(c *gin.context) {...})
        usergroup.post("/login", func(c *gin.context) {...})

    }
    shopgroup := r.group("/shop")
    {
        shopgroup.get("/index", func(c *gin.context) {...})
        shopgroup.get("/cart", func(c *gin.context) {...})
        shopgroup.post("/checkout", func(c *gin.context) {...})
    }
    r.run()
}

通常我们将路由分组用在划分业务逻辑或划分api版本时。

路由原理

gin框架中的路由使用的是这个库。

其基本原理就是构造一个路由地址的前缀树。