Gin Web Framework 中文版


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_GithubAll300004837500
BenchmarkAce_GithubAll1000013405913792167
BenchmarkBear_GithubAll500053444586448943
BenchmarkBeego_GithubAll300059244474705812
BenchmarkBone_GithubAll20069573086987848453
BenchmarkDenco_GithubAll1000015881920224167
BenchmarkEcho_GithubAll100001547006496203
BenchmarkGocraftWeb_GithubAll30005708061316561686
BenchmarkGoji_GithubAll200081803456112334
BenchmarkGojiv2_GithubAll200012139732747683712
BenchmarkGoJsonRest_GithubAll20007857961343712737
BenchmarkGoRestful_GithubAll30052381886896724519
BenchmarkGorillaMux_GithubAll100102577262118402272
BenchmarkHttpRouter_GithubAll2000010541413792167
BenchmarkHttpTreeMux_GithubAll1000031993465856671
BenchmarkKocha_GithubAll1000020944223304843
BenchmarkLARS_GithubAll200006256500
BenchmarkMacaron_GithubAll200011612702041942000
BenchmarkMartini_GithubAll20099917132265492325
BenchmarkPat_GithubAll2005590793149956827435
BenchmarkPossum_GithubAll1000031976884448609
BenchmarkR2router_GithubAll1000030513477328979
BenchmarkRivet_GithubAll1000013213416272167
BenchmarkTango_GithubAll3000552754638261618
BenchmarkTigerTonic_GithubAll100014394832391045374
BenchmarkTraffic_GithubAll10011383067265932921848
BenchmarkVulcan_GithubAll500039425319894609
  • (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() {

router := gin.Default()

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

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

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

"title": "Posts",

})

})

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

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

"title": "Users",

})

})

router.Run(":8080")

}

templates/posts/index.tmpl

1

2

3

4

5

6

7

{{ define "posts/index.tmpl" }}

<html><h1>

{{ .title }}

</h1>

<p>Using posts/index.tmpl</p>

</html>

{{ end }}


1

templates/users/index.tmpl


1

2

3

4

5

6

7

{{ define "users/index.tmpl" }}

<html><h1>

{{ .title }}

</h1>

<p>Using users/index.tmpl</p>

</html>

{{ end }}


自定義模板渲染器

你也可以使用你自己的html模板渲染

1

2

3

4

5

6

7

8

import "html/template"

 

func main() {

router := gin.Default()

html := template.Must(template.ParseFiles("file1", "file2"))

router.SetHTMLTemplate(html)

router.Run(":8080")

}


自定義分隔符

您可以使用自定義分隔符

1

2

3

r := gin.Default()

r.Delims("{[{", "}]}")

r.LoadHTMLGlob("/path/to/templates"))


自定義模板功能

查看詳細的示例代碼

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

import (

    "fmt"

    "html/template"

    "net/http"

    "time"

 

    "github.com/gin-gonic/gin"

)

 

func formatAsDate(t time.Time) string {

    year, month, day := t.Date()

    return fmt.Sprintf("%d%02d/%02d", year, month, day)

}

 

func main() {

    router := gin.Default()

    router.Delims("{[{", "}]}")

    router.SetFuncMap(template.FuncMap{

        "formatAsDate": formatAsDate,

    })

    router.LoadHTMLFiles("./fixtures/basic/raw.tmpl")

 

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

        c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{

            "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),

        })

    })

 

    router.Run(":8080")

}

raw.tmpl

1

Date: {[{.now | formatAsDate}]}

Result:

1

Date: 2017/07/01


Multitemplate

Gin允許默認只使用一個html.Template。檢查使用功能的多模板渲染,如go 1.6 block template

重定向

發出HTTP重定向很簡單:

1

2

3

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

c.Redirect(http.StatusMovedPermanently, "http://www.google.com/")

})

內部和外部位置均受支持。

