GO学习笔记(20) - 第三方框架-gin框架

144 阅读4分钟

目录

  • 简介
  • 生态框架
  • 安装配置
  • 入门指南

简介

其实对于golang而言,web框架的依赖要远比Python,Java之类的要小。自身的net/http足够简单,性能也非常不错。框架更像是一些常用函数或者工具的集合。借助框架开发,不仅可以省去很多常用的封装带来的时间,也有助于团队的编码风格和形成规范。

Gin 是一个基于 Go 语言编写的 Web 框架,比近似框架 martini 拥有更好的性能,借助高性能的 httprouter,速度提升了近 40 倍。详细介绍

gin框架应用

  • gorush:Go 编写的通知推送服务器。
  • fnproject:容器原生,云 serverless 平台。
  • photoprism:基于 Go 和 Google TensorFlow 实现的个人照片管理工具。
  • krakend:拥有中间件的超高性能 API 网关。
  • picfit:Go 编写的图像尺寸调整服务器。
  • gotify:基于 WebSocket 进行实时消息收发的简单服务器。
  • cds:企业级持续交付和 DevOps 自动化开源平台。

安装配置

  1. 添加环境变量
GO111MODULE  on
GOPROXY  https://goproxy.cn
GOROOT   是你安装go的路径
  1. 安装
go get -u github.com/gin-gonic/gin
  1. 高性能日志库zap安装
go get -u go.uber.org/zap

基础编程

  • 启动默认引擎(端口8080)
  • middleware应用
  • context的使用
package main

import (
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
	"math/rand"
	"time"
)

const keyRequestId = "requestId"

func main() {
	//默认服务端引擎
	//http://localhost:8080/ping
	//自动带有日志模块
	r := gin.Default()

	log,err := zap.NewProduction()
	if err != nil{
		panic(err)
	}

	//使用middleware,接入zap日志模块
	r.Use(func(c *gin.Context) {
		//path,response code,log latency,
		start := time.Now()
		c.Next()

		log.Info("incoming request",
			zap.String("path",c.Request.URL.Path),
			zap.Int("status",c.Writer.Status()),
			zap.Duration("elapsed", time.Now().Sub(start)),

		)
	},
	//另外一个use
	//入口,增加requestId
	func(c *gin.Context) {
		c.Set(keyRequestId,rand.Int())
		c.Next()
	})
	r.GET("/ping", func(c *gin.Context) {
		h := gin.H{"message":"pong",}
		if rid,exist := c.Get(keyRequestId); exist{
			h[keyRequestId]=rid
		}
		c.JSON(200,h)
	})

	r.GET("/hello", func(c *gin.Context) {
		c.String(200,"hello")
	})
        //默认8080端口
	r.Run()
}

restful路由

gin的路由来自httprouter库。除“不支持路由正则表达式"之外,httprouter具有的功能,gin也具有:

func main(){
    router := gin.Default()
    
    router.GET("/user/:name", func(c *gin.Context) {
        name := c.Param("name")
        c.String(http.StatusOK, "Hello %s", name)
    })
}
  • 冒号 :

冒号:加上一个参数名组成路由参数。可以使用c.Params的方法读取其值。这个值必须是字串string。诸如/user/rsj217,和user/hello都可以匹配,而/user//user/rsj217/不会被匹配。

下例中name代表":"的变量

func main(){
    router := gin.Default()
    
    router.GET("/user/:name", func(c *gin.Context) {
        name := c.Param("name")
        c.String(http.StatusOK, "Hello %s", name)
    })
}
  • 星号:* 除了:,gin还提供了*号处理参数,*号能匹配的规则就更多。

下例中action代表"*" 的变量

func main(){
    router := gin.Default()
    
    router.GET("/user/:name/*action", func(c *gin.Context) {
        name := c.Param("name")
        action := c.Param("action")
        message := name + " is " + action
        c.String(http.StatusOK, message)
    })
}
$  curl http://127.0.0.1:8000/user/carmen/
carmen is /%          
--                                                         
$  curl http://127.0.0.1:8000/user/carmen/中国
carmen is /中国%

文件上传

  • 上传单个文件
func main(){
    router := gin.Default()
    
    router.POST("/upload", func(c *gin.Context) {
        name := c.PostForm("name")
        fmt.Println(name)
        file, header, err := c.Request.FormFile("upload")
        if err != nil {
            c.String(http.StatusBadRequest, "Bad request")
            return
        }
        filename := header.Filename

        fmt.Println(file, err, filename)

        out, err := os.Create(filename)
        if err != nil {
            log.Fatal(err)
        }
        defer out.Close()
        _, err = io.Copy(out, file)
        if err != nil {
            log.Fatal(err)
        }
        c.String(http.StatusCreated, "upload successful")
    })
    router.Run(":8000")
}
  • 上传多个文件
router.POST("/multi/upload", func(c *gin.Context) {
        err := c.Request.ParseMultipartForm(200000)
        if err != nil {
            log.Fatal(err)
        }

        formdata := c.Request.MultipartForm 

        files := formdata.File["upload"] 
        for i, _ := range files { /
            file, err := files[i].Open()
            defer file.Close()
            if err != nil {
                log.Fatal(err)
            }

            out, err := os.Create(files[i].Filename)

            defer out.Close()

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

            _, err = io.Copy(out, file)

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

            c.String(http.StatusCreated, "upload successful")

        }

    })

表单上传与gin的render模板

  • 表单如下:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>upload</title>
</head>
<body>
<h3>Single Upload</h3>
<form action="/upload", method="post" enctype="multipart/form-data">
    <input type="text" value="hello gin" />
    <input type="file" name="upload" />
    <input type="submit" value="upload" />
</form>


<h3>Multi Upload</h3>
<form action="/multi/upload", method="post" enctype="multipart/form-data">
    <input type="text" value="hello gin" />
    <input type="file" name="upload" />
    <input type="file" name="upload" />
    <input type="submit" value="upload" />
</form>

</body>
</html>
  • 后端代码
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,
        })

    })

}

middleware中间件

  • golang的net/http设计的一大特点就是特别容易构建中间件。gin也提供了类似的中间件。
  • 中间件分为全局中间件,单个路由中间件和群组中间件。
  • 对于分组路由,嵌套使用中间件,可以限定中间件的作用范围。
  • 需要注意的是中间件只对注册过的路由函数起作用。
中间件实例
  • 鉴权
    router.GET("/auth/signin", func(c *gin.Context) {
        cookie := &http.Cookie{
            Name:     "session_id",
            Value:    "123",
            Path:     "/",
            HttpOnly: true,
        }
        http.SetCookie(c.Writer, cookie)
        c.String(http.StatusOK, "Login successful")
    })

    router.GET("/home", AuthMiddleWare(), func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"data": "home"})
    })

登录函数会设置一个session_id的cookie,注意这里需要指定path为/,不然gin会自动设置cookie的path为/auth,一个特别奇怪的问题。/home的逻辑很简单,使用中间件AuthMiddleWare注册之后,将会先执行AuthMiddleWare的逻辑,然后才到/home的逻辑。

func AuthMiddleWare() gin.HandlerFunc {
    return func(c *gin.Context) {
        if cookie, err := c.Request.Cookie("session_id"); err == nil {
            value := cookie.Value
            fmt.Println(value)
            if value == "123" {
                c.Next()
                return
            }
        }
        c.JSON(http.StatusUnauthorized, gin.H{
            "error": "Unauthorized",
        })
        c.Abort()
        return
    }
}