Gin框架学习笔记

Gin基础知识

 

安装gin以及快速开始

首先在GOPATH目录src下创建所需的项目文件

MacdeMacBook-pro-3:~ mac$ mkdir -p /Users/mac/go/src/github.com/Threadalive/gin_test_project
MacdeMacBook-pro-3:~ mac$ cd $_

这里使用mod进行项目管理,修改GO111MODULE开启mod模式,执行初始化init,并使用go get 命令下载gin项目:

MacdeMacBook-pro-3:gin_test_project mac$ export GO111MODULE=on
MacdeMacBook-pro-3:gin_test_project mac$ go mod init
go: creating new go.mod: module github.com/Threadalive/gin_test_project
MacdeMacBook-pro-3:gin_test_project mac$ go get -v github.com/gin-gonic/[email protected]

若访问github下载失败 ,可修改代理配置,使用七牛云或阿里云镜像仓库:

MacdeMacBook-pro-3:gin_test_project mac$ go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct

下载成功后目录中出现go.mod以及go.sum,这两个文件记录项目的依赖即模块信息。

使用Goland打开项目,即可快速创建简单的helloworld 项目进行测试,使用Get方式接收并反馈。

Gin框架学习笔记_第1张图片

运行项目,客户端发送请求,得到json格式反馈:

MacdeMacBook-pro-3:gin_test_project mac$ curl -X GET "http://localhost:8080/ping"
{"mseeage":"pong"}
MacdeMacBook-pro-3:gin_test_project mac$ 

请求路由

  • 多种请求

gin框架可接收http请求的各类方法,包括GET、POST、DELETE等等。

可通过直接绑定方法或使用handle函数进行说明,如下:

func main() {
	r := gin.Default()
	r.GET("/get", func(c *gin.Context) {
		c.String(200,"get")
	})
	r.POST("/post", func(c *gin.Context) {
		c.String(200,"post")
	})
	r.Handle("DELETE","/delete", func(c *gin.Context) {
		c.String(200,"delete")
	})
	//接收任意类型请求方法
	r.Any("/any", func(context *gin.Context) {
		context.String(200,"any")
	})
	r.Run()
}

其中Any函数能够接收所有类型的请求。测试如下:

MacdeMacBook-pro-3:gin_curl -X GET "http://localhost:8080/get" 
get
MacdeMacBook-pro-3:gin_test_project mac$ curl -X GET "http://localhost:8080/"
get
MacdeMacBook-pro-3:gin_test_project mac$ curl -X POST "http://localhost:8080/post"
post
MacdeMacBook-pro-3:gin_test_project mac$ curl -X DELETE "http://localhost:8080/delete"
delete
MacdeMacBook-pro-3:gin_test_project mac$ curl -X DELETE "http://localhost:8080/any"
any
MacdeMacBook-pro-3:gin_test_project mac$ 

 

  • 绑定静态文件夹

gin中绑定静态文件夹有3种方式:

  1. static()
  2. staticFS()
  3. staticFile()

新建router_static包,放置静态资源包assets,static,测试代码如下:

package main

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

func main() {
	r := gin.Default()
	//绑定静态资源路径
	r.Static("/assets","./assets")
	//绑定静态文件系统
	r.StaticFS("/static",http.Dir("static"))
	//绑定单个静态文件
	r.StaticFile("/favicon.ico","./favicon.ico")

	r.Run()
}

客户端访问结果:

MacdeMacBook-pro-3:gin_test_project mac$ curl "http://localhost:8080/assets/a.html"



    
    a


body

MacdeMacBook-pro-3:gin_test_project mac$ 
//测试static下的b.html
MacdeMacBook-pro-3:gin_test_project mac$ curl "http://localhost:8080/static/b.html"



    
    Title


b_content

MacdeMacBook-pro-3:gin_test_project mac$ 
  • 参数作为URL

gin中可以将参数直接作为URL的一部分在请求中进行传递,并在回调函数中通过context.Parm("key")进行获取,实例如下:

package main

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