自定義中間件


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 Logger() gin.HandlerFunc {

return func(c *gin.Context) {

t := time.Now()

 

// Set example variable

c.Set("example", "12345")

 

// before request

 

c.Next()

 

// after request

latency := time.Since(t)

log.Print(latency)

 

// access the status we are sending

status := c.Writer.Status()

log.Println(status)

}

}

 

func main() {

r := gin.New()

r.Use(Logger())

 

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

example := c.MustGet("example").(string)

 

// it would print: "12345"

log.Println(example)

})

 

// Listen and serve on 0.0.0.0:8080

r.Run(":8080")

}


使用BasicAuth()中間件


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

//模擬一些私人數據

var secrets = gin.H{

"foo":    gin.H{"email": "[email protected]", "phone": "123433"},

"austin": gin.H{"email": "[email protected]", "phone": "666"},

"lena":   gin.H{"email": "[email protected]", "phone": "523443"},

}

 

func main() {

r := gin.Default()

 

// Group using gin.BasicAuth() middleware

// gin.Accounts is a shortcut for map[string]string

authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{

"foo":    "bar",

"austin": "1234",

"lena":   "hello2",

"manu":   "4321",

}))

 

// /admin/secrets endpoint

// hit "localhost:8080/admin/secrets

authorized.GET("/secrets", func(c *gin.Context) {

// get user, it was set by the BasicAuth middleware

user := c.MustGet(gin.AuthUserKey).(string)

if secret, ok := secrets[user]; ok {

c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})

} else {

c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})

}

})

 

// Listen and serve on 0.0.0.0:8080

r.Run(":8080")

}


Goroutines在一箇中間件裏面

在中間件或處理程序中啓動新的Goroutine時,不應使用其中的原始上下文,而必須使用只讀副本。

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

func main() {

r := gin.Default()

 

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

// create copy to be used inside the goroutine

cCp := c.Copy()

go func() {

// simulate a long task with time.Sleep(). 5 seconds

time.Sleep(5 * time.Second)

 

// note that you are using the copied context "cCp", IMPORTANT

log.Println("Done! in path " + cCp.Request.URL.Path)

}()

})

 

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

// simulate a long task with time.Sleep(). 5 seconds

time.Sleep(5 * time.Second)

 

// since we are NOT using a goroutine, we do not have to copy the context

log.Println("Done! in path " + c.Request.URL.Path)

})

 

// Listen and serve on 0.0.0.0:8080

r.Run(":8080")

}


自定義HTTP配置

http.ListenAndServe()直接使用,如下所示:

1

2

3

4

func main() {

router := gin.Default()

http.ListenAndServe(":8080", router)

}

1

2

3

4

5

6

7

8

9

10

11

12

func main() {

router := gin.Default()

 

s := &http.Server{

Addr:           ":8080",

Handler:        router,

ReadTimeout:    10 * time.Second,

WriteTimeout:   10 * time.Second,

MaxHeaderBytes: 1 << 20,

}

s.ListenAndServe()

}


支持讓我們加密

1行LetsEncrypt HTTPS服務器的示例。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

package main

 

import (

"log"

 

"github.com/gin-gonic/autotls"

"github.com/gin-gonic/gin"

)

 

func main() {

r := gin.Default()

 

// Ping handler

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

c.String(200, "pong")

})

 

log.Fatal(autotls.Run(r, "example1.com", "example2.com"))

}

自定義autocert管理器的例子。

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 (

"log"

 

"github.com/gin-gonic/autotls"

"github.com/gin-gonic/gin"

"golang.org/x/crypto/acme/autocert"

)

 

func main() {

r := gin.Default()

 

// Ping handler

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

c.String(200, "pong")

})

 

m := autocert.Manager{

Prompt:     autocert.AcceptTOS,

HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"),

Cache:      autocert.DirCache("/var/www/.cache"),

}

 

log.Fatal(autotls.RunWithManager(r, &m))

}


