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

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

}


猜你喜欢

转载自blog.51cto.com/wuguiyunwei/2131563