func main() {
	r := gin.Default()
	r.GET("/:name/:id", func(c *gin.Context) {
		c.JSON(200,gin.H{
			"name":c.Param("name"),
			"id":c.Param("id"),
		})
	})
	r.Run()

}

 使用客户端访问效果如下:

MacdeMacBook-pro-3:gin_test_project mac$ curl -X GET "http://localhost:8080/dzx/007"
{"id":"007","name":"dzx"}
MacdeMacBook-pro-3:gin_test_project mac$ 
  • 泛绑定

泛绑定是指将不同的url路由到同一处理函数中,只需要使用星号作为路由前缀即可。如下,所有访问路由以/user开头的get请求都将被该方法接收。

package main

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

func main() {
	r := gin.Default()
	r.GET("/user/*action", func(c *gin.Context) {
		c.String(200,"hello world")
	})
	r.Run()
}

获取请求参数

  1. 获取GET请求参数

获取GET方法请求参数可直接使用context.Query("key"),或context.DefaultQuery("key","default_value"),区别在于是否带默认值。

测试如下:

package main

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

func main() {
	r := gin.Default()
	r.GET("/test", func(c *gin.Context) {
		firstName:=c.Query("first_name")
		lastName:=c.DefaultQuery("last_name","last_default_name")
		c.String(http.StatusOK,"%s%s",firstName,lastName)
	})
	r.Run()
}

客户端访问效果如下:

MacdeMacBook-pro-3:gin_test_project mac$ curl -X GET "http://localhost:8080/test?first_name=dong"
donglast_default_name
MacdeMacBook-pro-3:gin_test_project mac$  
MacdeMacBook-pro-3:gin_test_project mac$ curl -X GET "http://localhost:8080/test?first_name=dong&&last_name=zhenxing"
dongzhenxing
MacdeMacBook-pro-3:gin_test_project mac$ 

 

     2.获取POST请求参数

使用POST的方法与GET大体无异,使用context.PostForm("key"),或context.DefaultPostForm("key","default_value")将gin实例的方法改成POST即可。

    3.获取body值

要获取body中的值需要借助ioutil包中的readAll方法,该方法返回一个字节序列以及一个错误,演示如下:


func main() {
	r := gin.Default()
	r.POST("/test", func(c *gin.Context) {
		bodyByts,err := ioutil.ReadAll(c.Request.Body)
		if err != nil {
			c.String(http.StatusBadRequest,err.Error())
			c.Abort()
		}
		c.String(http.StatusOK,string(bodyByts))
	})
	r.Run()
}

 客户端访问如下:

MacdeMacBook-pro-3:gin_test_project mac$ curl -X POST "http://localhost:8080/test" -d '{"name":"dzx"}'
{"name":"dzx"}
MacdeMacBook-pro-3:gin_test_project mac$ 

在使用ioutil获取了body值后,无法再使用PostForm等方法获取参数,因为readAll函数以将body中值提出,尝试获取代码如下:

package main

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

func main() {
	r := gin.Default()
	r.POST("/test", func(c *gin.Context) {
		bodyByts,err := ioutil.ReadAll(c.Request.Body)
		if err != nil {
			c.String(http.StatusBadRequest,err.Error())
			c.Abort()
		}
		firstName := c.PostForm("first_name")
		lastName := c.DefaultPostForm("last_name","last_default_name")

		c.String(http.StatusOK,"%s,%s",firstName,lastName,string(bodyByts))
	})
	r.Run()
}

 客户端带参数访问结果:

MacdeMacBook-pro-3:gin_test_project mac$ curl -X POST "http://localhost:8080/test" -d 'first_name=dong&last_name=zhenxing'
,last_default_name%!(EXTRA string=first_name=dong&last_name=zhenxing)
MacdeMacBook-pro-3:gin_test_project mac$ 

要解决这个问题,需要将字节序列中的值再存回body中,如下:

//将字节序列中值存回body
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyByts))

客户端访问效果如下:

