|
|
@@ -1,4 +1,4 @@
|
|
|
-#Gin Web Framework [](https://travis-ci.org/gin-gonic/gin) [](https://coveralls.io/r/gin-gonic/gin?branch=develop)
|
|
|
+#Gin Web Framework [](https://travis-ci.org/gin-gonic/gin) [](https://coveralls.io/r/gin-gonic/gin?branch=master)
|
|
|
|
|
|
[](https://godoc.org/github.com/gin-gonic/gin) [](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
|
|
|
|
|
@@ -9,108 +9,60 @@ Gin is a web framework written in Golang. It features a martini-like API with mu
|
|
|
```
|
|
|
$ cat test.go
|
|
|
```
|
|
|
-```go
|
|
|
+```go
|
|
|
package main
|
|
|
|
|
|
-import (
|
|
|
- "net/http"
|
|
|
- "github.com/gin-gonic/gin"
|
|
|
-)
|
|
|
+import "github.com/gin-gonic/gin"
|
|
|
|
|
|
func main() {
|
|
|
- router := gin.Default()
|
|
|
- router.GET("/", func(c *gin.Context) {
|
|
|
- c.String(http.StatusOK, "hello world")
|
|
|
- })
|
|
|
- router.GET("/ping", func(c *gin.Context) {
|
|
|
- c.String(http.StatusOK, "pong")
|
|
|
- })
|
|
|
- router.POST("/submit", func(c *gin.Context) {
|
|
|
- c.String(http.StatusUnauthorized, "not authorized")
|
|
|
- })
|
|
|
- router.PUT("/error", func(c *gin.Context) {
|
|
|
- c.String(http.StatusInternalServerError, "an error happened :(")
|
|
|
+ r := gin.Default()
|
|
|
+ r.GET("/ping", func(c *gin.Context) {
|
|
|
+ c.String(200, "pong")
|
|
|
})
|
|
|
- router.Run(":8080")
|
|
|
+ r.Run(":8080") // listen and serve on 0.0.0.0:8080
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-##Gin is new, will it be supported?
|
|
|
-
|
|
|
-Yes, Gin is an internal tool of [Manu](https://github.com/manucorporat) and [Javi](https://github.com/javierprovecho) for many of our projects/start-ups. We developed it and we are going to continue using and improve it.
|
|
|
-
|
|
|
-
|
|
|
-##Roadmap for v1.0
|
|
|
-- [ ] Ask our designer for a cool logo
|
|
|
-- [ ] Add tons of unit tests
|
|
|
-- [ ] Add internal benchmarks suite
|
|
|
-- [ ] More powerful validation API
|
|
|
-- [ ] Improve documentation
|
|
|
-- [ ] Add Swagger support
|
|
|
-- [x] Stable API
|
|
|
-- [x] Improve logging system
|
|
|
-- [x] Improve JSON/XML validation using bindings
|
|
|
-- [x] Improve XML support
|
|
|
-- [x] Flexible rendering system
|
|
|
-- [x] Add more cool middlewares, for example redis caching (this also helps developers to understand the framework).
|
|
|
-- [x] Continuous integration
|
|
|
-- [x] Performance improments, reduce allocation and garbage collection overhead
|
|
|
-- [x] Fix bugs
|
|
|
-
|
|
|
+##Gin v1. released
|
|
|
+- [x] Zero allocation router.
|
|
|
+- [x] Still the fastest http router and framework. From routing to writing.
|
|
|
+- [x] Complete suite of unit tests
|
|
|
+- [x] Battle tested
|
|
|
+- [x] API frozen, new releases will not break your code.
|
|
|
|
|
|
|
|
|
## Start using it
|
|
|
-Obviously, you need to have Git and Go already installed to run Gin.
|
|
|
-Run this in your terminal
|
|
|
+1. Download and install it:
|
|
|
|
|
|
-```
|
|
|
+```sh
|
|
|
go get github.com/gin-gonic/gin
|
|
|
```
|
|
|
-Then import it in your Go code:
|
|
|
+2. Import it in your code:
|
|
|
|
|
|
-```
|
|
|
+```go
|
|
|
import "github.com/gin-gonic/gin"
|
|
|
```
|
|
|
|
|
|
##API Examples
|
|
|
|
|
|
-#### Create most basic PING/PONG HTTP endpoint
|
|
|
-```go
|
|
|
-package main
|
|
|
-
|
|
|
-import (
|
|
|
- "net/http"
|
|
|
- "github.com/gin-gonic/gin"
|
|
|
-)
|
|
|
-
|
|
|
-func main() {
|
|
|
- r := gin.Default()
|
|
|
- r.GET("/ping", func(c *gin.Context) {
|
|
|
- c.String(http.StatusOK, "pong")
|
|
|
- })
|
|
|
-
|
|
|
- // Listen and serve on 0.0.0.0:8080
|
|
|
- r.Run(":8080")
|
|
|
-}
|
|
|
-```
|
|
|
-
|
|
|
#### Using GET, POST, PUT, PATCH, DELETE and OPTIONS
|
|
|
|
|
|
```go
|
|
|
func main() {
|
|
|
- // Creates a gin router + logger and recovery (crash-free) middlewares
|
|
|
- r := gin.Default()
|
|
|
+ // Creates a gin router with default middlewares:
|
|
|
+ // logger and recovery (crash-free) middlewares
|
|
|
+ router := gin.Default()
|
|
|
|
|
|
- r.GET("/someGet", getting)
|
|
|
- r.POST("/somePost", posting)
|
|
|
- r.PUT("/somePut", putting)
|
|
|
- r.DELETE("/someDelete", deleting)
|
|
|
- r.PATCH("/somePatch", patching)
|
|
|
- r.HEAD("/someHead", head)
|
|
|
- r.OPTIONS("/someOptions", options)
|
|
|
+ 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)
|
|
|
|
|
|
// Listen and server on 0.0.0.0:8080
|
|
|
- r.Run(":8080")
|
|
|
+ router.Run(":8080")
|
|
|
}
|
|
|
```
|
|
|
|
|
|
@@ -118,26 +70,24 @@ func main() {
|
|
|
|
|
|
```go
|
|
|
func main() {
|
|
|
- r := gin.Default()
|
|
|
+ router := gin.Default()
|
|
|
|
|
|
// This handler will match /user/john but will not match neither /user/ or /user
|
|
|
- r.GET("/user/:name", func(c *gin.Context) {
|
|
|
- name := c.Params.ByName("name")
|
|
|
- message := "Hello "+name
|
|
|
- c.String(http.StatusOK, message)
|
|
|
+ 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/join/
|
|
|
- r.GET("/user/:name/*action", func(c *gin.Context) {
|
|
|
- name := c.Params.ByName("name")
|
|
|
- action := c.Params.ByName("action")
|
|
|
+ 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)
|
|
|
})
|
|
|
|
|
|
- // Listen and server on 0.0.0.0:8080
|
|
|
- r.Run(":8080")
|
|
|
+ router.Run(":8080")
|
|
|
}
|
|
|
```
|
|
|
|
|
|
@@ -158,78 +108,32 @@ func main() {
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-###Form parameters
|
|
|
-```go
|
|
|
-func main() {
|
|
|
- r := gin.Default()
|
|
|
-
|
|
|
- // This will respond to urls like search?firstname=Jane&lastname=Doe
|
|
|
- r.GET("/search", func(c *gin.Context) {
|
|
|
- // You need to call ParseForm() on the request to receive url and form params first
|
|
|
- c.Request.ParseForm()
|
|
|
-
|
|
|
- firstname := c.Request.Form.Get("firstname")
|
|
|
- lastname := c.Request.Form.Get("lastname")
|
|
|
-
|
|
|
- message := "Hello "+ firstname + lastname
|
|
|
- c.String(http.StatusOK, message)
|
|
|
- })
|
|
|
- r.Run(":8080")
|
|
|
-}
|
|
|
-```
|
|
|
+### Multipart/Urlencoded Form
|
|
|
|
|
|
-###Multipart Form
|
|
|
```go
|
|
|
-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()
|
|
|
|
|
|
- r := gin.Default()
|
|
|
-
|
|
|
- r.POST("/login", func(c *gin.Context) {
|
|
|
-
|
|
|
- var form LoginForm
|
|
|
-
|
|
|
- // you can bind multipart form with explicit binding declaration:
|
|
|
- // c.BindWith(&form, binding.Form)
|
|
|
- // or you can simply use autobinding with Bind method:
|
|
|
- c.Bind(&form) // in this case proper binding will be automatically selected
|
|
|
-
|
|
|
- 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"})
|
|
|
- }
|
|
|
-
|
|
|
- })
|
|
|
-
|
|
|
- r.Run(":8080")
|
|
|
-
|
|
|
+ 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,
|
|
|
+ })
|
|
|
+ })
|
|
|
+ router.Run(":8080")
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-Test it with:
|
|
|
-```bash
|
|
|
-$ curl -v --form user=user --form password=password http://localhost:8080/login
|
|
|
-```
|
|
|
-
|
|
|
#### Grouping routes
|
|
|
```go
|
|
|
func main() {
|
|
|
- r := gin.Default()
|
|
|
+ router := gin.Default()
|
|
|
|
|
|
// Simple group: v1
|
|
|
- v1 := r.Group("/v1")
|
|
|
+ v1 := router.Group("/v1")
|
|
|
{
|
|
|
v1.POST("/login", loginEndpoint)
|
|
|
v1.POST("/submit", submitEndpoint)
|
|
|
@@ -237,15 +141,14 @@ func main() {
|
|
|
}
|
|
|
|
|
|
// Simple group: v2
|
|
|
- v2 := r.Group("/v2")
|
|
|
+ v2 := router.Group("/v2")
|
|
|
{
|
|
|
v2.POST("/login", loginEndpoint)
|
|
|
v2.POST("/submit", submitEndpoint)
|
|
|
v2.POST("/read", readEndpoint)
|
|
|
}
|
|
|
|
|
|
- // Listen and server on 0.0.0.0:8080
|
|
|
- r.Run(":8080")
|
|
|
+ router.Run(":8080")
|
|
|
}
|
|
|
```
|
|
|
|
|
|
@@ -354,6 +257,50 @@ func main() {
|
|
|
}
|
|
|
```
|
|
|
|
|
|
+
|
|
|
+###Multipart/Urlencoded binding
|
|
|
+```go
|
|
|
+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
|
|
|
+ c.Bind(&form) // in this case proper binding will be automatically selected
|
|
|
+
|
|
|
+ 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:
|
|
|
+```bash
|
|
|
+$ curl -v --form user=user --form password=password http://localhost:8080/login
|
|
|
+```
|
|
|
+
|
|
|
+
|
|
|
#### XML and JSON rendering
|
|
|
|
|
|
```go
|
|
|
@@ -390,48 +337,41 @@ func main() {
|
|
|
```
|
|
|
|
|
|
####Serving static files
|
|
|
-Use Engine.ServeFiles(path string, root http.FileSystem):
|
|
|
|
|
|
```go
|
|
|
func main() {
|
|
|
- r := gin.Default()
|
|
|
- r.Static("/assets", "./assets")
|
|
|
+ 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
|
|
|
- r.Run(":8080")
|
|
|
+ router.Run(":8080")
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-Use the following example to serve static files at top level route of your domain. Files are being served from directory ./html.
|
|
|
-
|
|
|
-```
|
|
|
-r := gin.Default()
|
|
|
-r.Use(static.Serve("/", static.LocalFile("html", false)))
|
|
|
-```
|
|
|
-
|
|
|
-Note: this will use `httpNotFound` instead of the Router's `NotFound` handler.
|
|
|
-
|
|
|
####HTML rendering
|
|
|
|
|
|
Using LoadHTMLTemplates()
|
|
|
|
|
|
```go
|
|
|
func main() {
|
|
|
- r := gin.Default()
|
|
|
- r.LoadHTMLGlob("templates/*")
|
|
|
- r.GET("/index", func(c *gin.Context) {
|
|
|
- obj := gin.H{"title": "Main website"}
|
|
|
- c.HTML(http.StatusOK, "index.tmpl", obj)
|
|
|
+ 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",
|
|
|
+ })
|
|
|
})
|
|
|
-
|
|
|
- // Listen and server on 0.0.0.0:8080
|
|
|
- r.Run(":8080")
|
|
|
+ router.Run(":8080")
|
|
|
}
|
|
|
```
|
|
|
```html
|
|
|
-<h1>
|
|
|
+<html><h1>
|
|
|
{{ .title }}
|
|
|
</h1>
|
|
|
+</html>
|
|
|
```
|
|
|
|
|
|
You can also use your own html template render
|
|
|
@@ -440,41 +380,13 @@ You can also use your own html template render
|
|
|
import "html/template"
|
|
|
|
|
|
func main() {
|
|
|
- r := gin.Default()
|
|
|
+ router := gin.Default()
|
|
|
html := template.Must(template.ParseFiles("file1", "file2"))
|
|
|
- r.SetHTMLTemplate(html)
|
|
|
-
|
|
|
- // Listen and server on 0.0.0.0:8080
|
|
|
- r.Run(":8080")
|
|
|
+ router.SetHTMLTemplate(html)
|
|
|
+ router.Run(":8080")
|
|
|
}
|
|
|
```
|
|
|
|
|
|
-#####Using layout files with templates
|
|
|
-```go
|
|
|
-var baseTemplate = "main.tmpl"
|
|
|
-
|
|
|
-r.GET("/", func(c *gin.Context) {
|
|
|
- r.SetHTMLTemplate(template.Must(template.ParseFiles(baseTemplate, "whatever.tmpl")))
|
|
|
- c.HTML(200, "base", data)
|
|
|
-})
|
|
|
-```
|
|
|
-main.tmpl
|
|
|
-```html
|
|
|
-{{define "base"}}
|
|
|
-<html>
|
|
|
- <head></head>
|
|
|
- <body>
|
|
|
- {{template "content" .}}
|
|
|
- </body>
|
|
|
-</html>
|
|
|
-{{end}}
|
|
|
-```
|
|
|
-whatever.tmpl
|
|
|
-```html
|
|
|
-{{define "content"}}
|
|
|
-<h1>Hello World!</h1>
|
|
|
-{{end}}
|
|
|
-```
|
|
|
|
|
|
#### Redirects
|
|
|
|