Gin官方地址:https://gin-gonic.com/
Gin的Github 地址:https://github.com/gin-gonic/gin
gin官方简介
Gin is a web framework written in Golang.
It features a martini-like API with much better performance, up to 40 times faster.
If you need performance and good productivity, you will love Gin.
粗俗的翻译成中文就是Gin是一个web框架,并且性能雕的飞起,谁用谁知道
安装方式
go get -u github.com/gin-gonic/gin
导入
import "github.com/gin-gonic/gin"
JSON
Gin 使用go包的中的encoding/json 作为json解析库 也可以使用第三方json解析库jsoniter
o build -tags=jsoniter
Hello world
package mainimport ("github.com/gin-gonic/gin""net/http")func main() {r := gin.Default()r.GET("/ping", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"code":0,"message": "success","data":"pong",})})r.Run()}
c.JSON方法就是返回一个JSON格式的字符串具体的执行函数是
// render/json.gofunc WriteJSON(w http.ResponseWriter, obj interface{}) error {writeContentType(w, jsonContentType)encoder := json.NewEncoder(w)err := encoder.Encode(&obj)return err}
gin.H其实是一个 map[string]interface{}
type H map[string]interface{}
执行
$ curl http://127.0.0.1:8080/ping{"code":0,"data":"pong","message":"success"}
创建web服务请求,使用gin的Default方法创建一个路由handler。然后通过HTTP方法绑定路由规则和路由函数。不同于net/http库的路由函数,gin进行了封装,把request和response都封装到gin.Context的上下文环境。
package mainimport ("github.com/gin-gonic/gin""net/http""time")func main() {router := gin.Default()router.GET("/", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"method": c.Request.Method,"time": time.Now().Format("2006-01-02:15:04:05")})})router.Run(":8000")}
参数
路由参数
通过Param获取路径中的可变参数
package mainimport ("github.com/gin-gonic/gin""net/http")func main() {r := gin.Default()r.GET("/user/:id", func(c *gin.Context) {id := c.Param("id")c.JSON(http.StatusOK,gin.H{"user_id":id})})r.GET("/user/:id/:name", func(c *gin.Context) {id := c.Param("id")name := c.Param("name")c.JSON(http.StatusOK,gin.H{"id":id,"name":name})})r.Run()}
go run main.go 然后执行请求
curl -X GET 'http://localhost:8080/user/123'{"user_id":"123"}curl -X GET 'http://localhost:8080/user/你好'{"user_id":"你好"}curl -X GET 'http://localhost:8080/user/123/tony'{"id":"123","name":"tony"}
Gin路径中的匹配都是字符串,模糊匹配中对数字、字母和汉字都认为合法的
需要注意的是Gin的路由是单一的,不能有重复。比如这里我们注册了/users/:id,那么我们就不能再注册匹配/users/list路由。
r.GET("/user/list", func(c *gin.Context) {c.JSON(http.StatusOK,gin.H{"hello":"world"})})
上面的路由会出现冲突报错
panic: 'list' in new path '/user/list' conflicts with existing wildcard ':id' in existing prefix '/user/:id'
Query参数
- query(?)参数可以通过DefaultQuery()或Query()方法获取
- DefaultQuery()若参数不村则,返回默认值,Query()若不存在,返回空串 ```go package main
import ( “github.com/gin-gonic/gin” “net/http” )
func main() { router := gin.Default() router.GET(“/user”, func(c *gin.Context) { name:= c.DefaultQuery(“name”,”guest”) id := c.Query(“id”) c.JSON(http.StatusOK,gin.H{“name”:name,”id”:id}) }) router.Run(“:8090”) }
```bashcurl -X GET 'http://127.0.0.1:8090/user?id=1&name=yangyu'{"id":"1","name":"yangyu"}curl -X GET 'http://127.0.0.1:8090/user?id=1'{"id":"1","name":"guest"}
表单参数
表单参数可以通过PostForm()方法获取
package mainimport ("github.com/gin-gonic/gin""net/http")func main() {router := gin.Default()router.POST("/post", func(c *gin.Context) {page := c.Query("page")size := c.DefaultQuery("size", "10")id := c.PostForm("id")name := c.PostForm("name")c.JSON(http.StatusOK,gin.H{"Id":id,"page":page,"size":size,"name":name})})router.Run(":8090")}
$ curl -X POST -F 'name=小明' -F 'id=101' 'http://127.0.0.1:8090/post?page=2'{"Id":"101","name":"小明","page":"2","size":"10"}
RestAPI
package mainimport ("github.com/gin-gonic/gin""net/http")func main() {router := gin.Default()c := func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"method": c.Request.Method})}router.GET("/get", c)router.POST("/post", c)router.PUT("/put", c)router.DELETE("/delete", c)router.PATCH("/patch", c)router.HEAD("/head", c)router.OPTIONS("/options", c)router.Run(":8000")}
数据协议
目前主流的是json和ProtoBuf,xml已经逐渐西山,yaml 主要用于配置方面
package mainimport ("github.com/gin-gonic/gin""github.com/gin-gonic/gin/testdata/protoexample""net/http")func main() {r := gin.Default()r.GET("/proto/json", func(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"msg": "hello json", "code": 0})})r.GET("/proto/xml", func(c *gin.Context) {c.XML(http.StatusOK, gin.H{"message": "hello xml", "code": 0})})r.GET("/proto/yaml", func(c *gin.Context) {c.YAML(http.StatusOK, gin.H{"message": "hello yaml", "code": 0})})r.GET("/proto/pb", func(c *gin.Context) {reps := []int64{int64(1), int64(2)}label := "ProtoBuf"data := &protoexample.Test{Label: &label,Reps: reps,}c.ProtoBuf(http.StatusOK, data)})r.GET("/user/info", func(c *gin.Context) {user := struct {Id int64Name stringIsVIP bool}{Id:101,Name:"小明",IsVIP:true}response := struct {Data interface{}Msg stringCode int}{Data:user,Msg:"success",Code:0}c.JSON(http.StatusOK, response)})r.Run(":8090")}
执行请求.
$ curl 'http://127.0.0.1:8090/proto/json'{"code":0,"msg":"hello json"}$ curl 'http://127.0.0.1:8090/user/info'{"Data":{"Id":101,"Name":"小明","IsVIP":true},"Msg":"success","Code":0}
参数请求校验
gin 格式校验使用了第三库是https://github.com/go-playground/validator
MustBind
主要包括Bind, BindJSON, BindXML, BindQuery, BindYAML, BindHeader
底层调用的都是MustBindWith,如果校验不通过直接调用的c.AbortWithError(400, err).SetType(ErrorTypeBind)
ShouldBind
package mainimport ("github.com/gin-gonic/gin""net/http")type LoginReq struct {Password string `json:"password" binding:"required"`Name string `json:"name" binding:"required"`}func main() {router := gin.Default()router.POST("/user", func(c *gin.Context) {var req LoginReqif err :=c.ShouldBind(&req);err!=nil{c.JSON(http.StatusBadRequest,gin.H{"code":400,"mes":"非法请求","data":err.Error()})return}c.JSON(http.StatusOK,gin.H{"code":0,"mes":"success","data":req})})router.Run(":8090")}
curl -X POST -d '{"password":"123456","name":"123@qq.com"}' -H "Content-Type: application/json" 'http://127.0.0.1:8090/user'{"code":0,"data":{"password":"123456","name":"123@qq.com"},"mes":"success"}
如果缺少参数
$ curl -X POST -d '{"password":"123456"}' -H "Content-Type: application/json" 'http://127.0.0.1:8090/user'{"code":400,"data":"Key: 'LoginReq.Name' Error:Field validation for 'Name' failed on the 'required' tag","mes":"非法请求"}
ShouldBindBodyWith
上面的代码也可以使用ShouldBindBodyWith,如果需要在其他地方使用body 建议使用ShouldBindBodyWith,
func main() {router := gin.Default()router.POST("/user", func(c *gin.Context) {var req LoginReqif err :=c.ShouldBindBodyWith(&req,binding.JSON);err!=nil{c.JSON(http.StatusBadRequest,gin.H{"code":400,"mes":"非法请求","data":err.Error()})return}c.JSON(http.StatusOK,gin.H{"code":0,"mes":"success","data":req})})router.Run(":8090")}
ShouldBindBodyWith的源码就是上下文中存储了请求体,如果没有对请求体的额外使用地方 建议直接ShouldBindWith
// ShouldBindBodyWith is similar with ShouldBindWith, but it stores the request// body into the context, and reuse when it is called again.//// NOTE: This method reads the body before binding. So you should use// ShouldBindWith for better performance if you need to call only once.func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (err error) {var body []byteif cb, ok := c.Get(BodyBytesKey); ok {if cbb, ok := cb.([]byte); ok {body = cbb}}if body == nil {body, err = ioutil.ReadAll(c.Request.Body)if err != nil {return err}c.Set(BodyBytesKey, body)}return bb.BindBody(body, obj)}
binding.BindingBody 有如下具体类型
var (JSON = jsonBinding{}XML = xmlBinding{}Form = formBinding{}Query = queryBinding{}FormPost = formPostBinding{}FormMultipart = formMultipartBinding{}ProtoBuf = protobufBinding{}MsgPack = msgpackBinding{}YAML = yamlBinding{}Uri = uriBinding{}Header = headerBinding{})
自定义
package main
import (
"net/http"
"reflect"
"time"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"gopkg.in/go-playground/validator.v8"
)
// Booking contains binded and validated data.
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.Year() > date.Year() || (today.Year() == date.Year() && today.YearDay() > date.YearDay()) {
return false
}
}
return true
}
func main() {
route := gin.Default()
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("bookabledate", bookableDate)
}
route.GET("/bookable", getBookable)
route.Run(":8090")
}
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()})
}
}
中间件
需要做权限校验,需要在每个请求中加入权限业务逻辑 ,代码不优雅
func main() {
router := gin.Default()
router.GET("/bar", func(c *gin.Context) {
token := c.Query("token")
if len(token)>0 {
c.JSON(http.StatusOK,gin.H{"code":0,"msg":"success","data":token})
}else {
c.AbortWithStatusJSON(http.StatusUnauthorized,gin.H{"code":401,"msg":"权限错误","data": struct {}{}})
}
})
router.GET("/foo", func(c *gin.Context) {
token := c.Query("token")
if len(token)>0 {
c.JSON(http.StatusOK,gin.H{"code":0,"msg":"success","data":token})
}else {
c.AbortWithStatusJSON(http.StatusUnauthorized,gin.H{"code":401,"msg":"权限错误","data": struct {}{}})
}
})
router.Run(":8090")
}
利用中间件方式,类似于责任链模式
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func AuthMiddleWare() gin.HandlerFunc{
return func(c *gin.Context) {
token := c.Query("token")
if len(token)>0 {
c.Set("TOKEN",token)
c.Next()
}else {
c.AbortWithStatusJSON(http.StatusUnauthorized,gin.H{"code":401,"msg":"权限错误","data": struct {}{}})
}
}
}
func main() {
router := gin.Default()
router.Use(AuthMiddleWare())
router.GET("/user", func(c *gin.Context) {
v ,_ := c.Get("TOKEN")
c.JSON(http.StatusOK,gin.H{"code":0,"msg":"success","data":v})
})
router.Run(":8090")
}
请求测试
$ curl "http://127.0.0.1:8090/bar?token=123456"
{"code":0,"data":"123456","msg":"success"}
$ curl "http://127.0.0.1:8090/bar"
{"code":401,"data":{},"msg":"权限错误"}
接口分组
func main() {
router := gin.Default()
// Simple group: v1
v1 := router.Group("/v1")
{
v1.POST("/login", loginEndpoint)
v1.POST("/submit", submitEndpoint)
v1.POST("/read", readEndpoint)
}
// Simple group: v2
v2 := router.Group("/v2")
{
v2.POST("/login", loginEndpoint)
v2.POST("/submit", submitEndpoint)
v2.POST("/read", readEndpoint)
}
router.Run(":8080")
}
Github的Gin项目
https://github.com/YahuiAn/Go-bjut
热加载调试 Hot Reload
Python 的 Flask框架,有 debug 模式,启动时传入 debug=True 就可以热加载(Hot Reload, Live Reload)了。即更改源码,保存后,自动触发更新,浏览器上刷新即可。免去了杀进程、重新启动之苦。
Gin 原生不支持,但有很多额外的库可以支持。例如
- github.com/codegangsta/gin
- github.com/pilu/fresh
这次,我们采用 github.com/pilu/fresh 。
go get -v -u github.com/pilu/fresh
安装好后,只需要将go run main.go命令换成fresh即可。每次更改源文件,代码将自动重新编译(Auto Compile)。
参考
https://geektutu.com/post/gee.html
https://github.com/gin-gonic/examples
