GitHub - keep-learn/gin: Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin. · GitHub
You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Star0 (0)You must be signed in to star a repository
About
Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.
Gin is a web framework written in Go (Golang). It features a martini-like API with performance that is up to 40 times faster thanks to httprouter. If you need performance and good productivity, you will love Gin.
To install Gin package, you need to install Go and set your Go workspace first.
The first need Go installed (version 1.11+ is required), then you can use the below Go command to install Gin.
$ go get -u github.com/gin-gonic/gin
Import it in your code:
import"github.com/gin-gonic/gin"
(Optional) Import net/http. This is required for example if using constants such as http.StatusOK.
import"net/http"
Quick start
<
10BC8
div class="highlight highlight-source-shell notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="# assume the following codes in example.go file
$ cat example.go">
# assume the following codes in example.go file
$ cat example.go
package main
import"github.com/gin-gonic/gin"funcmain() {
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 (for windows "localhost:8080")
}
# run example.go and visit 0.0.0.0:8080/ping (for windows "localhost:8080/ping") on browser
$ go run example.go
funcmain() {
// Creates a gin router with default middleware:// logger and recovery (crash-free) middlewarerouter:=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
}
Parameters in path
funcmain() {
router:=gin.Default()
// This handler will match /user/john but will not match /user/ or /userrouter.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 "+actionc.String(http.StatusOK, message)
})
// For each matched request Context will hold the route definitionrouter.POST("/user/:name/*action", func(c*gin.Context) {
c.FullPath() =="/user/:name/*action"// true
})
router.Run(":8080")
}
Querystring parameters
funcmain() {
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=Doerouter.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")
}
The filename is always optional and must not be used blindly by the application: path information should be stripped, and conversion to the server file system rules should be done.
funcmain() {
router:=gin.Default()
// Set a lower memory limit for multipart forms (default is 32 MiB)// router.MaxMultipartMemory = 8 << 20 // 8 MiBrouter.POST("/upload", func(c*gin.Context) {
// single filefile, _:=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")
}
// Default With the Logger and Recovery middleware already attachedr:=gin.Default()
Using middleware
funcmain() {
// Creates a router without any middleware by defaultr:=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.Stdoutr.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 grouptesting:=authorized.Group("testing")
testing.GET("/analytics", analyticsEndpoint)
}
// Listen and serve on 0.0.0.0:8080r.Run(":8080")
}
How to write log file
funcmain() {
// 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")
}
Behavior - These methods use MustBindWith under the hood. If there is a binding error, the request is aborted with c.AbortWithError(400, err).SetType(ErrorTypeBind). This sets the response status code to 400 and the Content-Type header is set to text/plain; charset=utf-8. Note that if you try to set the response code after this, it will result in a warning [GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422. If you wish to have greater control over the behavior, consider using the ShouldBind equivalent method.
Behavior - These methods use ShouldBindWith under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately.
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 MustBindWith or ShouldBindWith.
You can also specify that specific fields are required. If a field is decorated with binding:"required" and has a empty value when binding, an error will be returned.
// Binding from JSONtypeLoginstruct {
Userstring`form:"user" json:"user" xml:"user" binding:"required"`Passwordstring`form:"password" json:"password" xml:"password" binding:"required"`
}
funcmain() {
router:=gin.Default()
// Example for binding JSON ({"user": "manu", "password": "123"})router.POST("/loginJSON", func(c*gin.Context) {
varjsonLoginiferr:=c.ShouldBindJSON(&json); err!=nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
ifjson.User!="manu"||json.Password!="123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
})
// Example for binding XML (// <?xml version="1.0" encoding="UTF-8"?>// <root>// <user>user</user>// <password>123</password>// </root>)router.POST("/loginXML", func(c*gin.Context) {
varxmlLoginiferr:=c.ShouldBindXML(&xml); err!=nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
ifxml.User!="manu"||xml.Password!="123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
})
// Example for binding a HTML form (user=manu&password=123)router.POST("/loginForm", func(c*gin.Context) {
varformLogin// This will infer what binder to use depending on the content-type header.iferr:=c.ShouldBind(&form); err!=nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
ifform.User!="manu"||form.Password!="123" {
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
})
// Listen and serve on 0.0.0.0:8080router.Run(":8080")
}
Sample request
$ 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"}
Skip validate
When running the above example using the above the curl command, it returns error. Because the example use binding:"required" for Password. If use binding:"-" for Password, then it will not return error when running the above example again.
Custom Validators
It is also possible to register custom validators. See the example code.
$ curl "localhost:8085/bookable?check_in=2018-04-16&check_out=2018-04-17"{"message":"Booking dates are valid!"}
$ curl "localhost:8085/bookable?check_in=2018-03-10&check_out=2018-03-09"{"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"}
typeProfileFormstruct {
Namestring`form:"name" binding:"required"`Avatar*multipart.FileHeader`form:"avatar" binding:"required"`// or for multiple files// Avatars []*multipart.FileHeader `form:"avatar" binding:"required"`
}
funcmain() {
router:=gin.Default()
router.POST("/profile", 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:varformProfileForm// in this case proper binding will be automatically selectediferr:=c.ShouldBind(&form); err!=nil {
c.String(http.StatusBadRequest, "bad request")
return
}
err:=c.SaveUploadedFile(form.Avatar, form.Avatar.Filename)
iferr!=nil {
c.String(http.StatusInternalServerError, "unknown error")
return
}
// db.Save(&form)c.String(http.StatusOK, "ok")
})
router.Run(":8080")
}
Test it with:
$ curl -X POST -v --form name=user --form "avatar=@./avatar.png" http://localhost:8080/profile
XML, JSON, YAML and ProtoBuf rendering
funcmain() {
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 structvarmsgstruct {
Namestring`json:"user"`MessagestringNumberint
}
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})
})
r.GET("/someProtoBuf", func(c*gin.Context) {
reps:= []int64{int64(1), int64(2)}
label:="test"// The specific definition of protobuf is written in the testdata/protoexample file.data:=&protoexample.Test{
Label: &label,
Reps: reps,
}
// Note that data becomes binary data in the response// Will output protoexample.Test protobuf serialized datac.ProtoBuf(http.StatusOK, data)
})
// Listen and serve on 0.0.0.0:8080r.Run(":8080")
}
SecureJSON
Using SecureJSON to prevent json hijacking. Default prepends "while(1)," to response body if the given struct is array values.
funcmain() {
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:8080r.Run(":8080")
}
JSONP
Using JSONP to request data from a server in a different domain. Add callback to response body if the query parameter callback exists.
funcmain() {
r:=gin.Default()
r.GET("/JSONP", func(c*gin.Context) {
data:= gin.H{
"foo": "bar",
}
//callback is x// Will output : x({\"foo\":\"bar\"})c.JSONP(http.StatusOK, data)
})
// Listen and serve on 0.0.0.0:8080r.Run(":8080")
// client// curl http://127.0.0.1:8080/JSONP?callback=x
}
AsciiJSON
Using AsciiJSON to Generates ASCII-only JSON with escaped non-ASCII characters.
funcmain() {
r:=gin.Default()
r.GET("/someJSON", func(c*gin.Context) {
data:= gin.H{
"lang": "GO语言",
"tag": "<br>",
}
// will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"}c.AsciiJSON(http.StatusOK, data)
})
// Listen and serve on 0.0.0.0:8080r.Run(":8080")
}
PureJSON
Normally, JSON replaces special HTML characters with their unicode entities, e.g. < becomes \u003c. If you want to encode such characters literally, you can use PureJSON instead.
This feature is unavailable in Go 1.6 and lower.
funcLogger() gin.HandlerFunc {
returnfunc(c*gin.Context) {
t:=time.Now()
// Set example variablec.Set("example", "12345")
// before requestc.Next()
// after requestlatency:=time.Since(t)
log.Print(latency)
// access the status we are sendingstatus:=c.Writer.Status()
log.Println(status)
}
}
funcmain() {
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:8080r.Run(":8080")
}
Using BasicAuth() middleware
// simulate some private datavarsecrets= 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"},
}
funcmain() {
r:=gin.Default()
// Group using gin.BasicAuth() middleware// gin.Accounts is a shortcut for map[string]stringauthorized:=r.Group("/admin", gin.BasicAuth(gin.Accounts{
"foo": "bar",
"austin": "1234",
"lena": "hello2",
"manu": "4321",
}))
// /admin/secrets endpoint// hit "localhost:8080/admin/secretsauthorized.GET("/secrets", func(c*gin.Context) {
// get user, it was set by the BasicAuth middlewareuser:=c.MustGet(gin.AuthUserKey).(string)
ifsecret, 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:8080r.Run(":8080")
}
Goroutines inside a middleware
When starting new Goroutines inside a middleware or handler, you SHOULD NOT use the original context inside it, you have to use a read-only copy.
funcmain() {
r:=gin.Default()
r.GET("/long_async", func(c*gin.Context) {
// create copy to be used inside the goroutinecCp:=c.Copy()
gofunc() {
// simulate a long task with time.Sleep(). 5 secondstime.Sleep(5*time.Second)
// note that you are using the copied context "cCp", IMPORTANTlog.Println("Done! in path "+cCp.Request.URL.Path)
}()
})
r.GET("/long_sync", func(c*gin.Context) {
// simulate a long task with time.Sleep(). 5 secondstime.Sleep(5*time.Second)
// since we are NOT using a goroutine, we do not have to copy the contextlog.Println("Done! in path "+c.Request.URL.Path)
})
// Listen and serve on 0.0.0.0:8080r.Run(":8080")
}
manners: A polite Go HTTP server that shuts down gracefully.
graceful: Graceful is a Go package enabling graceful shutdown of an http.Handler server.
grace: Graceful restart & zero downtime deploy for Go servers.
If you are using Go 1.8, you may not need to use this library! Consider using http.Server's built-in Shutdown() method for graceful shutdowns. See the full graceful-shutdown example with gin.
// +build go1.8package main
import (
"context""log""net/http""os""os/signal""syscall""time""github.com/gin-gonic/gin"
)
funcmain() {
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,
}
gofunc() {
// service connectionsiferr:=srv.ListenAndServe(); err!=nil&&err!=http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
// Wait for interrupt signal to gracefully shutdown the server with// a timeout of 5 seconds.quit:=make(chan os.Signal)
// kill (no param) default send syscall.SIGTERM// kill -2 is syscall.SIGINT// kill -9 is syscall.SIGKILL but can't be catch, so don't need add itsignal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quitlog.Println("Shutdown Server ...")
ctx, cancel:=context.WithTimeout(context.Background(), 5*time.Second)
defercancel()
iferr:=srv.Shutdown(ctx)
1073F
; err!=nil {
log.Fatal("Server Shutdown:", err)
}
// catching ctx.Done(). timeout of 5 seconds.select {
case<-ctx.Done():
log.Println("timeout of 5 seconds.")
}
log.Println("Server exiting")
}
Build a single binary with templates
You can build a server into a single binary containing templates by using go-assets.
The normal methods for binding request body consumes c.Request.Body and they
cannot be called multiple times.
typeformAstruct {
Foostring`json:"foo" xml:"foo" binding:"required"`
}
typeformBstruct {
Barstring`json:"bar" xml:"bar" binding:"required"`
}
funcSomeHandler(c*gin.Context) {
objA:=formA{}
objB:=formB{}
// This c.ShouldBind consumes c.Request.Body and it cannot be reused.iferrA:=c.ShouldBind(&objA); errA==nil {
c.String(http.StatusOK, `the body should be formA`)
// Always an error is occurred by this because c.Request.Body is EOF now.
} elseiferrB:=c.ShouldBind(&objB); errB==nil {
c.String(http.StatusOK, `the body should be formB`)
} else {
...
}
}
For this, you can use c.ShouldBindBodyWith.
funcSomeHandler(c*gin.Context) {
objA:=formA{}
objB:=formB{}
// This reads c.Request.Body and stores the result into the context.iferrA:=c.ShouldBindBodyWith(&objA, binding.JSON); errA==nil {
c.String(http.StatusOK, `the body should be formA`)
// At this time, it reuses body stored in the context.
} elseiferrB:=c.ShouldBindBodyWith(&objB, binding.JSON); errB==nil {
c.String(http.StatusOK, `the body should be formB JSON`)
// And it can accepts other formats
} elseiferrB2:=c.ShouldBindBodyWith(&objB, binding.XML); errB2==nil {
c.String(http.StatusOK, `the body should be formB XML`)
} else {
...
}
}
c.ShouldBindBodyWith stores body into the context before binding. This has
a slight impact to performance, so you should not use this method if you are
enough to call binding at once.
This feature is only needed for some formats -- JSON, XML, MsgPack,
ProtoBuf. For other formats, Query, Form, FormPost, FormMultipart,
can be called by c.ShouldBind() multiple times without any damage to
performance (See #1341).
http2 server push
http.Pusher is supported only go1.8+. See the golang blog for detail information.
package main
import (
"html/template""log""github.com/gin-gonic/gin"
)
varhtml=template.Must(template.New("https").Parse(`<html><head> <title>Https Test</title> <script src="/assets/app.js"></script></head><body> <h1 style="color:red;">Welcome, Ginner!</h1></body></html>`))
funcmain() {
r:=gin.Default()
r.Static("/assets", "./assets")
r.SetHTMLTemplate(html)
r.GET("/", func(c*gin.Context) {
ifpusher:=c.Writer.Pusher(); pusher!=nil {
// use pusher.Push() to do server pushiferr:=pusher.Push("/assets/app.js", nil); err!=nil {
log.Printf("Failed to push: %v", err)
}
}
c.HTML(200, "https", gin.H{
"status": "success",
})
})
// Listen and Server in https://127.0.0.1:8080r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key")
}
Define format for the log of routes
The default log of routes is:
[GIN-debug] POST /foo --> main.main.func1 (3 handlers)
[GIN-debug] GET /bar --> main.main.func2 (3 handlers)
[GIN-debug] GET /status --> main.main.func3 (3 handlers)
If you want to log this information in given format (e.g. JSON, key values or something else), then you can define this format with gin.DebugPrintRouteFunc.
In the example below, we log all routes with standard log package but you can use another log tools that suits of your needs.
Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.