No Description

Manu Mtz-Almeida d73f532972 Merge branch 'test-load-html-glob' of https://github.com/kmulvey/gin 10 years ago
Godeps 4430610795 Updates godep 10 years ago
binding 0c9f086b74 Renames Validate() to validate() 10 years ago
examples 302cd99322 Fixing compile bug 10 years ago
render a21cc8dd3d Updates HTML render 10 years ago
.gitignore 0a192fb0fa Tons of unit tests 10 years ago
.travis.yml da99d3d2d3 Add notifications/webhooks to .travis.yml 10 years ago
AUTHORS.md f414648384 - More unit tests 10 years ago
CHANGELOG.md 218b924e72 Updates CHANGELOG 10 years ago
LICENSE b6bd5b0d9f Add MIT license 11 years ago
README.md ce784498b4 Adds benchmarks 10 years ago
auth.go f2ab821223 Dropping bsearch in BasicAuth() 10 years ago
auth_test.go a91893d22b Fixes auth test 10 years ago
benchmarks_test.go aa9078bc73 More benchmarks 10 years ago
context.go 1228b03914 Fixing some unit tests 10 years ago
context_test.go 766493c916 Fixes all unit tests 10 years ago
debug.go eb91af753d Better debug warning message 10 years ago
debug_test.go e2adae9003 Solution for Google App Engine? 10 years ago
deprecated.go 2915fa0ffe Zero allocation router, first commit 10 years ago
errors.go 835f66fdc9 404 not found performance improvements 10 years ago
errors_test.go 4eeca21039 Errors conforms to MarshalJSON interface 10 years ago
fs.go 48633f7001 Better documentation 10 years ago
gin.go 86824a640c Updates CHANGELOG 10 years ago
gin_integration_test.go b235d14ca1 compare the byte array 10 years ago
gin_test.go 766493c916 Fixes all unit tests 10 years ago
githubapi_test.go 709fde85d1 More benchmarks 10 years ago
logger.go 48633f7001 Better documentation 10 years ago
logger_test.go 7469025182 forgot the default of ColorForMethod 10 years ago
middleware_test.go 835f66fdc9 404 not found performance improvements 10 years ago
mode.go 0d1b9856ed Adds shortcut for disabling bind validation. 10 years ago
mode_test.go 4d315f474b More unit tests 10 years ago
path.go 835f66fdc9 404 not found performance improvements 10 years ago
path_test.go 766493c916 Fixes all unit tests 10 years ago
recovery.go 9ecb76ef6e Cosmetic changes 10 years ago
recovery_test.go 9ecb76ef6e Cosmetic changes 10 years ago
response_writer.go 835f66fdc9 404 not found performance improvements 10 years ago
response_writer_test.go 0cb52ccef7 Improves unit test coverage 10 years ago
routergroup.go 1228b03914 Fixing some unit tests 10 years ago
routergroup_test.go cac77e04e3 Adds option for listing directory files + better unit tests 10 years ago
routes_test.go 0d5dc25d39 Fixes unit test when running in Windows 10 years ago
tree.go 66e9feb622 Optimizes tree lookup 10 years ago
tree_test.go eb3e9293ed Renames []HandleFunc to HandlersChain 10 years ago
utils.go c4914f0ff7 Adds WrapF() and WrapH() 10 years ago
utils_test.go 138e4b49bf Using WrapH() internally. 10 years ago
wercker.yml 295201dad2 Adds wercker.yml 10 years ago

README.md

#Gin Web Framework Build Status Coverage Status

GoDoc

Gin is a web framework written in Golang. It features a martini-like API with much better performance, up to 40 times faster thanks to httprouter. If you need performance and good productivity, you will love Gin.

Gin console logger

$ cat test.go
package main

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

func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.String(200, "pong")
	})
	r.Run(":8080") // listen and serve on 0.0.0.0:8080
}

Benchmarks