MacdeMacBook-pro-3:gin_test_project mac$ curl -X POST "http://localhost:8080/test" -d 'first_name=dong&last_name=zhenxing'
dong,zhenxing,first_name=dong&last_name=zhenxing
MacdeMacBook-pro-3:gin_test_project mac$ 

 

    4.获取参数绑定结构体

 要将请求中的参数绑定到程序中的结构体中,只需要使用shouldBind()方法,传入要绑定的结构体的地址即可,gin会自动根据参数类型和名称自动绑定对应的结构体变量,演示如下:

package main

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

type Person struct {
	Name string `form:"name"`
	Address string `form:"address"`
	Birthday time.Time `form:"birthday" time_format:"2006-01-02"`
}
func main()  {
	r := gin.Default()
	r.GET("/testing", testing)
	r.POST("/testing", testing)
	r.Run()
}

func testing(c *gin.Context) {
	var person Person
	if err := c.ShouldBind(&person);err == nil{
		c.String(200,"%v",person)
	}else {
		c.String(200,"person bind error:%v",err)
	}
}

在结构体中可以使用标签标记变量对应请求的名称 。

MacdeMacBook-pro-3:gin_test_project mac$ curl -X GET 'http://localhost:8080/testing?name=dzx&address=wuhan&birthday=2020-07-01'
{dzx wuhan 2020-07-01 00:00:00 +0800 CST}
MacdeMacBook-pro-3:gin_test_project mac$ 
MacdeMacBook-pro-3:gin_test_project mac$ curl -X POST 'http://localhost:8080/testing' -d 'name=dzx&address=wuhan&birthday=2020-07-01'
{dzx wuhan 2020-07-01 00:00:00 +0800 CST}
MacdeMacBook-pro-3:gin_test_project mac$ 

验证请求参数

  1. 结构体验证

我们可以通过在结构体的标签中标记验证的指标,将请求中的参数映射到结构体中的变量时,可以使用validator通过tag中的标记进行参数校验,validator通过反射获取tag中的验证规则,对参数经校验,示例如下:

package main

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

type Person struct {
	//非空,数字大于10
	Age     string    `form:"age" binding:"required,gt=10"`
	Name string `form:"name" binding:"required"`
	Address  string    `form:"address" binding:"required"`
}

func main() {
	r := gin.Default()
	r.GET("/testing", func (c *gin.Context) {
		var person Person
		if err := c.ShouldBind(&person); err != nil {
			c.String(500, "%v", err)
			return
		} else {
			c.String(200, "person:%v", person)
		}
	})
	r.Run()
}

 客户端尝试发送结果如下:

MacdeMacBook-pro-3:gin_test_project mac$ curl -X GET "localhost:8080/testing?name=dzx&address=wuhan"
Key: 'Person.Age' Error:Field validation for 'Age' failed on the 'required' tag
MacdeMacBook-pro-3:gin_test_project mac$ 

     2.自定义验证

    3.升级验证

 

中间件

 

  • 使用gin中间件

我们创建gin实例时一般使用如r := gin.Default()的方式,在默认方法中,如下:

engine := New()
engine.Use(Logger(), Recovery())

它使用了Logger与Recovery两种中间件,这里我们尝试只使用Logger中间件创建一个实例:

package main

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

func main() {
	r := gin.New()
	//单独使用Logger中间件创建引擎
	r.Use(gin.Logger())
	r.GET("/test", func(c *gin.Context) {
		name := c.DefaultQuery("name","default_name")
		c.String(200,"%s",name)
	})

	r.Run()
}

 运行项目后客户端访问,他会在输出台中输出日志。

Gin框架学习笔记_第2张图片

也可以将日志输出到单独文件中:

func main() {
	//创建日志文件
	f,_ := os.Create("gin.log")
	//修改默认输出器,输出到上面文件
	gin.DefaultWriter = io.MultiWriter(f)
	gin.DefaultErrorWriter = io.MultiWriter(f)

	r := gin.New()
	//单独使用Logger中间件创建引擎
	r.Use(gin.Logger())
	r.GET("/test", func(c *gin.Context) {
		name := c.DefaultQuery("name","default_name")
		c.String(200,"%s",name)
	})

	r.Run()
}

