您好,登錄后才能下訂單哦!
Gin是用Go(Golang)編寫的一個(gè)網(wǎng)頁框架。它具有類似馬提尼的API,具有更好的性能,由于httprouter,速度提高了40倍。 烏龜運(yùn)維
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):總重復(fù)次數(shù)達(dá)到的時(shí)間越長(zhǎng),意味著越有信心的結(jié)果
(2):?jiǎn)未沃貜?fù)持續(xù)時(shí)間(ns / op),越低越好
(3):堆內(nèi)存(B / op),越低越好
(4):每個(gè)重復(fù)的平均分配(分配/操作),越低越好
零分配路由器。
仍然是最快的http路由器和框架。從路由到寫作。
完整的單元測(cè)試套件
測(cè)試戰(zhàn)斗
API凍結(jié),新版本不會(huì)破壞你的代碼。
下載并安裝它
1 | go get github.com/gin-gonic/gin |
在你的代碼中導(dǎo)入它:
1 | import "github.com/gin-gonic/gin" |
(可選)導(dǎo)入net/http
。例如,如果使用常量如http.StatusOK
。
1 | import "net/http" |
go get
govendor
1 | $ go get github.com/kardianos/govendor |
創(chuàng)建你的項(xiàng)目文件夾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 |
在項(xiàng)目中復(fù)制起始模板
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
用作默認(rèn)的json包,但你可以通過從其他標(biāo)簽建立更改為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和詳細(xì)示例代碼。
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" |
查看詳細(xì)的示例代碼
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") } |
要將請(qǐng)求主體綁定到一個(gè)類型,使用模型綁定。我們目前支持綁定JSON,XML和標(biāo)準(zhǔn)表單值(foo = bar&boo = baz)。
杜松子酒使用go-playground / validator.v8進(jìn)行驗(yàn)證。在這里查看關(guān)于標(biāo)簽使用情況的完整文檔。
請(qǐng)注意,您需要在要綁定的所有字段上設(shè)置相應(yīng)的綁定標(biāo)簽。例如,從JSON綁定時(shí),設(shè)置json:"fieldname"
。
另外,杜松子提供了兩套綁定方法:
類型 – 必須綁定
方法 – ,,Bind
BindJSON
BindQuery
行為 – 這些方法MustBindWith
在引擎蓋下使用。如果存在綁定錯(cuò)誤,則請(qǐng)求被中止c.AbortWithError(400, err).SetType(ErrorTypeBind)
。這將響應(yīng)狀態(tài)碼設(shè)置為400,并將Content-Type
標(biāo)題設(shè)置為text/plain; charset=utf-8
。請(qǐng)注意,如果在此之后嘗試設(shè)置響應(yīng)代碼,將會(huì)導(dǎo)致警告[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422
。如果您希望更好地控制行為,請(qǐng)考慮使用ShouldBind
等效的方法。
類型 – 應(yīng)該綁定
方法 – ,,ShouldBind
ShouldBindJSON
ShouldBindQuery
行為 – 這些方法ShouldBindWith
在引擎蓋下使用。如果發(fā)生綁定錯(cuò)誤,則返回錯(cuò)誤,開發(fā)人員有責(zé)任正確處理請(qǐng)求和錯(cuò)誤。
當(dāng)使用綁定方法時(shí),杜松子試圖根據(jù)Content-Type頭來推斷活頁夾。如果你確定你是綁定的,你可以使用MustBindWith
或ShouldBindWith
。
您也可以指定特定字段是必需的。如果一個(gè)字段裝飾binding:"required"
并綁定時(shí)有一個(gè)空值,將返回一個(gè)錯(cuò)誤。
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"} |
也可以注冊(cè)自定義驗(yàn)證器。請(qǐng)參閱示例代碼。
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
函數(shù)只綁定查詢參數(shù),而不是發(fā)布數(shù)據(jù)。查看詳細(xì)信息。
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") } |
查看詳細(xì)信息。
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" |
查看詳細(xì)信息
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),"
如果給定的結(jié)構(gòu)體是數(shù)組值,那么缺省前置于響應(yīng)主體。
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")) |
查看詳細(xì)的示例代碼。
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允許默認(rèn)只使用一個(gè)html.Template。檢查使用功能的多模板渲染,如go 1.6 block template
。
發(fā)出HTTP重定向很簡(jiǎn)單:
1 2 3 | r.GET("/test", func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, "http://www.google.com/") }) |
內(nèi)部和外部位置均受支持。
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 | //模擬一些私人數(shù)據(jù) 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") } |
在中間件或處理程序中啟動(dòng)新的Goroutine時(shí),不應(yīng)使用其中的原始上下文,而必須使用只讀副本。
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服務(wù)器的示例。
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) } } |
你想優(yōu)雅地重新啟動(dòng)或停止你的網(wǎng)絡(luò)服務(wù)器?有一些辦法可以做到。
我們可以使用fvbock / endless來替換默認(rèn)值ListenAndServe
。有關(guān)更多詳細(xì)信息,請(qǐng)參閱問題#296。
1 2 3 4 | router := gin.Default() router.GET("/", handler) // [...] endless.ListenAndServe(":4242", router) |
無止境的替代:
禮貌:禮貌的Go HTTP服務(wù)器,優(yōu)雅地關(guān)閉。
優(yōu)雅:優(yōu)雅是一個(gè)Go包,可以正常關(guān)閉http.Handler服務(wù)器。
寬限期:Go服務(wù)器的平穩(wěn)重啟和零宕機(jī)部署。
如果你使用的是Go 1.8,你可能不需要使用這個(gè)庫??紤]使用http.Server內(nèi)置的Shutdown()方法來正常關(guān)閉。用杜松子酒查看完整的關(guān)機(jī)示例。
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測(cè)試的首選方式。
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") } |
測(cè)試上面的代碼示例:
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()) } |
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。