Gin

Gin is a HTTP web framework written in Go

Installation

use command to install Gin

1
$ go get -u github.com/gin-gonic/gin

QuickStart

1
2
3
4
5
6
7
8
9
10
11
12
13
func main()  {
//Router instance
r := gin.Default()
//gin.Context packages request and response
r.GET("/", func(context *gin.Context) {
context.String(http.StatusOK, "hello world")
context.JSON(http.StatusOK, gin.H{
"message": "helloWorld",
})
})
//listen and serve, default port: 8080
r.Run()
}

the difference of new() and default() :

Default() Creates an Engine instance with the Logger and Recovery middleware already attached.

1
2
r := gin.Default()
r := gin.New()

Gin group

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"github.com/gin-gonic/gin"
"net/http"
)

func main() {
router := gin.Default()
//create a goods group
goodsGroup := router.Group("/goods")
{
goodsGroup.GET("/list", goodsList)
goodsGroup.GET("/add", goodsAdd)
}
router.Run()
}

func goodsAdd(context *gin.Context) {
context.String(http.StatusOK, "added a goods")
}

func goodsList(context *gin.Context) {
context.String(http.StatusOK, "checked the goods list")
}

Url Params

get parameters from url

use :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"github.com/gin-gonic/gin"
"net/http"
)

func main() {
router := gin.Default()
//a goods group
goodsGroup := router.Group("/goods")
{
goodsGroup.GET("/:id/:action", goodsDetail)
}
router.Run()
}

func goodsDetail(context *gin.Context) {
id := context.Param("id")
action := context.Param("action")
context.JSON(http.StatusOK, gin.H{
"id" : id,
"action" : action,
})
}

when we need the path in url

use *

1
goodsGroup.GET("/:id/*action", goodsDetail)
1
2
3
http://localhost:8080/goods/1/delete/ok/o
//result:
{"action":"/delete/ok/o","id":"1"}

bind uri with struct

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"github.com/gin-gonic/gin"
"net/http"
)

type Person struct {
Id int `uri:"id" binding:"required"`
Name string `uri:"name" binding:"required"`
}
func main() {
router := gin.Default()
router.GET("/:id/:name", func(context *gin.Context) {
var person Person
if err := context.ShouldBindUri(&person); err != nil {
context.Status(404)
return
}
context.JSON(http.StatusOK, gin.H{
"name" : person.Name,
"id" : person.Id,
})
})
router.Run()
}

Get/Post Parms

get

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"github.com/gin-gonic/gin"
"net/http"
)

func main() {
r := gin.Default()
r.GET("/welcome", welcome)
r.Run()
}

func welcome(context *gin.Context) {
//default param
firstName := context.DefaultQuery("firstname", "issak")
lastName := context.DefaultQuery("lastname", "kenny")
context.JSON(http.StatusOK, gin.H{
"first_name" : firstName,
"last_name" : lastName,
})
}

url:

1
http://localhost:8080/welcome?firstname=a&lastname=b

post

1
2
3
4
5
6
7
8
9
10
11
r.POST("/form_post", formPost)

func formPost(context *gin.Context) {
message := context.PostForm("message")
nick := context.DefaultPostForm("nick", "anonymous")
context.JSON(http.StatusOK, gin.H{
"first_name" : message,
"last_name" : nick,
})
}

send post request

image-20220313160231463

get and post

1
2
3
4
5
6
7
8
9
10
11
12
13
14
r.POST("/post", getPost)

func getPost(context *gin.Context) {
id := context.Query("id")
page := context.DefaultQuery("page", "0")
nick := context.PostForm("nick")
message := context.DefaultPostForm("message", "m")
context.JSON(http.StatusOK, gin.H{
"id" : id,
"page" : page,
"nick" : nick,
"message" : message,
})
}

image-20220313160810654

JSON、Protobuf

json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"github.com/gin-gonic/gin"
"net/http"
)

func main() {
r := gin.Default()
r.GET("/moreJSON", moreJSON)
r.Run()
}

func moreJSON(context *gin.Context) {
var msg struct{
Name string `json:"user"`
Message string
Number int
}
msg.Name = "xxx"
msg.Message = "this is a test json"
msg.Number = 20
context.JSON(http.StatusOK, msg)
}

image-20220313161724013

