routing
The routing library used in the gin framework is based on httprouter
Restful style API
Representational State Transfer is an API design concept for Internet applications:
- The URL locates the resource and uses HTTP to describe the operation
- Add POST / delete DELETE / change PUT / check GET
parameter
API parameters : Param method
r.GET("/:name/*action", func(c *gin.Context) { name := c.Param("name") action := c.Param("action") // action = /yyy action = strings.Trim(action, "/") //截取 c.String(http.StatusOK, name+" - "+action) })
URL parameters :
DefaultQuery(): parameter does not exist, return default value
c.DefaultQuery("name", "枯藤")
- Query(): The parameter does not exist, return empty
Form parameters : PostForm method
types := c.DefaultPostForm("type", "post") username := c.PostForm("username")
upload files
- multipart/form-data format for file upload
- The gin file upload is similar to the native net/http method, the difference is that gin encapsulates the native request into c.Request
Upload a single file
<form action="http://localhost:8080/upload" method="post" enctype="multipart/form-data">
上传文件:<input type="file" name="file" >
<input type="submit" value="提交">
</form>
//限制上传最大尺寸
r.MaxMultipartMemory = 8 << 20
r.POST("/upload", func(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.String(500, "上传图片出错")
return
}
// c.JSON(200, gin.H{"message": file.Header.Context})
c.SaveUploadedFile(file, file.Filename)
c.String(http.StatusOK, file.Filename)
})
upload limit
- Restrict file types: headers.Header.Get("Content-Type")
- Limit file size: headers.Size
r.POST("/upload-limit", func(c *gin.Context) {
_, headers, err := c.Request.FormFile("file")
if err != nil {
c.String(500, "上传图片出错")
return
}
if headers.Size > 1024*1024*2 {
c.String(500, "图片太大了")
return
}
t := headers.Header.Get("Content-Type")
if t != "image/jpeg" {
c.String(500, "图片格式错误:"+t)
return
}
// c.JSON(200, gin.H{"message": file.Header.Context})
c.SaveUploadedFile(headers, "./upload/"+headers.Filename)
c.String(http.StatusOK, headers.Filename)
})
Upload multiple files
<form action="http://localhost:8000/upload" method="post" enctype="multipart/form-data">
上传文件:<input type="file" name="files" multiple>
<input type="submit" value="提交">
</form>
r.POST("/upload", func(c *gin.Context) {
form, err := c.MultipartForm()
if err != nil {
c.String(http.StatusBadRequest, fmt.Sprintf("get err %s", err.Error()))
}
// 获取所有图片
files := form.File["files"]
// 遍历所有图片
for _, file := range files {
// 逐个存
if err := c.SaveUploadedFile(file, file.Filename); err != nil {
c.String(http.StatusBadRequest, fmt.Sprintf("upload err %s", err.Error()))
return
}
}
c.String(200, fmt.Sprintf("upload ok %d files", len(files)))
})
Group Group
v1 := r.Group("/v1")
{
v1.GET("/login", login)
v1.GET("submit", submit)
}
Data parsing and binding
type Login struct {
// binding:"required"修饰的字段,若接收为空值,则报错,是必须字段
User string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"`
Password string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}
Json
var json Login
// 将request的body中的数据,自动按照json格式解析到结构体
c.ShouldBindJSON(&json); //json.User json.Password
form
<form action="http://localhost:8000/loginForm" method="post" enctype="application/x-www-form-urlencoded">
用户名<input type="text" name="username"><br>
密码<input type="password" name="password">
<input type="submit" value="提交">
</form>
var form Login
c.Bind(&form); // form.User form.Password
URI
r.GET("/:user/:password", func(c *gin.Context) {
var login Login
c.ShouldBindUri(&login);// login.User login.Password
}
render
data rendering
JSON
c.JSON(200, gin.H{"msg": "json"}) //{"msg":"json"}
XML
c.XML(200, gin.H{"msg": "xml"}) //<map><msg>xml</msg></map>
YAML
c.YAML(200, gin.H{"msg": "yaml"}) //文件下载 msg: yaml
ProtoBuf
reps := []int64{int64(1), int64(2)} // 定义数据 label := "label" // 传protobuf格式数据 data := &protoexample.Test{ Label: &label, Reps: reps, } r.GET("/proto", func(c *gin.Context) { c.ProtoBuf(200, data) //文件下载 label })
HTML rendering
- gin supports loading HTML templates, and then configures and returns the corresponding data according to the template parameters, which is essentially string replacement
- LoadHTMLGlob() method can load template files
r.LoadHTMLGlob("html/*.html")
r.GET("/html", func(c *gin.Context) {
c.HTML(200, "index.html", gin.H{"name": "test"})
})
redirect
//301重定向
c.Redirect(http.StatusMovedPermanently, "https://blog.itxiaoma.cn")
asynchronous
- When starting a new goroutine, the original context should not be used, a read-only copy of it must be used
copyContext := c.Copy() // Context副本
go func() {
time.Sleep(3 * time.Second)
log.Println("异步执行:" + copyContext.Request.URL.Path) //异步执行:/async
}()
c.String(200, "Sync...")
middleware
- Middleware refers to special functions that can intercept the http request-response life cycle
Gin uses Logger(), Recovery() two global middleware by default
//去除默认全局中间件 r := gin.New()//不带中间件
global middleware
func FullMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("request", "中间件")
}
}
r.Use(FullMiddleware())//使用中间件
r.GET("/middleware", func(c *gin.Context) {
req, _ := c.Get("request")
c.String(200, req.(string))
})
Next
- The operation before c.Next() is executed before the Handler is executed; generally, the verification process is performed to check whether the access is allowed.
- The operations after c.Next() are executed after the Handler is executed; generally, summary processing is performed, such as formatted output, response end time, and response duration calculation.
func NextMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("中间件开始执行")
c.Next()
fmt.Println("中间件执行完毕")
}
}
func NextResponse(r *gin.Engine) {
r.Use(NextMiddleware())
r.GET("/next", func(c *gin.Context) {
fmt.Println("请求执行...")
// 中间件开始执行
// 请求执行...
// 中间件执行完毕
})
}
Abort
- Indicates termination, that is, when Abort is executed, all subsequent middleware function calls will be stopped.
func AbortMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("中间件开始执行")
if c.Query("key") == "abort" {
c.String(200, "Abort")
c.Abort()
}
}
}
func AbortResponse(r *gin.Engine) {
r.Use(AbortMiddleware())
r.GET("/abort", func(c *gin.Context) {
fmt.Println("请求执行...")
c.String(200, "OK")
})
}
local middleware
//局部中间件
func PartMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
fmt.Println("中间件开始执行")
}
}
func PartResponse(r *gin.Engine) {
r.GET("/part", PartMiddleware(), func(c *gin.Context) {
fmt.Println("请求执行...")
// 中间件开始执行
// 请求执行...
})
}
Middleware recommendation
- RestGate - Secure authentication for REST API endpoints
- staticbin - Middleware/handler for serving static files from binary data
- gin-cors - Official middleware for CORS gin
- gin-csrf - CSRF protection
- gin-health - Middleware for reporting via gocraft/health
- gin-merry - Pretty- printed error middleware with context
- gin-revision - Revision middleware for the Gin framework
- gin-jwt - JWT middleware for the Gin framework
- gin-sessions - Session middleware based on mongodb and mysql
- gin-location - Middleware for exposing a server's hostname and scheme
- gin-nice-recovery - Emergency recovery middleware that lets you build better user experiences
- gin-limit - Limit simultaneous requests; can help increase traffic
- gin-limit-by-key - An in-memory middleware for rate limiting access by custom key and rate.
- ez-gin-template - Simple template wrapper for gin
- gin-hydra - gin middleware Hydra
- gin-glog - Designed to replace Gin's default log
- gin-gomonitor - For exposing metrics via Go-Monitor
- gin-oauth2 - for OAuth2
- Alternative static asset handler for the static gin framework.
- xss-mw - XssMw is a middleware designed to "automatically remove XSS" from user submitted input
- gin-helmet - Simple collection of security middleware.
- gin-jwt-session - Provides middleware for JWT/Session/Flash, easy to use, while also providing necessary tuning options. Samples are also available.
- gin-template - Easy-to-use html/template for the gin framework.
- gin-redis-ip-limiter - IP address based request limiter. It can be used with redis and sliding window mechanism.
- gin-method-override - _method method to be overridden by POST formal parameters, inspired by Ruby's rack of the same name
- gin-access-limit - limit - Access control middleware by specifying allowed source CIDR notation.
- gin-session - Session middleware for Gin
- gin-stats - Lightweight and useful request metrics middleware
- gin-statsd - Gin middleware that reports to the statsd daemon
- gin-health-check - check-health check middleware for Gin
- gin-session-middleware - An efficient, secure and easy-to-use Go Session library.
- ginception - Pretty exceptions page
- gin-inspector - Gin middleware for investigating http requests.
- gin-dump - Gin middleware/handler to dump request and response headers/body. Very helpful for debugging applications.
- go-gin-prometheus - Gin Prometheus metrics exporter
- ginprom - Prometheus metrics exporter for Gin
- gin-go-metrics - Gin middleware to gather and store metrics using rcrowley/go-metrics
- ginrpc - Gin middleware/processor autobinding tool. Object registration is supported through annotation routes like beego
Original: https://github.com/gin-gonic/contrib/blob/master/README.md
session control
cookies
func CookieHandler(r *gin.Engine) {
r.GET("/cookie", func(c *gin.Context) {
cookie, err := c.Cookie("test")
if err != nil {
c.SetCookie(
"test",
"value",
60, //maxAge int, 单位为秒
"/", //path,cookie所在目录
"localhost", //domain string,域名
false, //secure 是否智能通过https访问
true, //httpOnly bool 是否允许别人通过js获取自己的cookie
)
}
c.String(200, cookie)
})
}
Cookie verification
func AuthMiddleWare() gin.HandlerFunc {
return func(c *gin.Context) {
// 获取客户端cookie并校验
if cookie, err := c.Cookie("abc"); err == nil {
if cookie == "123" {
c.Next()
return
}
}
// 返回错误
c.JSON(http.StatusUnauthorized, gin.H{"error": "err"})
// 若验证不通过,不再调用后续的函数处理
c.Abort()
return
}
}
Session
Install
go get github.com/gin-contrib/sessions
use
var store = cookie.NewStore([]byte("secret"))
func SessionHandler(r *gin.Engine) {
r.GET("/session",
sessions.Sessions("mysession", store), //路由上加入session中间件
func(c *gin.Context) {
session := sessions.Default(c)
if session.Get("name") != "itxiaoma" {
session.Set("name", "itxiaoma")
//记着调用save方法,写入session
session.Save()
}
c.JSON(200, gin.H{"name": session.Get("name")}) //{"name":"itxiaoma"}
})
}
parameter validation
Using the data verification of the gin framework, you can save the data without parsing the data and reduce the if else, which will be much simpler.
Struct Validation
type Person struct {
Name string `form:"name"`
}
func JsonHandler(r *gin.Engine) {
r.GET("/structure", func(c *gin.Context) {
var person Person
c.ShouldBind(&person)
c.String(200, fmt.Sprintf("%#v", person))
//访问:http://localhost:8080/structure?name=xxx
//输出:structure.Person{Name:"xxx"}
})
}
custom validation
Customize the validation function for the parameters bound to the structure
Official website example: https://github.com/gin-gonic/gin#custom-validators
introduce
go get github.com/go-playground/validator/v10
use:
package validator
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/validator/v10"
"net/http"
)
type Person struct {
Name string `form:"name" binding:"NotAdmin"` // 1、自定义注册名称
}
// 2、自定义校验方法
var notAdmin validator.Func = func(fl validator.FieldLevel) bool {
name, ok := fl.Field().Interface().(string)
if ok {
return name != "admin"
}
return true
}
func MyValidatorHandler(r *gin.Engine) {
// 3、将自定义的校验方法注册到 validator 中
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
// 这里的 key 和 fn 可以不一样最终在 struct 使用的是 key
v.RegisterValidation("NotAdmin", notAdmin)
}
r.GET("/validator", func(c *gin.Context) {
var person Person
if e := c.ShouldBind(&person); e == nil {
c.String(http.StatusOK, "%v", person)
} else {
c.String(http.StatusOK, "person bind err:%v", e.Error())
//person bind err:Key: 'Person.Name' Error:Field validation for 'Name' failed on the 'NotAdmin' tag
}
})
}
Multipart/Urlencoded binding
package main
import (
"github.com/gin-gonic/gin"
)
type LoginForm struct {
User string `form:"user" binding:"required"`
Password string `form:"password" binding:"required"`
}
func main() {
router := gin.Default()
router.POST("/login", func(c *gin.Context) {
// 你可以使用显式绑定声明绑定 multipart form:
// c.ShouldBindWith(&form, binding.Form)
// 或者简单地使用 ShouldBind 方法自动绑定:
var form LoginForm
// 在这种情况下,将自动选择合适的绑定
if c.ShouldBind(&form) == nil {
if form.User == "user" && form.Password == "password" {
c.JSON(200, gin.H{"status": "you are logged in"})
} else {
c.JSON(401, gin.H{"status": "unauthorized"})
}
}
})
router.Run(":8080")
}
test:
curl -v --form user=user --form password=password http://localhost:8080/login
Other types: URL parameters
c.ShouldBindWith(&p, binding.Query);
other
log
f, _ := os.Create("log/gin.log")
gin.DefaultWriter = io.MultiWriter(f)
r = gin.Default()
r.GET("/log", func(c *gin.Context) {
//同时将日志写入文件和控制台
//gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
c.String(200, "log ok")
})
r.Run()
verification code
Execute logic:
- First write the key-value pair (k->v) in the session, write the value on the image, then generate the image and display it on the browser
- The front-end sends the verification code to the back-end, and the back-end obtains v according to the k in the session, and compares and checks
package captcha
import (
"bytes"
"github.com/dchest/captcha"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
var sessionMaxAge = 3600
var sessionSecret = "itxiaoma"
func SessionMiddleware(keyPairs string) gin.HandlerFunc {
var store sessions.Store
store = cookie.NewStore([]byte(sessionSecret))
store.Options(sessions.Options{
MaxAge: sessionMaxAge, //seconds
Path: "/",
})
return sessions.Sessions(keyPairs, store)
}
func Captcha(c *gin.Context, length ...int) {
l := captcha.DefaultLen
w, h := 107, 36
if len(length) == 1 {
l = length[0]
}
if len(length) == 2 {
w = length[1]
}
if len(length) == 3 {
h = length[2]
}
captchaId := captcha.NewLen(l)
session := sessions.Default(c)
session.Set("captcha", captchaId)
_ = session.Save()
_ = Serve(c.Writer, c.Request, captchaId, ".png", "zh", false, w, h)
}
func CaptchaVerify(c *gin.Context, code string) bool {
session := sessions.Default(c)
if captchaId := session.Get("captcha"); captchaId != nil {
session.Delete("captcha")
_ = session.Save()
if captcha.VerifyString(captchaId.(string), code) {
return true
} else {
return false
}
} else {
return false
}
}
func Serve(w http.ResponseWriter, r *http.Request, id, ext, lang string, download bool, width, height int) error {
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Expires", "0")
var content bytes.Buffer
switch ext {
case ".png":
w.Header().Set("Content-Type", "image/png")
_ = captcha.WriteImage(&content, id, width, height)
case ".wav":
w.Header().Set("Content-Type", "audio/x-wav")
_ = captcha.WriteAudio(&content, id, lang)
default:
return captcha.ErrNotFound
}
if download {
w.Header().Set("Content-Type", "application/octet-stream")
}
http.ServeContent(w, r, id+ext, time.Time{}, bytes.NewReader(content.Bytes()))
return nil
}
func main() {
r := gin.Default()
r.LoadHTMLGlob("captcha/*.html")
r.Use(SessionMiddleware("itxiaoma"))
r.GET("/captcha", func(c *gin.Context) {
Captcha(c, 4)
})
r.GET("/captcha-html", func(c *gin.Context) {
c.HTML(http.StatusOK, "captcha.html", nil)
})
r.GET("/captcha/verify/:value", func(c *gin.Context) {
value := c.Param("value")
if CaptchaVerify(c, value) {
c.JSON(http.StatusOK, gin.H{"status": 0, "msg": "success"})
} else {
c.JSON(http.StatusOK, gin.H{"status": 1, "msg": "failed"})
}
})
r.Run()
}
JWT
package jwt
import (
"fmt"
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"net/http"
"time"
)
//自定义字符串
var jwtkey = []byte("itxiaoma")
var str string
type Claims struct {
UserId uint
jwt.StandardClaims
}
//颁发token
func setting(ctx *gin.Context) {
expireTime := time.Now().Add(7 * 24 * time.Hour)
claims := &Claims{
UserId: 2,
StandardClaims: jwt.StandardClaims{
ExpiresAt: expireTime.Unix(), //过期时间
IssuedAt: time.Now().Unix(),
Issuer: "127.0.0.1", // 签名颁发者
Subject: "user token", //签名主题
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// fmt.Println(token)
tokenString, err := token.SignedString(jwtkey)
if err != nil {
fmt.Println(err)
}
str = tokenString
ctx.JSON(200, gin.H{"token": tokenString})
}
//解析token
func getting(ctx *gin.Context) {
tokenString := ctx.GetHeader("Authorization")
fmt.Println(tokenString)
//vcalidate token formate
if tokenString == "" {
ctx.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "权限不足1"})
ctx.Abort()
return
}
token, claims, err := ParseToken(tokenString)
if err != nil || !token.Valid {
ctx.JSON(http.StatusUnauthorized, gin.H{"code": 401, "msg": "权限不足2"})
ctx.Abort()
return
}
ctx.JSON(200, gin.H{"claims": claims})
}
func ParseToken(tokenString string) (*jwt.Token, *Claims, error) {
Claims := &Claims{}
token, err := jwt.ParseWithClaims(tokenString, Claims, func(token *jwt.Token) (i interface{}, err error) {
return jwtkey, nil
})
return token, Claims, err
}
func main() {
r := gin.Default()
r.GET("/set-jwt", setting)
r.GET("/get-jwt", getting)
r.Run()
}
debugging
curl http://localhost:8080/get-jwt -H "Authorization:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjIsImV4cCI6MTY1NDA1NzQ1MCwiaWF0IjoxNjUzNDUyNjUwLCJpc3MiOiIxMjcuMC4wLjEiLCJzdWIiOiJ1c2VyIHRva2VuIn0.IN_Tj-M6CMHFlunnRIvUgog2GMDyWpj7iOsjwUeD0Sk"
Rights Management Casbin
Casbin is a powerful and efficient open source access control library for Golang projects.
Permissions actually control who can perform what operations on what resources .
Casbin abstracts the access control model into a configuration file (model file) based on the PERM (Policy, Effect, Request, Matchers) metamodel. So switching or updating the authorization mechanism simply requires modifying the configuration file.
Introduce:
go get github.com/casbin/casbin/v2
Model file model.conf
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
- request is an abstraction of an access request, which corresponds to the parameters of the e.Enforce() function one-to-one
- Policy is the definition of policies or rules. It defines specific rules.
- request is an abstraction of an access request. It has a one-to-one correspondence with the parameters of the e.Enforce() function. The matcher matcher will match the request with each defined policy one by one, and generate multiple matching results.
- The effect decides whether to allow or deny the request by summing up all the results from applying the matcher to the request.
sub: access entity obj: access object act: access action
The above model file specifies that the permission is composed of three elements sub,obj,act
, and the request can only be passed if there is an exact same policy as it in the policy list. The result of the matcher can be obtained by p.eft
, some(where (p.eft == allow))
means that only one policy allows it.
File storage policy policy.csv
That is, who can perform what operations on what resources:
p, dajun, data1, read
p, lizi, data2, write
文件的两行内容表示dajun
data1
有read
权限, lizi
对数据data2
有write
permissions
func check(e *casbin.Enforcer, sub, obj, act string) {
ok, _ := e.Enforce(sub, obj, act)
if ok {
fmt.Printf("%s CAN %s %s\n", sub, act, obj)
} else {
fmt.Printf("%s CANNOT %s %s\n", sub, act, obj)
}
}
func main() {
e, err := casbin.NewEnforcer("./model.conf", "./policy.csv")
if err != nil {
log.Fatalf("NewEnforecer failed:%v\n", err)
}
check(e, "dajun", "data1", "read") //dajun CAN read data1
check(e, "lizi", "data2", "write") //lizi CAN write data2
check(e, "dajun", "data1", "write")//dajun CANNOT write data1
check(e, "dajun", "data2", "read") //dajun CANNOT read data2
}
Super administrator:
[matchers]
e = r.sub == p.sub && r.obj == p.obj && r.act == p.act || r.sub == "root"
check
check(e, "root", "data1", "read")
check(e, "root", "data2", "write")
check(e, "root", "data1", "execute")
check(e, "root", "data3", "rwx")
RBAC model
Model file:
[role_definition]
g = _, _
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
g = _,_
defines the mapping relationship of user-role, role-role, the former is a member of the latter and has the permissions of the latter.
g(r.sub, p.sub)
is used to determine whether the request subject r.sub
belongs to the role of p.sub
.
Gorm Adapter
data:
CREATE DATABASE IF NOT EXISTS casbin;
USE casbin;
CREATE TABLE IF NOT EXISTS casbin_rule (
p_type VARCHAR(100) NOT NULL,
v0 VARCHAR(100),
v1 VARCHAR(100),
v2 VARCHAR(100),
v3 VARCHAR(100),
v4 VARCHAR(100),
v5 VARCHAR(100)
);
INSERT INTO casbin_rule VALUES
('p', 'dajun', 'data1', 'read', '', '', ''),
('p', 'lizi', 'data2', 'write', '', '', '');
Gorm Adapter
15301f0d4d60a2d929e3463a6c6614bc---加载policy
, Gorm Adapter
casbin
库中的casbin_rule
表:
package main
import (
"fmt"
"github.com/casbin/casbin/v2"
gormadapter "github.com/casbin/gorm-adapter/v2"
_ "github.com/go-sql-driver/mysql"
)
func check(e *casbin.Enforcer, sub, obj, act string) {
ok, _ := e.Enforce(sub, obj, act)
if ok {
fmt.Printf("%s CAN %s %s\n", sub, act, obj)
} else {
fmt.Printf("%s CANNOT %s %s\n", sub, act, obj)
}
}
func main() {
a, _ := gormadapter.NewAdapter("mysql", "root:12345@tcp(127.0.0.1:3306)/")
e, _ := casbin.NewEnforcer("./model.conf", a)
check(e, "dajun", "data1", "read")
check(e, "lizi", "data2", "write")
check(e, "dajun", "data1", "write")
check(e, "dajun", "data2", "read")
}
run:
dajun CAN read data1
lizi CAN write data2
dajun CANNOT write data1
dajun CANNOT read data2
other
Gin Chinese documentation: https://learnku.com/docs/gin-gonic/1.7
Reference
Gin Framework Chinese Documentation
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。