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 main
import (
"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.go
func 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 main
import (
"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 main
import (
"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”) }
```bash
curl -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 main
import (
"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 main
import (
"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 main
import (
"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 int64
Name string
IsVIP bool
}{Id:101,Name:"小明",IsVIP:true}
response := struct {
Data interface{}
Msg string
Code 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 main
import (
"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 LoginReq
if 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 LoginReq
if 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 []byte
if 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