为了账号安全,请及时绑定邮箱和手机立即绑定

Gin Web Framework 中文版

标签:
Go


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

go get github.com/gin-gonic/gin

在你的代码中导入它:

1

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

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

1

import "net/http"

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

go get govendor

1

$ go get github.com/kardianos/govendor

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

1

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

Vendor init your project and add gin

1

2

$ govendor init

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

在项目中复制起始模板

1

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

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头来推断活页夹。如果你确定你是绑定的,你可以使用MustBindWith或ShouldBindWith。

您也可以指定特定字段是必需的。如果一个字段装饰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": "foo@bar.com", "phone": "123433"},

"austin": gin.H{"email": "austin@example.com", "phone": "666"},

"lena":   gin.H{"email": "lena@guapa.com", "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())

}

golangpythonc++


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 1
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消