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 |
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):每个重复的平均分配(分配/操作),越低越好
零分配路由器。
仍然是最快的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" |
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 |
Ginencoding/json
用作默认的json包,但你可以通过从其他标签建立更改为jsoniter。
1 | $ go build -tags=jsoniter . |
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") } |
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") } |
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 |
引用问题#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" |
查看详细的示例代码
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" |
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") } |
使用
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"
。
另外,杜松子提供了两套绑定方法:
类型 – 必须绑定
方法 – ,,Bind
BindJSON
BindQuery
行为 – 这些方法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
等效的方法。
类型 – 应该绑定
方法 – ,,ShouldBind
ShouldBindJSON
ShouldBindQuery
行为 – 这些方法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" |
查看详细信息
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"]} |
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 |
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来防止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") } |
使用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> <h2> {{ .title }} </h2> </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><h2> {{ .title }} </h2> <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><h2> {{ .title }} </h2> <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 |
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") } |
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") } |
在中间件或处理程序中启动新的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.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)) } |
查看问题并尝试以下示例:
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()) } |
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。