protobuf

1
2
3
4
5
6
7
8
9
syntax = "proto3";

option go_package = "example.com/gin;proto";

message Teacher {
string name = 1;
repeated string course = 2;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"example.com/gin/05/proto"
"github.com/gin-gonic/gin"
"net/http"
)

func main() {
r := gin.Default()
r.GET("/someProtobuf", returnProto)
r.Run()
}

func returnProto(context *gin.Context) {
course := []string{"python", "blockchain", "golang"}
user := &proto.Teacher{
Name: "kennyS",
Course: course,
}
context.ProtoBuf(http.StatusOK, user)
}
1
2
E:\Project\Go\src\gin\05\proto>protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative u
ser.proto

image-20220313165629566

pureJSON

generally, json transfers html character into corresponding unicode character

such as < to /u003c. pureJSON helps us keep the same html

1
r.GET("/pureJSON", pureJSON)
1
2
3
4
5
func pureJSON(context *gin.Context) {
context.PureJSON(http.StatusOK, gin.H{
"html" : "<b>Hello, world</b>",
})
}
1
{"html":"<b>Hello, world</b>"}

Form validation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package main

import (
"github.com/gin-gonic/gin"
"net/http"
)

type LoginForm struct {
User string `json:"user" binding:"required,min=3,max=10"`
Password string `json:"password" binding:"required"`
}
type SignUpForm struct {
Age uint8 `json:"age" binding:"gte=1,lte=130"`
Name string `json:"name" binding:"required,min=3"`
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required"`
RePassword string `json:"rePassword" binding:"required,eqfield=Password"` //Cross-Field validation
}

func main() {
r := gin.Default()
//login
r.POST("/loginJSON", func(context *gin.Context) {
var loginForm LoginForm
if err := context.ShouldBind(&loginForm); err != nil {
context.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
context.JSON(http.StatusOK, gin.H{
"message" : "logged in successfully",
})
})
//signup
r.POST("/signUp", func(context *gin.Context) {
var signUpForm SignUpForm
if err := context.ShouldBind(&signUpForm); err != nil {
context.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}
context.JSON(http.StatusOK, gin.H{
"message" : "signed up successfully",
})
})
r.Run()
}

image-20220314193914893

image-20220314194143660

validator err translate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package main

import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/locales/en"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
en2 "github.com/go-playground/validator/v10/translations/en"
zh2 "github.com/go-playground/validator/v10/translations/zh"
"net/http"
)

//global translator
var trans ut.Translator

type LoginForm struct {
User string `json:"user" binding:"required,min=3,max=10"`
Password string `json:"password" binding:"required"`
}

func InitTrans(locale string)(err error){
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
zhT := zh.New() //chinese translator
enT := en.New() //english translator
//the first is spare
uni := ut.New(enT, zhT, enT)
trans, ok = uni.GetTranslator(locale)
if !ok {
return fmt.Errorf("uni.GetTranslator(%s)", locale)
}
switch locale {
case "en":
en2.RegisterDefaultTranslations(v, trans)
case "zh":
zh2.RegisterDefaultTranslations(v, trans)
default:
en2.RegisterDefaultTranslations(v, trans)
}
return
}
return
}


func main() {
if err := InitTrans("zh"); err != nil{
fmt.Println("init failed!")
return
}

r := gin.Default()
//login
r.POST("/loginJSON", func(context *gin.Context) {
var loginForm LoginForm
if err := context.ShouldBind(&loginForm); err != nil {
errs, ok := err.(validator.ValidationErrors)
if !ok{
context.JSON(http.StatusOK, gin.H{
"msg" : err.Error(),
})
}
context.JSON(http.StatusBadRequest, gin.H{
"error": errs.Translate(trans),
})
return
}
context.JSON(http.StatusOK, gin.H{
"message" : "logged in successfully",
})
})

r.Run()
}

image-20220314202617476

