Gin是用Go(Golang)编写的一个网页框架。它具有类似马提尼的API,具有更好的性能,由于httprouter,速度提高了40倍。 乌龟运维

1

2

#在example.go文件中假定以下代码

$ cat example.go


1

2

3

4

5

6

7

8

9

10

11

12

13

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

}


1

2

# run example.go and visit 0.0.0.0:8080/ping on browser

$ go run example.go


Benchmarks

Gin uses a custom version of HttpRouter

See all benchmarks

Benchmark name (1) (2) (3) (4)
BenchmarkGin_GithubAll 30000 48375 0 0
BenchmarkAce_GithubAll 10000 134059 13792 167
BenchmarkBear_GithubAll 5000 534445 86448 943
BenchmarkBeego_GithubAll 3000 592444 74705 812
BenchmarkBone_GithubAll 200 6957308 698784 8453
BenchmarkDenco_GithubAll 10000 158819 20224 167
BenchmarkEcho_GithubAll 10000 154700 6496 203
BenchmarkGocraftWeb_GithubAll 3000 570806 131656 1686
BenchmarkGoji_GithubAll 2000 818034 56112 334
BenchmarkGojiv2_GithubAll 2000 1213973 274768 3712
BenchmarkGoJsonRest_GithubAll 2000 785796 134371 2737
BenchmarkGoRestful_GithubAll 300 5238188 689672 4519
BenchmarkGorillaMux_GithubAll 100 10257726 211840 2272
BenchmarkHttpRouter_GithubAll 20000 105414 13792 167
BenchmarkHttpTreeMux_GithubAll 10000 319934 65856 671
BenchmarkKocha_GithubAll 10000 209442 23304 843
BenchmarkLARS_GithubAll 20000 62565 0 0
BenchmarkMacaron_GithubAll 2000 1161270 204194 2000
BenchmarkMartini_GithubAll 200 9991713 226549 2325
BenchmarkPat_GithubAll 200 5590793 1499568 27435
BenchmarkPossum_GithubAll 10000 319768 84448 609
BenchmarkR2router_GithubAll 10000 305134 77328 979
BenchmarkRivet_GithubAll 10000 132134 16272 167
BenchmarkTango_GithubAll 3000 552754 63826 1618
BenchmarkTigerTonic_GithubAll 1000 1439483 239104 5374
BenchmarkTraffic_GithubAll 100 11383067 2659329 21848
BenchmarkVulcan_GithubAll 5000 394253 19894 609
  • (1):总重复次数达到的时间越长,意味着越有信心的结果

  • (2):单次重复持续时间(ns / op),越低越好

  • (3):堆内存(B / op),越低越好

  • (4):每个重复的平均分配(分配/操作),越低越好

Gin v1. stable

  •  零分配路由器。

  • 仍然是最快的http路由器和框架。从路由到写作。

  •  完整的单元测试套件

  •  测试战斗

  •  API冻结,新版本不会破坏你的代码。

开始使用它

  1. 下载并安装它


1

go get github.com/gin-gonic/gin


  1. 在你的代码中导入它:


1

import "github.com/gin-gonic/gin"


  1. (可选)导入net/http。例如,如果使用常量如http.StatusOK


1

import "net/http"


使用像Govendor这样的供应商工具

  1. go get govendor


1

$ go get github.com/kardianos/govendor


  1. 创建你的项目文件夹cd到里面


1

$ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_"


  1. Vendor init your project and add gin


1

2

$ govendor init

$ govendor fetch github.com/gin-gonic/gin@v1.2


  1. 在项目中复制起始模板


1

$ curl https://raw.githubusercontent.com/gin-gonic/gin/master/examples/basic/main.go > main.go


  1. Run your project


1

$ go run main.go


用jsoniter构建

Ginencoding/json用作默认的json包,但你可以通过从其他标签建立更改为jsoniter。

1

$ go build -tags=jsoniter .


API Examples

Using GET, POST, PUT, PATCH, DELETE and OPTIONS


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

func main() {

// Disable Console Color

// gin.DisableConsoleColor()

 

// Creates a gin router with default middleware:

// logger and recovery (crash-free) middleware

router := gin.Default()

 

router.GET("/someGet", getting)

router.POST("/somePost", posting)

router.PUT("/somePut", putting)

router.DELETE("/someDelete", deleting)

router.PATCH("/somePatch", patching)

router.HEAD("/someHead", head)

router.OPTIONS("/someOptions", options)

 

// By default it serves on :8080 unless a

// PORT environment variable was defined.

router.Run()

// router.Run(":3000") for a hard coded port

}


路径中的参数


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

