首先在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方式接收并反馈。
运行项目,客户端发送请求,得到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种方式:
新建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$
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()
}
获取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$
我们可以通过在结构体的标签中标记验证的指标,将请求中的参数映射到结构体中的变量时,可以使用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实例时一般使用如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()
}
运行项目后客户端访问,他会在输出台中输出日志。
也可以将日志输出到单独文件中:
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后将直接宕机。加上该中间件后,错误信息将在日志中输出:
这里自定义一个引擎中间件,参考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方法,它将根据上下文的超时时间进行服务器关停,客户端仍可收到反馈消息。
测试运行效果如下:
在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$