BenchmarkAce_Param5  2000000           579 ns/op         160 B/op          1 allocs/op
BenchmarkBear_Param5     1000000          1599 ns/op         469 B/op          5 allocs/op
BenchmarkBeego_Param5    1000000          3472 ns/op         992 B/op         13 allocs/op
BenchmarkBone_Param5     1000000          1558 ns/op         432 B/op          3 allocs/op
BenchmarkDenco_Param5    3000000           554 ns/op         160 B/op          1 allocs/op
BenchmarkGin_Param5 10000000           215 ns/op           0 B/op          0 allocs/op
BenchmarkGocraftWeb_Param5   1000000          2689 ns/op         928 B/op         12 allocs/op
BenchmarkGoji_Param5     1000000          1194 ns/op         336 B/op          2 allocs/op
BenchmarkGoJsonRest_Param5    500000          3765 ns/op        1105 B/op         17 allocs/op
BenchmarkGoRestful_Param5     200000         11263 ns/op        2672 B/op         31 allocs/op
BenchmarkGorillaMux_Param5    300000          6050 ns/op         912 B/op          9 allocs/op
BenchmarkHttpRouter_Param5   5000000           397 ns/op         160 B/op          1 allocs/op
BenchmarkHttpTreeMux_Param5  1000000          1088 ns/op         336 B/op          2 allocs/op
BenchmarkKocha_Param5    1000000          1608 ns/op         440 B/op         10 allocs/op
BenchmarkMacaron_Param5   300000          4506 ns/op        1376 B/op         14 allocs/op
BenchmarkMartini_Param5   100000         15237 ns/op        1280 B/op         12 allocs/op
BenchmarkPat_Param5   300000          4988 ns/op        1008 B/op         42 allocs/op
BenchmarkPossum_Param5   1000000          2012 ns/op         624 B/op          7 allocs/op
BenchmarkR2router_Param5     1000000          1531 ns/op         432 B/op          6 allocs/op
BenchmarkRevel_Param5     200000          7964 ns/op        2024 B/op         35 allocs/op
BenchmarkRivet_Param5    1000000          1895 ns/op         528 B/op          9 allocs/op
BenchmarkTango_Param5    1000000          3093 ns/op         944 B/op         18 allocs/op
BenchmarkTigerTonic_Param5    200000         11992 ns/op        2519 B/op         53 allocs/op
BenchmarkTraffic_Param5   200000          8537 ns/op        2280 B/op         31 allocs/op
BenchmarkVulcan_Param5   1000000          1290 ns/op          98 B/op          3 allocs/op
BenchmarkZeus_Param5     1000000          1537 ns/op         416 B/op          3 allocs/op

https://github.com/gin-gonic/go-http-routing-benchmark/blob/master/round2.md

##Gin v1. stable

  • Zero allocation router.
  • Still the fastest http router and framework. From routing to writing.
  • Complete suite of unit tests
  • Battle tested
  • API frozen, new releases will not break your code.

Start using it

  1. Download and install it:
go get github.com/gin-gonic/gin
  1. Import it in your code:
import "github.com/gin-gonic/gin"

##API Examples

Using GET, POST, PUT, PATCH, DELETE and OPTIONS

func main() {
	// Creates a gin router with default middlewares:
	// logger and recovery (crash-free) middlewares
	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)

	// Listen and server on 0.0.0.0:8080
	router.Run(":8080")
}

Parameters in path

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

Querystring parameters

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

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

Grouping routes

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

Blank Gin without middlewares by default

Use

r := gin.New()

instead of

r := gin.Default()

Using middlewares

func main() {
	// Creates a router without any middleware by default
	r := gin.New()

	// Global middlewares
	r.Use(gin.Logger())
	r.Use(gin.Recovery())

	// Per route middlewares, you can add as many as you desire.
	r.GET("/benchmark", MyBenchLogger(), benchEndpoint)

	// Authorization group
	// authorized := r.Group("/", AuthRequired())
	// exactly the same than:
	authorized := r.Group("/")
	// per group middlewares! 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 and validation

To bind a request body into a type, use model binding. We currently support binding of JSON, XML and standard form values (foo=bar&boo=baz).

Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set json:"fieldname".

When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use BindWith.

You can also specify that specific fields are required. If a field is decorated with binding:"required" and has a empty value when binding, the current request will fail with an error.

// Binding from JSON
type LoginJSON struct {
	User     string `json:"user" binding:"required"`
	Password string `json:"password" binding:"required"`
}

// Binding from form values
type LoginForm struct {
    User     string `form:"user" binding:"required"`
    Password string `form:"password" binding:"required"`   
}

func main() {
	r := gin.Default()

    // Example for binding JSON ({"user": "manu", "password": "123"})
	r.POST("/loginJSON", func(c *gin.Context) {
		var json LoginJSON

        c.Bind(&json) // This will infer what binder to use depending on the content-type header.
        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)
    r.POST("/loginHTML", func(c *gin.Context) {
        var form LoginForm

        c.BindWith(&form, binding.Form) // You can also specify which binder to use. We support binding.Form, binding.JSON and binding.XML.
        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
	r.Run(":8080")
}

###Multipart/Urlencoded binding

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:

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

XML and JSON rendering

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

####Serving static files

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 rendering

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")
}
<html><h1>
	{{ .title }}
</h1>
</html>

You can also use your own html template render

import "html/template"

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

Redirects

Issuing a HTTP redirect is easy:

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

Both internal and external locations are supported.

Custom Middlewares

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

Using BasicAuth() middleware

// 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 setted 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 inside a middleware

When starting inside a middleware or handler, you SHOULD NOT use the original context inside it, you have to use a read-only copy.

func main() {
	r := gin.Default()

	r.GET("/long_async", func(c *gin.Context) {
		// create copy to be used inside the goroutine
		c_cp := c.Copy()
		go func() {
			// simulate a long task with time.Sleep(). 5 seconds
			time.Sleep(5 * time.Second)

			// note than you are using the copied context "c_cp", IMPORTANT
			log.Println("Done! in path " + c_cp.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")
}

Custom HTTP configuration

Use http.ListenAndServe() directly, like this:

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

or

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