[root@go ~]# cat /etc/profile|tail -5
# go enviroment
export GOROOT=/usr/local/go
export GOPATH=~/golib:~/goproject
export GOBIN=~/gobin
export PATH=$PATH:$GOROOT/bin:$GOBIN
一、安装Gin及快速开始
1.1 安装及环境配置
[root@go ~]# mkdir $GOPATH/src/github/jstang007/gin_test_project
[root@go ~]# cd $GOPATH/src/github/jstang007/gin_test_project
[root@go gin_test_project]# export GO111MODULE=on #开启go module环境变量
[root@go gin_test_project]# go mod init
[root@go gin_test_project]# go get -v github.com/gin-goniv/gin@v1.4
1.2 快速启动
[root@go gin_test_project]# mkdir start
[root@go gin_test_project]# touch start/main.go
[root@go gin_test_project]# vim start/main.go
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080
}
[root@go gin_test_project]# go run start/main.go
[root@go gin_test_project]# curl -X GET 127.0.0.1:8080/ping
pong
二、请求路由
2.1 设置多种请求类型
在项目目录下创建route_type/main.go文件,并编辑
package main
import "github.com/gin-gonic/gin"
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(c *gin.Context) { //支持get\post\del\put\alter
c.String(200, "any")
})
r.Run()
}
测试:
[root@go gin_test_project]# curl -X DELETE127.0.0.1:8080/delete
delete
[root@go gin_test_project]# curl -X PUT 127.0.0.1:8080/any
any
2.2 绑定静态文件夹
创建文件夹route_static,该文件夹树形结构:
- route_static
- accets
a.html
- static
b.html
favicon.ico
main.go
编辑main.go文件
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.Static("/accets", "./accets")
r.StaticFS("/static", http.Dir("static"))
r.StaticFile("/favicon.ico", "./favicon.ico") //设置单个静态文件
r.Run()
}
2.3 参数作为URI
创建目录route_uri,route_uri/main.go
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"), //value -key
"id": c.Param("id"), //末尾一定要由逗号
})
})
r.Run()
} //http://192.168.27.145:8080/golang/1
2.4 泛绑定
创建route_generic目录和route_generic/main.go文件
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()
} //http://192.168.27.145:8080/user/111
//http://192.168.27.145:8080/user/xxx
三、获取请求参数
3.1 获取GET的请求参数
创建param_get文件夹和param_get/main.go文件
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/test", func(c *gin.Context) {
firstName := c.Query("first_name") //key
lastName := c.DefaultQuery("last_name", "last_default_name") //key, 默认value
c.String(http.StatusOK, "%s, %s", firstName, lastName)
})
r.Run()
} //http://192.168.27.145:8080/test?first_name=xxx&last_name=yyy
//http://192.168.27.145:8080/test?first_name=xxx
3.2 获取POST的请求
创建param_post文件夹和param_post/main.go文件
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.POST("/test", func(c *gin.Context) {
firstName := c.DefaultPostForm("first_name","golang")//key, 默认value
lastName := c.PostFrom("last_name")
c.String(http.StatusOK, "%s, %s", firstName, lastName)
})
r.Run()
}//curl -X POST 127.0.0.1:8080/test?first_name=xxx
3.3 获取Body请求参数
package main
import (
"bytes"
"io/ioutil"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.POST("/test", func(c *gin.Context) {
bodyBytes, err := ioutil.ReadAll(c.Request.Body)
if err != nil {
c.String(http.StatusBadRequest, err.Error())
c.Abort()
}
//调用完read之后,PostFrom里数值被清空,需要重新写入到c.Request.Body
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
firstName := c.PostForm("first_name")
lastName := c.DefaultPostForm("last_name", "last_default_name")
c.String(http.StatusOK, "%s, %s, %s", firstName, lastName, string(bodyBytes))
})
r.Run()
} // curl -X POST 127.0.0.1:8080/test -d '{"name":"golang"}'
//curl -X POST "127.0.0.1:8080/test" -d 'first_name=xxx&last_name=yyy'
//xxx, yyy, first_name=xxx&last_name=yyy
3.4 获取参数bind绑定结构
package main
import (
"fmt"
"time"
"github.com/gin-gonic/gin"
)
//Person 人
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
//这里是根据请求content-type来做不同的binding操作
if err := c.ShouldBind(&person); err == nil {
fmt.Printf("====%v====", person)
c.String(200, "%v", person)
} else {
c.String(200, ":%v" , err.Error())
}
}
//curl -X GET "http://127.0.0.1:8080/testing?name=xxx&address=China&birthday=2000-04-25"
//{xxx China 2000-04-25 00:00:00 +0800 CST}
//curl -X POST 127.0.0.1:8080/testing -d "name=jack&address=China&birthday=2020-02-02"
//{jack China 2020-02-02 00:00:00 +0800 CST}
//curl -H "Content-type:application/json" -X POST 127.0.0.1:8080/testing -d '{"name":"rose","address":"US"}'
//{rose US}
四、验证请求参数
获取的参数是否合法,如何定义验证规则
4.1 结构体binding验证
package main
import "github.com/gin-gonic/gin"
//Person A
type Person struct {
Age int `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.Error())
c.Abort()
return
}
c.String(200, "%v", person)
})
r.Run()
}
// curl -X GET "http://127.0.0.1:8080/testing?age=11&name=knho&address=cn"
//{11 knho cn}
// curl -X GET "http://127.0.0.1:8080/testing?age=10&name=knho&address=cn"
//Key: 'Person.Age' Error:Field validation for 'Age' failed on the 'gt' tag{10 knho cn}
// curl -X GET "http://127.0.0.1:8080/testing?age=12&name=knho"
//Key: 'Person.Address' Error:Field validation for 'Address' failed on the 'required' tag
//https://godoc.org/gopkg.in/go-playground/validator.v8
4.2 自定义验证
package main
import (
"net/http"
"reflect"
"time"
"gopkg.in/go-playground/validator.v8"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
)
//Booking x
type Booking struct {
CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
}
func bookableDate(
v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
) bool {
if date, ok := field.Interface().(time.Time); ok {
today := time.Now()
if today.Unix() > date.Unix() {
return false
}
}
return true
}
func main() {
r := gin.Default()
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("bookabledate", bookableDate)
}
r.GET("/bookable", getBookable)
r.Run()
}
func getBookable(c *gin.Context) {
var b Booking
if err := c.ShouldBindWith(&b, binding.Query); err == nil {
c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
}
//1、当check_in是未来日期,并比check_out早时
// curl -X GET "127.0.0.1:8080/bookable?check_in=2020-07-01&check_out=2020-07-08"
// {"message":"Booking dates are valid!"}
//2、当check_in是未来日期,并比check_out晚时
//curl -X GET "127.0.0.1:8080/bookable?check_in=2020-07-09&check_out=2020-07-08"
// {"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"}
//3、当check_in是已过日期
//curl -X GET "127.0.0.1:8080/bookable?check_in=2020-05-09&check_out=2020-07-08"
// {"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}
4.3 支持多语言错误信息
package main
import (
"github.com/gin-gonic/gin"
"github.com/go-playground/locales/en"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"gopkg.in/go-playground/validator.v9"
en_translations "gopkg.in/go-playground/validator.v9/translations/en"
zh_translations "gopkg.in/go-playground/validator.v9/translations/zh"
)
// Person x
type Person struct {
Age int `form:"age" validate:"required,gt=10"`
Name string `form:"name" validate:"required"`
Address string `form:"address" validate:"required"`
}
// ut
var (
Uni *ut.UniversalTranslator
Validate *validator.Validate
)
//验证信息多语言化
func main() {
Validate = validator.New() //创建验证器
en := en.New()
zh := zh.New()
Uni = ut.New(zh, en) //创建翻译器
r := gin.Default()
r.GET("/test", startPage)
r.POST("/test", startPage)
r.Run()
}
func startPage(c *gin.Context) {
locale := c.DefaultQuery("locale", "zh") //获取uri中locale的值
trans, _ := Uni.GetTranslator(locale) //在翻译器中挑选locale
switch locale {
case "zh":
zh_translations.RegisterDefaultTranslations(Validate, trans) //将对应翻译器注册到验证器中
case "en":
en_translations.RegisterDefaultTranslations(Validate, trans)
default:
zh_translations.RegisterDefaultTranslations(Validate, trans)
}
var person Person
if err := c.ShouldBind(&person); err != nil { //绑定结构体
c.String(500, "%v\n", err.Error())
c.Abort()
return
}
if err := Validate.Struct(person); err != nil { //验证结构体
errs := err.(validator.ValidationErrors)
sliceErrs := []string{}
for _, e := range errs {
sliceErrs = append(sliceErrs, e.Translate(trans))
}
c.String(500, "%v\n", sliceErrs)
c.Abort()
return
}
c.String(200, "%v\n", person)
}
五、中间件
基于gin服务器和执行的回调函数的中间层,可以作为请求拦截、日志打印等功能
5.1 使用Gin中间件
package main
import (
"io"
"os"
"github.com/gin-gonic/gin"
)
func main() {
f, _ := os.Create("gin.log")
gin.DefaultWriter = io.MultiWriter(f)
gin.DefaultErrorWriter = io.MultiWriter(f)
r := gin.New() //default默认实现两个中间件: Logger+Recovery
r.Use(gin.Logger(), gin.Recovery()) //设置中间件
r.GET("/test", func(c *gin.Context) {
name := c.DefaultQuery("name", "middleware_gin")
panic("主动触发错误\n") //没有使用Recovery中间间将会死掉服务, Recovery会捕获错误,让服务不至于挂掉
c.String(200, "%s", name)
})
r.Run()
}
5.2 自定义ip白名单中间件
package main
import "github.com/gin-gonic/gin"
//IPAuthMiddleware x
func IPAuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
ipList := []string{"127.0.0.1"}
flag := false
clientIP := c.ClientIP()
for _, host := range ipList {
if clientIP == host {
flag = true
break
}
}
if !flag {
c.String(401, "%s, not in iplist", clientIP)
c.Abort()
}
}
}
func main() {
r := gin.Default()
r.Use(IPAuthMiddleware())
r.GET("/test", func(c *gin.Context) {
c.String(200, "golang")
})
r.Run()
}
//curl -X GET "http://192.168.27.145:8080/test"
// 192.168.27.145, not in iplist
//curl -X GET "http://127.0.0.1:8080/test"
//golang
六、其他补充
6.1 优雅关停
指服务停止时,如果请求未处理完毕,会有一个超时等待时间,去完成后续的处理,保证服务不在中间中断停止
原始服务器关闭和优雅关闭区别:
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("test", func(c *gin.Context) {
time.Sleep(10 * time.Second)
c.String(200, "golang")
})
srv := &http.Server{
Addr: ":8085",
Handler: r,
}
go func() { //将srv放入协程
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
quit := make(chan os.Signal)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) //无法捕获kill -9
<-quit //一直等待关闭
log.Println("shutdown server ...")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel() //直到cancel方法执行完
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("server shutdown:", err)
}
log.Println("server exiting")
}
访问url后立即结束服务(ctrl+c)
6.2 模板渲染
目录:
- teamplate_drawing
- teamplate
index.html
main.go
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.LoadHTMLGlob("teamplate/*") //将模板文件夹载入到gin框架
r.GET("/", func(c *gin.Context) {
c.HTML(200, "index.html", gin.H{
"title": "index.html",
})
})
r.Run()
}
6.3 自动证书配置
指无需配置任何证书,即可自动下载证书,过期自动续约的功能
package main
import "github.com/gin-gonic/gin"
/*
* 废弃
engine := gin.Default()
//证书位置使用的是相对路径
_ = http.ListenAndServeTLS(":443", "cert/www.domain.cn.crt",
"cert/www.domain.cn.key", engine)
*/
func main() {
r := gin.Default()
r.GET("/test", func(c *gin.Context) {
c.String(200, "golang")
})
autotls.Run(r, "zan71.com")
}