func main() {

router := gin.Default()

 

// This handler will match /user/john but will not match neither /user/ or /user

router.GET("/user/:name", func(c *gin.Context) {

name := c.Param("name")

c.String(http.StatusOK, "Hello %s", name)

})

 

// However, this one will match /user/john/ and also /user/john/send

// If no other routers match /user/john, it will redirect to /user/john/

router.GET("/user/:name/*action", func(c *gin.Context) {

name := c.Param("name")

action := c.Param("action")

message := name + " is " + action

c.String(http.StatusOK, message)

})

 

router.Run(":8080")

}


查询字符串参数


1

2

3

4

5

6

7

8

9

10

11

12

13

func main() {

router := gin.Default()

 

// Query string parameters are parsed using the existing underlying request object.

// The request responds to a url matching:  /welcome?firstname=Jane&lastname=Doe

router.GET("/welcome", func(c *gin.Context) {

firstname := c.DefaultQuery("firstname", "Guest")

lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")

 

c.String(http.StatusOK, "Hello %s %s", firstname, lastname)

})

router.Run(":8080")

}


Multipart/Urlencoded Form


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

func main() {

router := gin.Default()

 

router.POST("/form_post", func(c *gin.Context) {

message := c.PostForm("message")

nick := c.DefaultPostForm("nick", "anonymous")

 

c.JSON(200, gin.H{

"status":  "posted",

"message": message,

"nick":    nick,

})

})

router.Run(":8080")

}


Another example: query + post form


1

2

3

4

POST /post?id=1234&page=1 HTTP/1.1

Content-Type: application/x-www-form-urlencoded

 

name=manu&message=this_is_great


1

2

3

4

5

6

7

8

9

10

11

12

13

14

func main() {

router := gin.Default()

 

router.POST("/post", func(c *gin.Context) {

 

id := c.Query("id")

page := c.DefaultQuery("page", "0")

name := c.PostForm("name")

message := c.PostForm("message")

 

fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message)

})

router.Run(":8080")

}


1

id: 1234; page: 1; name: manu; message: this_is_great


Upload files

单个文件

引用问题#774和详细示例代码。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

func main() {

router := gin.Default()

// Set a lower memory limit for multipart forms (default is 32 MiB)

// router.MaxMultipartMemory = 8 << 20  // 8 MiB

router.POST("/upload", func(c *gin.Context) {

// single file

file, _ := c.FormFile("file")

log.Println(file.Filename)

 

// Upload the file to specific dst.

// c.SaveUploadedFile(file, dst)

 

c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))

})

router.Run(":8080")

}

How to curl:

1

2

3

curl -X POST http://localhost:8080/upload \

  -F "file=@/Users/appleboy/test.zip" \

  -H "Content-Type: multipart/form-data"


Multiple files

查看详细的示例代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

func main() {

router := gin.Default()

// Set a lower memory limit for multipart forms (default is 32 MiB)

// router.MaxMultipartMemory = 8 << 20  // 8 MiB

router.POST("/upload", func(c *gin.Context) {

// Multipart form

form, _ := c.MultipartForm()

files := form.File["upload[]"]

 

for _, file := range files {

log.Println(file.Filename)

 

// Upload the file to specific dst.

// c.SaveUploadedFile(file, dst)

}

c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))

})

router.Run(":8080")

}

How to curl:

1

2

3

4

curl -X POST http://localhost:8080/upload \

  -F "upload[]=@/Users/appleboy/test1.zip" \

  -F "upload[]=@/Users/appleboy/test2.zip" \

  -H "Content-Type: multipart/form-data"


Grouping routes


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

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")

}


没有中间件的默认空白Gin

使用

1

r := gin.New()

代替

1

2

// Default With the Logger and Recovery middleware already attached

r := gin.Default()


使用中间件


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

func main() {

// Creates a router without any middleware by default

r := gin.New()

 

// Global middleware

// Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release.

// By default gin.DefaultWriter = os.Stdout

r.Use(gin.Logger())

 

// Recovery middleware recovers from any panics and writes a 500 if there was one.

r.Use(gin.Recovery())

 

// Per route middleware, you can add as many as you desire.

r.GET("/benchmark", MyBenchLogger(), benchEndpoint)

 

// Authorization group

// authorized := r.Group("/", AuthRequired())

// exactly the same as:

authorized := r.Group("/")

// per group middleware! in this case we use the custom created

// AuthRequired() middleware just in the "authorized" group.

authorized.Use(AuthRequired())

{

authorized.POST("/login", loginEndpoint)

authorized.POST("/submit", submitEndpoint)

authorized.POST("/read", readEndpoint)

 

// nested group

testing := authorized.Group("testing")

testing.GET("/analytics", analyticsEndpoint)

}

 

// Listen and serve on 0.0.0.0:8080

r.Run(":8080")

}


如何写日志文件


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

