翻译自: https://github.com/gin-gonic/gin/blob/develop/README.md

Gin Web框架

Gin Web框架简单介绍-LMLPHP

Gin Web框架简单介绍-LMLPHP



Gin Web框架简单介绍-LMLPHP

Gin Web框架简单介绍-LMLPHP

Gin是用Golang实现的一种Web框架。基于 httprouter,它提供了相似martini但更好性能(路由性能约快40倍)的API服务. 假设你希望构建一个高性能的生产环境,你会喜欢上使用 Gin。

Gin Web框架简单介绍-LMLPHP

$ cat test.go
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 server on 0.0.0.0:8080
}

基准測试

Gin基于HttpRouter的这个定制版本号来构建。

查看所有測试

BenchmarkAce_GithubAll1000010948213792167
BenchmarkBear_GithubAll1000028749079952943
BenchmarkBeego_GithubAll30005621841462722092
BenchmarkBone_GithubAll50025787166480168119
BenchmarkDenco_GithubAll200009495520224167
BenchmarkEcho_GithubAll300005870500
BenchmarkGin_GithubAll300005099100
BenchmarkGocraftWeb_GithubAll50004496481332801889
BenchmarkGoji_GithubAll200068974856113334
BenchmarkGoJsonRest_GithubAll50005377691359952940
BenchmarkGoRestful_GithubAll100184106287972367725
BenchmarkGorillaMux_GithubAll20080363601531371791
BenchmarkHttpRouter_GithubAll200006350613792167
BenchmarkHttpTreeMux_GithubAll1000016592756112334
BenchmarkKocha_GithubAll1000017136223304843
BenchmarkMacaron_GithubAll20008170082249602315
BenchmarkMartini_GithubAll100126092092379522686
BenchmarkPat_GithubAll3004830398150410132222
BenchmarkPossum_GithubAll1000030171697440812
BenchmarkR2router_GithubAll10000270691773281182
BenchmarkRevel_GithubAll100014919193455535918
BenchmarkRivet_GithubAll10000283860842721079
BenchmarkTango_GithubAll5000473821870782470
BenchmarkTigerTonic_GithubAll200011201312410886052
BenchmarkTraffic_GithubAll2008708979266476222390
BenchmarkVulcan_GithubAll500035339219894609
BenchmarkZeus_GithubAll20009442343006882648

(1): 总反复次数

(2): 单次请求耗时 (ns/op)

(3): 堆内存大小 (B/op)

(4): 单次请求内存分配数 (allocs/op)

Gin v1. stable

  • [x] 零分配路由.
  • [x] 从路由到写请求, 依旧为最快的路由器和框架.
  • [x] 完备的单元測试套件.
  • [x] Battle tested.(?

    )

  • [x] API冻结, 新的release版不会影响现有的代码.

高速開始

  1. 下载并安装Gin:

    $ go get github.com/gin-gonic/gin
  2. 在代码中import进来:

    import "github.com/gin-gonic/gin"
  3. (可选) Import net/http. 假设用到诸如 http.StatusOK的常量, 须要引入该包:

    import "net/http"

API演示样例

使用 GET, POST, PUT, PATCH, DELETE 以及 OPTIONS

func main() {
// 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
}

路径參数

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")
}

查询字符串參数

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表单提交

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")
}

很多其它演示样例: 查询參数 + POST表单提交

POST /post?id=1234&page=1 HTTP/1.1
Content-Type: application/x-www-form-urlencoded name=manu&message=this_is_great
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")
}
id: 1234; page: 1; name: manu; message: this_is_great

很多其它演示样例: 上传文件

參考问题 #548.

func main() {
router := gin.Default() router.POST("/upload", func(c *gin.Context) { file, header , err := c.Request.FormFile("upload")
filename := header.Filename
fmt.Println(header.Filename)
out, err := os.Create("./tmp/"+filename+".png")
if err != nil {
log.Fatal(err)
}
defer out.Close()
_, err = io.Copy(out, file)
if err != nil {
log.Fatal(err)
}
})
router.Run(":8080")
}