将日志以及错误都输出到上面文件中,运行访问后生成gin.log文件如下:

 

在默认的gin引擎实例中,还使用了Recovery中间件,这个中间件将帮助项目重panic中恢复运行,若确实该中间件,出发panic后将直接宕机。加上该中间件后,错误信息将在日志中输出:

Gin框架学习笔记_第3张图片

  • 自定义ip白名单中间件

这里自定义一个引擎中间件,参考Logger中间件,我们只要返回一个gin.HandlerFunc()即可,代码如下:

package main

import (
	"github.com/gin-gonic/gin"
)
//ip白名单中间件
func IpAuthMiddleWare() gin.HandlerFunc {
	return func(c *gin.Context) {
		ipList := []string{
            //试用127.0.0.1,
			"127.0.0.2",
			"localhost",
		}
		flag := false
		clientIp := c.ClientIP()
		for _,ip := range ipList{
			if ip == clientIp {
				flag = true
				break
			}
		}
		if !flag {
			c.String(401,"%s is not in ipList",clientIp)
			c.Abort()
		}
	}
}
func main() {
	r := gin.Default()
	//自定义IP白名单中间件,r.USE(IpAuthMiddleWare())
	r.Use(IpAuthMiddleWare())

	r.GET("/test", func(c *gin.Context) {
		c.String(200,"access ok")
	})
	r.Run()
}

测试运行结果如下:

MacdeMacBook-pro-3:gin_test_project mac$ curl -X GET "127.0.0.1:8080/test"
access ok
MacdeMacBook-pro-3:gin_test_project mac$ curl -X GET "127.0.0.1:8080/test"
127.0.0.1 is not in ipList
MacdeMacBook-pro-3:gin_test_project mac$ 

 其他补充

  • 优雅关停

上面我们运行服务器都是直接使用r.run()方法进行运行,关闭时将直接强退,这里我们使用捕获中断信号的方式,对服务器进行优雅的关停,代码如下:

package main

import (
	"context"
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"
)

func main(){
	r := gin.Default()
	r.GET("/test", func(c *gin.Context) {
		time.Sleep(10*time.Second)
		c.String(200,"hello test")
	})

	server := &http.Server{
		Addr: ":8085",
		Handler: r,
	}

	go func() {
		if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed{
			log.Fatalf("listen: %s\n",err)
		}
	}()

	quit := make(chan os.Signal)
	//捕获两类信号
	signal.Notify(quit,syscall.SIGINT,syscall.SIGTERM)
	//阻塞channel
	<-quit
	log.Println("shutdown server...")

	//创建超时的上下文
	ctx,cancel := context.WithTimeout(context.Background(),10*time.Second)
	defer cancel()

	//执行关闭服务器
	if err := server.Shutdown(ctx);err!=nil {
		log.Fatal("server shutdown",err)
	}

	log.Println("server exiting...")
}

我们使用 signal类型的channel进行信号获取与进程阻塞,捕获到退出信号后,首先打印 ”shutdown server...“,并创建请求超时的上文,使用该上下文调用server的shutdown方法,它将根据上下文的超时时间进行服务器关停,客户端仍可收到反馈消息。

测试运行效果如下:

Gin框架学习笔记_第4张图片

  • 模板渲染

在web设计中,模板引擎能够帮我们在页面中注入后台传输的值,在golang中有特定的获取后端值的语法,这里测试简单的取值法。创建html文件如下:


{{.title}} 111 222

在后端代码中载入前端模板,使用gin.H{}注入对应值:

package main

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

func main() {
	r := gin.Default()
	r.LoadHTMLGlob("template/*")
	r.GET("/index", func(c *gin.Context) {
		c.HTML(200,"index.html",gin.H{
			"title":"index.html",
		})
	})
	r.Run()
}

客户端访问结果如下:

MacdeMacBook-pro-3:gin_test_project mac$ curl -X GET 'localhost:8080/index'

index.html 111 222

MacdeMacBook-pro-3:gin_test_project mac$
  • 自动证书配置

 

你可能感兴趣的:(go语言学习)