func main() {

    // Disable Console Color, you don't need console color when writing the logs to file.

    gin.DisableConsoleColor()

 

    // Logging to a file.

    f, _ := os.Create("gin.log")

    gin.DefaultWriter = io.MultiWriter(f)

 

    // Use the following code if you need to write the logs to file and console at the same time.

    // gin.DefaultWriter = io.MultiWriter(f, os.Stdout)

 

    router := gin.Default()

    router.GET("/ping", func(c *gin.Context) {

        c.String(200, "pong")

    })

 

    router.Run(":8080")

}


模型绑定和验证

要将请求主体绑定到一个类型,使用模型绑定。我们目前支持绑定JSON,XML和标准表单值(foo = bar&boo = baz)。

杜松子酒使用go-playground / validator.v8进行验证。在这里查看关于标签使用情况的完整文档。

请注意,您需要在要绑定的所有字段上设置相应的绑定标签。例如,从JSON绑定时,设置json:"fieldname"

另外,杜松子提供了两套绑定方法:

  • 类型 – 必须绑定

    • 方法 – ,,BindBindJSONBindQuery

    • 行为 – 这些方法MustBindWith在引擎盖下使用。如果存在绑定错误,则请求被中止c.AbortWithError(400, err).SetType(ErrorTypeBind)。这将响应状态码设置为400,并将Content-Type标题设置为text/plain; charset=utf-8。请注意,如果在此之后尝试设置响应代码,将会导致警告[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422。如果您希望更好地控制行为,请考虑使用ShouldBind等效的方法。

  • 类型 – 应该绑定

    • 方法 – ,,ShouldBindShouldBindJSONShouldBindQuery

    • 行为 – 这些方法ShouldBindWith在引擎盖下使用。如果发生绑定错误,则返回错误,开发人员有责任正确处理请求和错误。

当使用绑定方法时,杜松子试图根据Content-Type头来推断活页夹。如果你确定你是绑定的,你可以使用MustBindWithShouldBindWith

您也可以指定特定字段是必需的。如果一个字段装饰binding:"required"并绑定时有一个空值,将返回一个错误。

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

// Binding from JSON

type Login struct {

User     string `form:"user" json:"user" binding:"required"`

Password string `form:"password" json:"password" binding:"required"`

}

 

func main() {

router := gin.Default()

 

// Example for binding JSON ({"user": "manu", "password": "123"})

router.POST("/loginJSON", func(c *gin.Context) {

var json Login

if err := c.ShouldBindJSON(&json); err == nil {

if json.User == "manu" && json.Password == "123" {

c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})

} else {

c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})

}

} else {

c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})

}

})

 

// Example for binding a HTML form (user=manu&password=123)

router.POST("/loginForm", func(c *gin.Context) {

var form Login

// This will infer what binder to use depending on the content-type header.

if err := c.ShouldBind(&form); err == nil {

if form.User == "manu" && form.Password == "123" {

c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})

} else {

c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})

}

} else {

c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})

}

})

 

// Listen and serve on 0.0.0.0:8080

router.Run(":8080")

}

Sample request

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

$ curl -v -X POST \

  http://localhost:8080/loginJSON \

  -H 'content-type: application/json' \

  -d '{ "user": "manu" }'

> POST /loginJSON HTTP/1.1

> Host: localhost:8080

> User-Agent: curl/7.51.0