分组路由

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默认配置

使用

r := gin.New()

来取代

r := gin.Default()

使用中间件

func main() {
// Creates a router without any middleware by default
r := gin.New() // Global middleware
r.Use(gin.Logger())
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 server on 0.0.0.0:8080
r.Run(":8080")
}

model binding与验证

要绑定一个请求body到某个类型, 能够使用model binding。 眼下支持JSON, XML 以及标准from格式 (foo=bar&boo=baz)的绑定。

所有你想要绑定的域(field)。 须要你设置相应的绑定标识。 比如, 要绑定到JSON, 则这样声明json:"fieldname"

使用Bind方法时, Gin会尝试通过Content-Type头部来推定绑定的类型(如json还是form)。而假设你明白知道要绑定的类型, 能够使用BindWith方法。

你也能够指定哪些filed须要绑定。 假设某个filed像这样声明: binding:"required", 那么在进行绑定时假设发现是空值(注: 是请求中不存在该field名?), 当前的请求会失败并收到错误提示。

// 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 c.BindJSON(&json) == 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"})
}
}
}) // 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 c.Bind(&form) == 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"})
}
}
}) // Listen and server on 0.0.0.0:8080
router.Run(":8080")
}

Multipart/Urlencoded表单请求方式的绑定

package main

import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
) 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.BindWith(&form, binding.Form)
// or you can simply use autobinding with Bind method:
var form LoginForm
// in this case proper binding will be automatically selected
if c.Bind(&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")
}

使用下面命令測试:

$ curl -v --form user=user --form password=password http://localhost:8080/login

XML和JSON的渲染

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})
}) // Listen and server on 0.0.0.0:8080
r.Run(":8080")
}

处理静态文件的请求

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 server on 0.0.0.0:8080
router.Run(":8080")
}

HTML模板渲染

Using LoadHTMLTemplates()

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

<html>
<h1>
{{ .title }}
</h1>
</html>

使用不同路径下但同样文件名称的模板

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

{{ define "posts/index.tmpl" }}
<html><h1>
{{ .title }}
</h1>
<p>Using posts/index.tmpl</p>
</html>
{{ end }}

templates/users/index.tmpl

{{ define "users/index.tmpl" }}
<html><h1>
{{ .title }}
</h1>
<p>Using users/index.tmpl</p>
</html>
{{ end }}

你也能够使用自己的渲染模板

import "html/template"

func main() {
router := gin.Default()
html := template.Must(template.ParseFiles("file1", "file2"))
router.SetHTMLTemplate(html)
router.Run(":8080")
}

重定向

实现HTTP重定向并不麻烦:

r.GET("/test", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "http://www.google.com/")
})

内部或外部的地址都是支持的。

定制中间件

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 server on 0.0.0.0:8080
r.Run(":8080")
}

使用 BasicAuth() 中间件

// simulate some private data
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 server on 0.0.0.0:8080
r.Run(":8080")
}

中间件中的Goroutines

在一个middleware或handler中使用goroutine时, 你不能直接使用源gin.Context, 而仅仅能使用它的一份仅仅读拷贝。

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 server on 0.0.0.0:8080
r.Run(":8080")
}

自己定义HTTP配置

直接使用http.ListenAndServe()。 演示样例:

func main() {
router := gin.Default()
http.ListenAndServe(":8080", router)
}

或者

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

平滑重新启动或关闭

是否须要平滑重新启动或关闭你的服务?有下面这些方式能够实现。

我们能够用 fvbock/endless来替换默认的方法 ListenAndServe,详情请參考#296

router := gin.Default()
router.GET("/", handler)
// [...]
endless.ListenAndServe(":4242", router)

或者, 除使用endless之外的方法:

  • manners: 一种平滑关闭自己的Go HTTP服务。
05-17 04:15
查看更多