if we don’t want the User field to appear but make it to ‘json tag’ we define in the struct

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
func InitTrans(locale string)(err error){
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {

//-------------------------------------------------------------
//register a get-jsonTag customized method
v.RegisterTagNameFunc(func(fld reflect.StructField)string{
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
if name == "-"{
return ""
}
return name
})
//---------------------------------------------------------------

zhT := zh.New() //chinese translator
enT := en.New() //english translator
//the first is spare
uni := ut.New(enT, zhT, enT)
trans, ok = uni.GetTranslator(locale)
if !ok {
return fmt.Errorf("uni.GetTranslator(%s)", locale)
}
switch locale {
case "en":
en2.RegisterDefaultTranslations(v, trans)
case "zh":
zh2.RegisterDefaultTranslations(v, trans)
default:
en2.RegisterDefaultTranslations(v, trans)
}
return
}
return
}

now:

image-20220314205834582

also we don’t want LoginForm:

1
2
3
4
5
6
7
8
9
10
11
func removeTopStruct(fields map[string]string) map[string]string  {
rsp := map[string]string{}
for field, err := range fields{
rsp[field[strings.Index(field, ".")+1:]] = err
}
return rsp
}

context.JSON(http.StatusBadRequest, gin.H{
"error": removeTopStruct(errs.Translate(trans)),
})

image-20220314213452143

customized middleware

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package main

import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"time"
)

func MyLogger() gin.HandlerFunc {
return func(context *gin.Context) {
t := time.Now()
context.Set("example", "123")
//keep execute, index++
context.Next()

end := time.Since(t)
fmt.Printf("wasted time:%v", end)
fmt.Printf("status: %v", context.Writer.Status())
}
}

func main(){
r := gin.New()
//use Logger and recovery middleware
r.Use(gin.Logger(), gin.Recovery())
//use MyLogger()
r.Use(MyLogger())

r.GET("/test", func(context *gin.Context) {
context.JSON(http.StatusOK, gin.H{
"message":"666",
})
})

r.Run()

}

image-20220318134723133

set static path and html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"os"
"path/filepath"
)

func main() {
r := gin.Default()

dir, _ := filepath.Abs(filepath.Dir(os.Args[0]))
fmt.Println(dir) //C:\Users\Arison\AppData\Local\Temp

r.LoadHTMLFiles("templates/index.tmpl")
r.GET("/index", func(context *gin.Context) {
context.HTML(http.StatusOK, "index.tmpl", gin.H{
"title":"Issak",
})
})
r.Run()
}
1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>title</title>
</head>
<body>
<h1>
{{ .title }}
</h1>
</body>
</html>

image-20220318142658871

goland builds go files in C:\Users\Arison\AppData\Local\Temp, it’s a temp path, so the static html file can’t be found. We should use teminal to build an exe file and run it. Or use absolute path but not recommended

image-20220318143127435

to load all files in a path

1
r.LoadHTMLGlob("template/*")

same name in different path

image-20220318145101063

main.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"github.com/gin-gonic/gin"
"net/http"
)

func main() {
r := gin.Default()

r.LoadHTMLGlob("templates/**/*")

r.GET("/index", func(context *gin.Context) {
context.HTML(http.StatusOK, "index.tmpl", gin.H{
"title":"Issak",
})
})
r.GET("/goods/list", func(context *gin.Context) {
context.HTML(http.StatusOK, "goods/list.html", gin.H{
"title":"Issak",
})
})
r.GET("/users/list", func(context *gin.Context) {
context.HTML(http.StatusOK, "users/list.html", gin.H{
"title":"Issak",
})
})
r.Run()
}

goods/list

1
2
3
4
5
6
7
8
9
10
11
12
{{define "goods/list.html"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>goods list</h1>
</body>
</html>
{{end}}

users/list

1
2
3
4
5
6
7
8
9
10
11
12
{{define "users/list.html"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>users list</h1>
</body>
</html>
{{end}}

load static files

main.go

prefix with /static would be replace with ./static to find the local static files

1
r.Static("/static", "./static")

image-20220318145956261

users/list.html

1
2
3
4
5
6
7
8
9
10
11
12
13
{{define "users/list.html"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<h1>users list</h1>
</body>
</html>
{{end}}

graceful restart or stop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"os"
"os/signal"
"syscall"
)

func main() {
r := gin.Default()
r.GET("/", func(context *gin.Context) {
context.JSON(http.StatusOK, gin.H{
"msg":"66",
})
})
go func() {
r.Run()
}()

//receive signal
quit := make(chan os.Signal)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit

//Processing subsequent logic
fmt.Println("shutdown server")
fmt.Println("logout service")
}