> Accept: */*

> content-type: application/json

> Content-Length: 18

>

* upload completely sent off: 18 out of 18 bytes

< HTTP/1.1 400 Bad Request

< Content-Type: application/json; charset=utf-8

< Date: Fri, 04 Aug 2017 03:51:31 GMT

< Content-Length: 100

<

{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"}


自定义验证器

也可以注册自定义验证器。请参阅示例代码。

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

package main

 

import (

"net/http"

"reflect"

"time"

 

"github.com/gin-gonic/gin"

"github.com/gin-gonic/gin/binding"

"gopkg.in/go-playground/validator.v8"

)

 

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.YearDay() > date.YearDay() {

return false

}

}

return true

}

 

func main() {

route := gin.Default()

binding.Validator.RegisterValidation("bookabledate", bookableDate)

route.GET("/bookable", getBookable)

route.Run(":8085")

}

 

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

2

3

4

5

$ curl "localhost:8085/bookable?check_in=2017-08-16&check_out=2017-08-17"

{"message":"Booking dates are valid!"}

 

$ curl "localhost:8085/bookable?check_in=2017-08-15&check_out=2017-08-16"

{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}


只绑定查询字符串

ShouldBindQuery函数只绑定查询参数,而不是发布数据。查看详细信息。

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

package main

 

import (

"log"

 

"github.com/gin-gonic/gin"

)

 

type Person struct {

Name    string `form:"name"`

Address string `form:"address"`

}

 

func main() {

route := gin.Default()

route.Any("/testing", startPage)

route.Run(":8085")

}

 

func startPage(c *gin.Context) {

var person Person

if c.ShouldBindQuery(&person) == nil {

log.Println("====== Only Bind By Query String ======")

log.Println(person.Name)

log.Println(person.Address)

}

c.String(200, "Success")

}


绑定查询字符串或发布数据

查看详细信息。

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 "log"

import "github.com/gin-gonic/gin"

import "time"

 

type Person struct {

Name     string    `form:"name"`

Address  string    `form:"address"`

Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`

}

 

func main() {

route := gin.Default()

route.GET("/testing", startPage)

route.Run(":8085")

}

 

func startPage(c *gin.Context) {

var person Person

// If `GET`, only `Form` binding engine (`query`) used.

// If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`).

// See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48

if c.ShouldBind(&person) == nil {

log.Println(person.Name)

log.Println(person.Address)

log.Println(person.Birthday)

}

 

c.String(200, "Success")

}

Test it with:

1

$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15"


绑定HTML复选框

查看详细信息

main.go

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

...

 

type myForm struct {

    Colors []string `form:"colors[]"`

}

 

...

 

func formHandler(c *gin.Context) {

    var fakeForm myForm

    c.ShouldBind(&fakeForm)

    c.JSON(200, gin.H{"color": fakeForm.Colors})

}

 

...

form.html

1

2

3

4

5

6

7

8

9

10

<form action="/" method="POST">

    <p>Check some colors</p>

    <label for="red">Red</label>

    <input type="checkbox" name="colors[]" value="red" id="red" />

    <label for="green">Green</label>

    <input type="checkbox" name="colors[]" value="green" id="green" />

    <label for="blue">Blue</label>

    <input type="checkbox" name="colors[]" value="blue" id="blue" />

    <input type="submit" />

</form>

result:

1

{"color":["red","green","blue"]}


Multipart/Urlencoded binding


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

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) {

// you can bind multipart form with explicit binding declaration:

// c.ShouldBindWith(&form, binding.Form)

// or you can simply use autobinding with ShouldBind method:

var form LoginForm

// in this case proper binding will be automatically selected

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 it with:

1

$ curl -v --form user=user --form password=password http://localhost:8080/login


XML,JSON和YAML (rendering)渲染


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 main() {

r := gin.Default()

 

// gin.H is a shortcut for map[string]interface{}

r.GET("/someJSON", func(c *gin.Context) {

c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})

})

 

r.GET("/moreJSON", func(c *gin.Context) {

// You also can use a struct

var msg struct {

Name    string `json:"user"`

Message string

Number  int

}

msg.Name = "Lena"

msg.Message = "hey"

msg.Number = 123

// Note that msg.Name becomes "user" in the JSON

// Will output  :   {"user": "Lena", "Message": "hey", "Number": 123}

c.JSON(http.StatusOK, msg)

})

 

r.GET("/someXML", func(c *gin.Context) {

c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})

})

 

r.GET("/someYAML", func(c *gin.Context) {

c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})

})

 

// Listen and serve on 0.0.0.0:8080

r.Run(":8080")

}


SecureJSON

使用SecureJSON来防止json劫持。"while(1),"如果给定的结构体是数组值,那么缺省前置于响应主体。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

func main() {

r := gin.Default()

 

// You can also use your own secure json prefix

// r.SecureJsonPrefix(")]}',\n")

 

r.GET("/someJSON", func(c *gin.Context) {

names := []string{"lena", "austin", "foo"}

 

// Will output  :   while(1);["lena","austin","foo"]

c.SecureJSON(http.StatusOK, names)

})

 

// Listen and serve on 0.0.0.0:8080

r.Run(":8080")

}


提供静态文件


1

2

3

4

5

6

7

8

9

func main() {

router := gin.Default()

router.Static("/assets", "./assets")

router.StaticFS("/more_static", http.Dir("my_file_system"))

router.StaticFile("/favicon.ico", "./resources/favicon.ico")

 

// Listen and serve on 0.0.0.0:8080

router.Run(":8080")

}


HTML (rendering)渲染

使用LoadHTMLGlob()或LoadHTMLFiles()

1

2

3

4

5

6

7

8

9

10

11

func main() {

router := gin.Default()

router.LoadHTMLGlob("templates/*")

//router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")

router.GET("/index", func(c *gin.Context) {

c.HTML(http.StatusOK, "index.tmpl", gin.H{

"title": "Main website",

})

})

router.Run(":8080")

}

templates/index.tmpl

1

2

3

4

5

<html>

<h1>

{{ .title }}

</h1>

</html>

在不同的目录中使用同名的模板

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

func main() {