使用Gin運行多個服務

查看問題並嘗試以下示例:

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

package main

 

import (

"log"

"net/http"

"time"

 

"github.com/gin-gonic/gin"

"golang.org/x/sync/errgroup"

)

 

var (

g errgroup.Group

)

 

func router01() http.Handler {

e := gin.New()

e.Use(gin.Recovery())

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

c.JSON(

http.StatusOK,

gin.H{

"code":  http.StatusOK,

"error": "Welcome server 01",

},

)

})

 

return e

}

 

func router02() http.Handler {

e := gin.New()

e.Use(gin.Recovery())

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

c.JSON(

http.StatusOK,

gin.H{

"code":  http.StatusOK,

"error": "Welcome server 02",

},

)

})

 

return e

}

 

func main() {

server01 := &http.Server{

Addr:         ":8080",

Handler:      router01(),

ReadTimeout:  5 * time.Second,

WriteTimeout: 10 * time.Second,

}

 

server02 := &http.Server{

Addr:         ":8081",

Handler:      router02(),

ReadTimeout:  5 * time.Second,

WriteTimeout: 10 * time.Second,

}

 

g.Go(func() error {

return server01.ListenAndServe()

})

 

g.Go(func() error {

return server02.ListenAndServe()

})

 

if err := g.Wait(); err != nil {

log.Fatal(err)

}

}


優雅的重啓或停止

你想優雅地重新啓動或停止你的網絡服務器?有一些辦法可以做到。

我們可以使用fvbock / endless來替換默認值ListenAndServe。有關更多詳細信息,請參閱問題#296

1

2

3

4

router := gin.Default()

router.GET("/", handler)

// [...]

endless.ListenAndServe(":4242", router)

無止境的替代:

  • 禮貌:禮貌的Go HTTP服務器,優雅地關閉。

  • 優雅:優雅是一個Go包,可以正常關閉http.Handler服務器。

  • 寬限期:Go服務器的平穩重啓和零宕機部署。

如果你使用的是Go 1.8,你可能不需要使用這個庫。考慮使用http.Server內置的Shutdown()方法來正常關閉。用杜松子酒查看完整的關機示例。

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

// +build go1.8

 

package main

 

import (

"context"

"log"

"net/http"

"os"

"os/signal"

"time"

 

"github.com/gin-gonic/gin"

)

 

func main() {

router := gin.Default()

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

time.Sleep(5 * time.Second)

c.String(http.StatusOK, "Welcome Gin Server")

})

 

srv := &http.Server{

Addr:    ":8080",

Handler: router,

}

 

go func() {

// service connections

if err := srv.ListenAndServe(); err != nil {

log.Printf("listen: %s\n", err)

}

}()

 

// Wait for interrupt signal to gracefully shutdown the server with

// a timeout of 5 seconds.

quit := make(chan os.Signal)

signal.Notify(quit, os.Interrupt)

<-quit

log.Println("Shutdown Server ...")

 

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)

defer cancel()

if err := srv.Shutdown(ctx); err != nil {

log.Fatal("Server Shutdown:", err)

}

log.Println("Server exiting")

}


測試

net/http/httptest包是HTTP測試的首選方式。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

package main

 

func setupRouter() *gin.Engine {

r := gin.Default()

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

c.String(200, "pong")

})

return r

}

 

func main() {

r := setupRouter()

r.Run(":8080")

}

測試上面的代碼示例:

Go

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

package main

 

import (

"net/http"

"net/http/httptest"

"testing"

 

"github.com/stretchr/testify/assert"

)

 

func TestPingRoute(t *testing.T) {

router := setupRouter()

 

w := httptest.NewRecorder()

req, _ := http.NewRequest("GET", "/ping", nil)

router.ServeHTTP(w, req)

 

assert.Equal(t, 200, w.Code)

assert.Equal(t, "pong", w.Body.String())

}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章