Gin is a HTTP web framework written in Go (Golang).

Overview

Gin Web Framework

Build Status codecov Go Report Card GoDoc Join the chat at https://gitter.im/gin-gonic/gin Sourcegraph Open Source Helpers Release TODOs

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.

Contents

Installation

To install Gin package, you need to install Go and set your Go workspace first.

  1. The first need Go installed (version 1.12+ is required), then you can use the below Go command to install Gin.
$ go get -u github.com/gin-gonic/gin
  1. Import it in your code:
import "github.com/gin-gonic/gin"
  1. (Optional) Import net/http. This is required for example if using constants such as http.StatusOK.
import "net/http"

Quick start

# assume the following codes in example.go file
$ cat example.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 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

Benchmarks

Gin uses a custom version of HttpRouter

See all benchmarks

Benchmark name (1) (2) (3) (4)
BenchmarkGin_GithubAll 43550 27364 ns/op 0 B/op 0 allocs/op
BenchmarkAce_GithubAll 40543 29670 ns/op 0 B/op 0 allocs/op
BenchmarkAero_GithubAll 57632 20648 ns/op 0 B/op 0 allocs/op
BenchmarkBear_GithubAll 9234 216179 ns/op 86448 B/op 943 allocs/op
BenchmarkBeego_GithubAll 7407 243496 ns/op 71456 B/op 609 allocs/op
BenchmarkBone_GithubAll 420 2922835 ns/op 720160 B/op 8620 allocs/op
BenchmarkChi_GithubAll 7620 238331 ns/op 87696 B/op 609 allocs/op
BenchmarkDenco_GithubAll 18355 64494 ns/op 20224 B/op 167 allocs/op
BenchmarkEcho_GithubAll 31251 38479 ns/op 0 B/op 0 allocs/op
BenchmarkGocraftWeb_GithubAll 4117 300062 ns/op 131656 B/op 1686 allocs/op
BenchmarkGoji_GithubAll 3274 416158 ns/op 56112 B/op 334 allocs/op
BenchmarkGojiv2_GithubAll 1402 870518 ns/op 352720 B/op 4321 allocs/op
BenchmarkGoJsonRest_GithubAll 2976 401507 ns/op 134371 B/op 2737 allocs/op
BenchmarkGoRestful_GithubAll 410 2913158 ns/op 910144 B/op 2938 allocs/op
BenchmarkGorillaMux_GithubAll 346 3384987 ns/op 251650 B/op 1994 allocs/op
BenchmarkGowwwRouter_GithubAll 10000 143025 ns/op 72144 B/op 501 allocs/op
BenchmarkHttpRouter_GithubAll 55938 21360 ns/op 0 B/op 0 allocs/op
BenchmarkHttpTreeMux_GithubAll 10000 153944 ns/op 65856 B/op 671 allocs/op
BenchmarkKocha_GithubAll 10000 106315 ns/op 23304 B/op 843 allocs/op
BenchmarkLARS_GithubAll 47779 25084 ns/op 0 B/op 0 allocs/op
BenchmarkMacaron_GithubAll 3266 371907 ns/op 149409 B/op 1624 allocs/op
BenchmarkMartini_GithubAll 331 3444706 ns/op 226551 B/op 2325 allocs/op
BenchmarkPat_GithubAll 273 4381818 ns/op 1483152 B/op 26963 allocs/op
BenchmarkPossum_GithubAll 10000 164367 ns/op 84448 B/op 609 allocs/op
BenchmarkR2router_GithubAll 10000 160220 ns/op 77328 B/op 979 allocs/op
BenchmarkRivet_GithubAll 14625 82453 ns/op 16272 B/op 167 allocs/op
BenchmarkTango_GithubAll 6255 279611 ns/op 63826 B/op 1618 allocs/op
BenchmarkTigerTonic_GithubAll 2008 687874 ns/op 193856 B/op 4474 allocs/op
BenchmarkTraffic_GithubAll 355 3478508 ns/op 820744 B/op 14114 allocs/op
BenchmarkVulcan_GithubAll 6885 193333 ns/op 19894 B/op 609 allocs/op
  • (1): Total Repetitions achieved in constant time, higher means more confident result
  • (2): Single Repetition Duration (ns/op), lower is better
  • (3): Heap Memory (B/op), lower is better
  • (4): Average Allocations per Repetition (allocs/op), lower is better

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.

Build with jsoniter

Gin uses encoding/json as default json package but you can change to jsoniter by build from other tags.

$ go build -tags=jsoniter .

API Examples

You can find a number of ready-to-run examples at Gin examples repository.

Using GET, POST, PUT, PATCH, DELETE and 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
}

Parameters in path

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

	// This handler will match /user/john but will not match /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)
	})

	// For each matched request Context will hold the route definition
	router.POST("/user/:name/*action", func(c *gin.Context) {
		c.FullPath() == "/user/:name/*action" // true
	})

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

Another example: query + post form

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

Map as querystring or postform parameters

POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1
Content-Type: application/x-www-form-urlencoded

names[first]=thinkerou&names[second]=tianou
func main() {
	router := gin.Default()

	router.POST("/post", func(c *gin.Context) {

		ids := c.QueryMap("ids")
		names := c.PostFormMap("names")

		fmt.Printf("ids: %v; names: %v", ids, names)
	})
	router.Run(":8080")
}
ids: map[b:hello a:1234]; names: map[second:tianou first:thinkerou]

Upload files

Single file

References issue #774 and detail example code.

file.Filename SHOULD NOT be trusted. See Content-Disposition on MDN and #1693

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.

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:

curl -X POST http://localhost:8080/upload \
  -F "[email protected]/Users/appleboy/test.zip" \
  -H "Content-Type: multipart/form-data"

Multiple files

See the detail example code.

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:

curl -X POST http://localhost:8080/upload \
  -F "upload[][email protected]/Users/appleboy/test1.zip" \
  -F "upload[][email protected]/Users/appleboy/test2.zip" \
  -H "Content-Type: multipart/form-data"

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 middleware by default

Use

r := gin.New()

instead of

// Default With the Logger and Recovery middleware already attached
r := gin.Default()

Using middleware

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

Custom Recovery behavior

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.CustomRecovery(func(c *gin.Context, recovered interface{}) {
		if err, ok := recovered.(string); ok {
			c.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err))
		}
		c.AbortWithStatus(http.StatusInternalServerError)
	}))

	r.GET("/panic", func(c *gin.Context) {
		// panic with a string -- the custom middleware could save this to a database or report it to the user
		panic("foo")
	})

	r.GET("/", func(c *gin.Context) {
		c.String(http.StatusOK, "ohai")
	})

	// Listen and serve on 0.0.0.0:8080
	r.Run(":8080")
}

How to write log file

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

Custom Log Format

func main() {
	router := gin.New()

	// LoggerWithFormatter middleware will write the logs to gin.DefaultWriter
	// By default gin.DefaultWriter = os.Stdout
	router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {

		// your custom format
		return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
				param.ClientIP,
				param.TimeStamp.Format(time.RFC1123),
				param.Method,
				param.Path,
				param.Request.Proto,
				param.StatusCode,
				param.Latency,
				param.Request.UserAgent(),
				param.ErrorMessage,
		)
	}))
	router.Use(gin.Recovery())

	router.GET("/ping", func(c *gin.Context) {
		c.String(200, "pong")
	})

	router.Run(":8080")
}

Sample Output

::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" "

Controlling Log output coloring

By default, logs output on console should be colorized depending on the detected TTY.

Never colorize logs:

func main() {
    // Disable log's color
    gin.DisableConsoleColor()

    // Creates a gin router with default middleware:
    // logger and recovery (crash-free) middleware
    router := gin.Default()

    router.GET("/ping", func(c *gin.Context) {
        c.String(200, "pong")
    })

    router.Run(":8080")
}

Always colorize logs:

func main() {
    // Force log's color
    gin.ForceConsoleColor()

    // Creates a gin router with default middleware:
    // logger and recovery (crash-free) middleware
    router := gin.Default()

    router.GET("/ping", func(c *gin.Context) {
        c.String(200, "pong")
    })

    router.Run(":8080")
}

Model binding and validation

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

Gin uses go-playground/validator/v10 for validation. Check the full docs on tags usage here.

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".

Also, Gin provides two sets of methods for binding:

  • Type - Must bind
    • Methods - Bind, BindJSON, BindXML, BindQuery, BindYAML, BindHeader
    • 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.
  • Type - Should bind
    • Methods - ShouldBind, ShouldBindJSON, ShouldBindXML, ShouldBindQuery, ShouldBindYAML, ShouldBindHeader
    • 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 JSON
type Login struct {
	User     string `form:"user" json:"user" xml:"user"  binding:"required"`
	Password string `form:"password" json:"password" xml:"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 {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}

		if json.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 (
	//	
	//	
	//		user
	//		123
	//	)
	router.POST("/loginXML", func(c *gin.Context) {
		var xml Login
		if err := c.ShouldBindXML(&xml); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}

		if xml.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) {
		var form Login
		// This will infer what binder to use depending on the content-type header.
		if err := c.ShouldBind(&form); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}

		if form.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:8080
	router.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.

package main

import (
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/validator/v10"
)

// Booking contains binded and validated data.
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"`
}

var bookableDate validator.Func = func(fl validator.FieldLevel) bool {
	date, ok := fl.Field().Interface().(time.Time)
	if ok {
		today := time.Now()
		if today.After(date) {
			return false
		}
	}
	return true
}

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

	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
		v.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()})
	}
}
$ curl "localhost:8085/bookable?check_in=2030-04-16&check_out=2030-04-17"
{"message":"Booking dates are valid!"}

$ curl "localhost:8085/bookable?check_in=2030-03-10&check_out=2030-03-09"
{"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"}

$ curl "localhost:8085/bookable?check_in=2000-03-09&check_out=2000-03-10"
{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}%

Struct level validations can also be registered this way. See the struct-lvl-validation example to learn more.

Only Bind Query String

ShouldBindQuery function only binds the query params and not the post data. See the detail information.

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

Bind Query String or Post Data

See the detail information.

package main

import (
	"log"
	"time"

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

type Person struct {
        Name       string    `form:"name"`
        Address    string    `form:"address"`
        Birthday   time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
        CreateTime time.Time `form:"createTime" time_format:"unixNano"`
        UnixTime   time.Time `form:"unixTime" time_format:"unix"`
}

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)
                log.Println(person.CreateTime)
                log.Println(person.UnixTime)
        }

	c.String(200, "Success")
}

Test it with:

$ curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033"

Bind Uri

See the detail information.

package main

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

type Person struct {
	ID string `uri:"id" binding:"required,uuid"`
	Name string `uri:"name" binding:"required"`
}

func main() {
	route := gin.Default()
	route.GET("/:name/:id", func(c *gin.Context) {
		var person Person
		if err := c.ShouldBindUri(&person); err != nil {
			c.JSON(400, gin.H{"msg": err})
			return
		}
		c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID})
	})
	route.Run(":8088")
}

Test it with:

$ curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3
$ curl -v localhost:8088/thinkerou/not-uuid

Bind Header

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
)

type testHeader struct {
	Rate   int    `header:"Rate"`
	Domain string `header:"Domain"`
}

func main() {
	r := gin.Default()
	r.GET("/", func(c *gin.Context) {
		h := testHeader{}

		if err := c.ShouldBindHeader(&h); err != nil {
			c.JSON(200, err)
		}

		fmt.Printf("%#v\n", h)
		c.JSON(200, gin.H{"Rate": h.Rate, "Domain": h.Domain})
	})

	r.Run()

// client
// curl -H "rate:300" -H "domain:music" 127.0.0.1:8080/
// output
// {"Domain":"music","Rate":300}
}

Bind HTML checkboxes

See the detail information

main.go

...

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

<form action="/" method="POST">
    <p>Check some colorsp>
    <label for="red">Redlabel>
    <input type="checkbox" name="colors[]" value="red" id="red">
    <label for="green">Greenlabel>
    <input type="checkbox" name="colors[]" value="green" id="green">
    <label for="blue">Bluelabel>
    <input type="checkbox" name="colors[]" value="blue" id="blue">
    <input type="submit">
form>

result:

{"color":["red","green","blue"]}

Multipart/Urlencoded binding

type ProfileForm struct {
	Name   string                `form:"name" binding:"required"`
	Avatar *multipart.FileHeader `form:"avatar" binding:"required"`

	// or for multiple files
	// Avatars []*multipart.FileHeader `form:"avatar" binding:"required"`
}

func main() {
	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:
		var form ProfileForm
		// in this case proper binding will be automatically selected
		if err := c.ShouldBind(&form); err != nil {
			c.String(http.StatusBadRequest, "bad request")
			return
		}

		err := c.SaveUploadedFile(form.Avatar, form.Avatar.Filename)
		if err != 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 "[email protected]/avatar.png" http://localhost:8080/profile

XML, JSON, YAML and ProtoBuf 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})
	})

	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 data
		c.ProtoBuf(http.StatusOK, data)
	})

	// Listen and serve on 0.0.0.0:8080
	r.Run(":8080")
}

SecureJSON

Using SecureJSON to prevent json hijacking. Default prepends "while(1)," to response body if the given struct is array values.

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

JSONP

Using JSONP to request data from a server in a different domain. Add callback to response body if the query parameter callback exists.

func main() {
	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:8080
	r.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.

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

	r.GET("/someJSON", func(c *gin.Context) {
		data := gin.H{
			"lang": "GO语言",
			"tag":  "
"
, } // will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"} c.AsciiJSON(http.StatusOK, data) }) // Listen and serve on 0.0.0.0:8080 r.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.

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

	// Serves unicode entities
	r.GET("/json", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"html": "Hello, world!",
		})
	})

	// Serves literal characters
	r.GET("/purejson", func(c *gin.Context) {
		c.PureJSON(200, gin.H{
			"html": "Hello, world!",
		})
	})

	// listen and serve 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 serve on 0.0.0.0:8080
	router.Run(":8080")
}

Serving data from file

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

	router.GET("/local/file", func(c *gin.Context) {
		c.File("local/file.go")
	})

	var fs http.FileSystem = // ...
	router.GET("/fs/file", func(c *gin.Context) {
		c.FileFromFS("fs/file.go", fs)
	})
}

Serving data from reader

func main() {
	router := gin.Default()
	router.GET("/someDataFromReader", func(c *gin.Context) {
		response, err := http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png")
		if err != nil || response.StatusCode != http.StatusOK {
			c.Status(http.StatusServiceUnavailable)
			return
		}

		reader := response.Body
 		defer reader.Close()
		contentLength := response.ContentLength
		contentType := response.Header.Get("Content-Type")

		extraHeaders := map[string]string{
			"Content-Disposition": `attachment; filename="gopher.png"`,
		}

		c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
	})
	router.Run(":8080")
}

HTML rendering

Using LoadHTMLGlob() or LoadHTMLFiles()

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>

Using templates with same name in different directories

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.tmplp>
html>
{{ end }}

templates/users/index.tmpl

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

Custom Template renderer

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

Custom Delimiters

You may use custom delims

	r := gin.Default()
	r.Delims("{[{", "}]}")
	r.LoadHTMLGlob("/path/to/templates")

Custom Template Funcs

See the detail example code.

main.go

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("./testdata/template/raw.tmpl")

    router.GET("/raw", func(c *gin.Context) {
        c.HTML(http.StatusOK, "raw.tmpl", gin.H{
            "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
        })
    })

    router.Run(":8080")
}

raw.tmpl

Date: {[{.now | formatAsDate}]}

Result:

Date: 2017/07/01

Multitemplate

Gin allow by default use only one html.Template. Check a multitemplate render for using features like go 1.6 block template.

Redirects

Issuing a HTTP redirect is easy. Both internal and external locations are supported.

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

Issuing a HTTP redirect from POST. Refer to issue: #444

r.POST("/test", func(c *gin.Context) {
	c.Redirect(http.StatusFound, "/foo")
})

Issuing a Router redirect, use HandleContext like below.

r.GET("/test", func(c *gin.Context) {
    c.Request.URL.Path = "/test2"
    r.HandleContext(c)
})
r.GET("/test2", func(c *gin.Context) {
    c.JSON(200, gin.H{"hello": "world"})
})

Custom Middleware

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

Using BasicAuth() middleware

// simulate some private data
var secrets = gin.H{
	"foo":    gin.H{"email": "[email protected]", "phone": "123433"},
	"austin": gin.H{"email": "[email protected]", "phone": "666"},
	"lena":   gin.H{"email": "[email protected]", "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")
}

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.

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

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

Support Let's Encrypt

example for 1-line LetsEncrypt HTTPS servers.

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

example for custom autocert manager.

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

Run multiple service using Gin

See the question and try the following example:

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 {
		err := server01.ListenAndServe()
		if err != nil && err != http.ErrServerClosed {
			log.Fatal(err)
		}
		return err
	})

	g.Go(func() error {
		err := server02.ListenAndServe()
		if err != nil && err != http.ErrServerClosed {
			log.Fatal(err)
		}
		return err
	})

	if err := g.Wait(); err != nil {
		log.Fatal(err)
	}
}

Graceful shutdown or restart

There are a few approaches you can use to perform a graceful shutdown or restart. You can make use of third-party packages specifically built for that, or you can manually do the same with the functions and methods from the built-in packages.

Third-party packages

We can use fvbock/endless to replace the default ListenAndServe. Refer to issue #296 for more details.

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

Alternatives:

  • 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.

Manually

In case you are using Go 1.8 or a later version, you may not need to use those libraries. Consider using http.Server's built-in Shutdown() method for graceful shutdowns. The example below describes its usage, and we've got more examples using gin here.

// +build go1.8

package main

import (
	"context"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"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,
	}

	// Initializing the server in a goroutine so that
	// it won't block the graceful shutdown handling below
	go func() {
		if err := srv.ListenAndServe(); err != nil && errors.Is(err, http.ErrServerClosed) {
			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)
	// 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 it
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit
	log.Println("Shutting down server...")

	// The context is used to inform the server it has 5 seconds to finish
	// the request it is currently handling
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if err := srv.Shutdown(ctx); err != nil {
		log.Fatal("Server forced to shutdown:", err)
	}

	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.

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

	t, err := loadTemplate()
	if err != nil {
		panic(err)
	}
	r.SetHTMLTemplate(t)

	r.GET("/", func(c *gin.Context) {
		c.HTML(http.StatusOK, "/html/index.tmpl",nil)
	})
	r.Run(":8080")
}

// loadTemplate loads templates embedded by go-assets-builder
func loadTemplate() (*template.Template, error) {
	t := template.New("")
	for name, file := range Assets.Files {
		defer file.Close()
		if file.IsDir() || !strings.HasSuffix(name, ".tmpl") {
			continue
		}
		h, err := ioutil.ReadAll(file)
		if err != nil {
			return nil, err
		}
		t, err = t.New(name).Parse(string(h))
		if err != nil {
			return nil, err
		}
	}
	return t, nil
}

See a complete example in the https://github.com/gin-gonic/examples/tree/master/assets-in-binary directory.

Bind form-data request with custom struct

The follow example using custom struct:

type StructA struct {
    FieldA string `form:"field_a"`
}

type StructB struct {
    NestedStruct StructA
    FieldB string `form:"field_b"`
}

type StructC struct {
    NestedStructPointer *StructA
    FieldC string `form:"field_c"`
}

type StructD struct {
    NestedAnonyStruct struct {
        FieldX string `form:"field_x"`
    }
    FieldD string `form:"field_d"`
}

func GetDataB(c *gin.Context) {
    var b StructB
    c.Bind(&b)
    c.JSON(200, gin.H{
        "a": b.NestedStruct,
        "b": b.FieldB,
    })
}

func GetDataC(c *gin.Context) {
    var b StructC
    c.Bind(&b)
    c.JSON(200, gin.H{
        "a": b.NestedStructPointer,
        "c": b.FieldC,
    })
}

func GetDataD(c *gin.Context) {
    var b StructD
    c.Bind(&b)
    c.JSON(200, gin.H{
        "x": b.NestedAnonyStruct,
        "d": b.FieldD,
    })
}

func main() {
    r := gin.Default()
    r.GET("/getb", GetDataB)
    r.GET("/getc", GetDataC)
    r.GET("/getd", GetDataD)

    r.Run()
}

Using the command curl command result:

$ curl "http://localhost:8080/getb?field_a=hello&field_b=world"
{"a":{"FieldA":"hello"},"b":"world"}
$ curl "http://localhost:8080/getc?field_a=hello&field_c=world"
{"a":{"FieldA":"hello"},"c":"world"}
$ curl "http://localhost:8080/getd?field_x=hello&field_d=world"
{"d":"world","x":{"FieldX":"hello"}}

Try to bind body into different structs

The normal methods for binding request body consumes c.Request.Body and they cannot be called multiple times.

type formA struct {
  Foo string `json:"foo" xml:"foo" binding:"required"`
}

type formB struct {
  Bar string `json:"bar" xml:"bar" binding:"required"`
}

func SomeHandler(c *gin.Context) {
  objA := formA{}
  objB := formB{}
  // This c.ShouldBind consumes c.Request.Body and it cannot be reused.
  if errA := 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.
  } else if errB := c.ShouldBind(&objB); errB == nil {
    c.String(http.StatusOK, `the body should be formB`)
  } else {
    ...
  }
}

For this, you can use c.ShouldBindBodyWith.

func SomeHandler(c *gin.Context) {
  objA := formA{}
  objB := formB{}
  // This reads c.Request.Body and stores the result into the context.
  if errA := 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.
  } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil {
    c.String(http.StatusOK, `the body should be formB JSON`)
  // And it can accepts other formats
  } else if errB2 := 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"
)

var html = template.Must(template.New("https").Parse(`


  Https Test
  


  

Welcome, Ginner!

`)) func main() { r := gin.Default() r.Static("/assets", "./assets") r.SetHTMLTemplate(html) r.GET("/", func(c *gin.Context) { if pusher := c.Writer.Pusher(); pusher != nil { // use pusher.Push() to do server push if err := 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:8080 r.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.

import (
	"log"
	"net/http"

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

func main() {
	r := gin.Default()
	gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
		log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers)
	}

	r.POST("/foo", func(c *gin.Context) {
		c.JSON(http.StatusOK, "foo")
	})

	r.GET("/bar", func(c *gin.Context) {
		c.JSON(http.StatusOK, "bar")
	})

	r.GET("/status", func(c *gin.Context) {
		c.JSON(http.StatusOK, "ok")
	})

	// Listen and Server in http://0.0.0.0:8080
	r.Run()
}

Set and get a cookie

import (
    "fmt"

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

func main() {

    router := gin.Default()

    router.GET("/cookie", func(c *gin.Context) {

        cookie, err := c.Cookie("gin_cookie")

        if err != nil {
            cookie = "NotSet"
            c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true)
        }

        fmt.Printf("Cookie value: %s \n", cookie)
    })

    router.Run()
}

Testing

The net/http/httptest package is preferable way for HTTP testing.

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

Test for code example above:

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

Users

Awesome project lists using Gin web framework.

  • gorush: A push notification server written in Go.
  • fnproject: The container native, cloud agnostic serverless platform.
  • photoprism: Personal photo management powered by Go and Google TensorFlow.
  • krakend: Ultra performant API Gateway with middlewares.
  • picfit: An image resizing server written in Go.
  • brigade: Event-based Scripting for Kubernetes.
  • dkron: Distributed, fault tolerant job scheduling system.
Issues
  • upgrade validator version to v9

    upgrade validator version to v9

    enhancement need-review break-backward 
    opened by thinkerou 39
  • To everyone who's new here

    To everyone who's new here

    To everyone who's new here:

    If you use RESTful-Style API, router of gin will be a TROUBLEMAKER!

    #205 #1681 #136 #2005

    The root of the problem is httprouter

    question 
    opened by TaceyWong 37
  • X-Forwarded-For handling is unsafe

    X-Forwarded-For handling is unsafe

    Allow specifying which headers to use for deducing client IP.

    Allow specifying which proxies you trust to provide those headers.

    Set defaults to match current behaviour.

    Fixes #2473 and #2232

    bug enhancement 
    opened by sorenisanerd 33
  • current panic make cpu skyrocket to 96%+,maybe this is bug.

    current panic make cpu skyrocket to 96%+,maybe this is bug.

    https://github.com/gin-gonic/gin/blob/73c4633943d596bdbeaa7d02cebdd4bd0c4f4630/render/json.go#L58 When the response is written to the writer, after the panic occurs here, the capture of the machine causes the CPU to skyrocket, which is close to 96%. When the 100% is reached, the request is lost, and the http service hangs.

    opened by daheige 33
  • Inability to use '/' for static files

    Inability to use '/' for static files

    Hi,

    I'm very sad to find out that gin doesn't allow you to have '/' as your top level route for static files. Almost every single web framework out there assumes that the static files you have inside your project are going to be served from $static_dir and they will be mapped as HTTP routes from '/'. An example from express.js does this:

    server.use(express.static(__dirname + '/public'));
    

    Doing that maps "/index.html" to "public/index.html". Expected behavior. Any other frameworks like Rails or Martini have this convention as well. Basically it means that if you have the following files inside a "./public" directory:

    --- public/
     |
     |--- index.html
     |--- css/
     |      |--- app.css
    

    Files will be accessible with HTTP requests like $server_url/index.html or $server_url/css/app.css. Gin disallows you to do this (although the problem lies in httprouter), and forces you to have a separate (non-top-level) route for public/static files like:

    router.Static("/static", "/var/www")
    

    So, if I use this:

    router.Static("/", "/var/www")
    

    I get this panic:

    panic: wildcard route conflicts with existing children
    
    goroutine 16 [running]:
    runtime.panic(0x2d5da0, 0xc2080013a0)
        /usr/local/go/src/pkg/runtime/panic.c:279 +0xf5
    github.com/julienschmidt/httprouter.(*node).insertChild(0xc208004360, 0xc208001302, 0xc208040de1, 0x10, 0xc208040e20)
        /Users/cachafla/Code/Go/go/src/github.com/julienschmidt/httprouter/tree.go:201 +0x11e
    github.com/julienschmidt/httprouter.(*node).addRoute(0xc208004360, 0xc208040de1, 0x10, 0xc208040e20)
        /Users/cachafla/Code/Go/go/src/github.com/julienschmidt/httprouter/tree.go:172 +0x952
    github.com/julienschmidt/httprouter.(*Router).Handle(0xc208040c60, 0x3ce8b0, 0x3, 0xc208040de0, 0x11, 0xc208040e20)
        /Users/cachafla/Code/Go/go/src/github.com/julienschmidt/httprouter/router.go:205 +0x186
    github.com/gin-gonic/gin.(*RouterGroup).Handle(0xc208070340, 0x3ce8b0, 0x3, 0xc208040de0, 0x11, 0xc2080380a0, 0x1, 0x1)
        /Users/cachafla/Code/Go/go/src/github.com/gin-gonic/gin/gin.go:223 +0x477
    github.com/gin-gonic/gin.(*RouterGroup).GET(0xc208070340, 0xc208040d40, 0x11, 0xc2080380a0, 0x1, 0x1)
        /Users/cachafla/Code/Go/go/src/github.com/gin-gonic/gin/gin.go:233 +0x6d
    github.com/gin-gonic/gin.(*RouterGroup).Static(0xc208070340, 0xc208040d40, 0x11, 0x3eed10, 0x8)
        /Users/cachafla/Code/Go/go/src/github.com/gin-gonic/gin/gin.go:276 +0x252
    

    It might seem silly, but this is actually an incorrect behavior for defining a static directory to be served by the HTTP server. The router should be able to handle multiple matching routes, as almost any other HTTP library out there In my case, I really need my index.html to be /index.html and not /static/index.html.

    In any case, I understand that this comes from a predefined httprouter behavior and I acknowledge that I could use a prefix and "deal with it", but I would prefer if this worked as it should. If somebody has any tips on how to workaround this issue I would really appreciate it, thanks!

    opened by cachafla 33
  • Using http.Handler?

    Using http.Handler?

    I'd like to use something like authboss that has its own http.Handler: https://godoc.org/gopkg.in/authboss.v0#Authboss.NewRouter

    With Gorilla Mux you can attach the handler to a routegroup like so:

    mux.PathPrefix("/auth").Handler(ab.NewRouter())
    

    I see httprouter has this functionality as well: http://godoc.org/github.com/julienschmidt/httprouter#Router.Handler

    Can you do something similar with Gin? If I just wrapped it in a context would it handle the routing correctly still? Thanks!

    enhancement question 
    opened by techjanitor 32
  • not all tests run locally (master)

    not all tests run locally (master)

    see out put of go test -v here: https://gist.github.com/kmulvey/16dc3cc49ef58c5517c6

    running in travis seems fine

    bug help wanted 
    opened by kmulvey 32
  • Add support for fasthttp or fasthttprouter

    Add support for fasthttp or fasthttprouter

    Fasthttp provides an http server optimized for high performance. It doesn't allocate memory in hot paths - the same approach is used by httprouter.

    With fasthttp gin may become even faster. See also fasthttprouter, httprouter fork based on fasthttp.

    enhancement 
    opened by valyala 31
  • New behavior of r.Run binds to localhost, not 0.0.0.0, issues in Docker

    New behavior of r.Run binds to localhost, not 0.0.0.0, issues in Docker

    Description

    Hi! In gin 1.6.0 it appears starting a default gin instance will now bind to:

    localhost:8080

    Instead of previously it would bind to just :8080 (0.0.0.0)

    This causes issues when used inside of Docker as it is binding to Docker's localhost rather than 0.0.0.0

    How to reproduce

    package main
    
    import (
    	"github.com/gin-gonic/gin"
    )
    
    func main() {
    	g := gin.Default()
    	g.GET("/hello/:name", func(c *gin.Context) {
    		c.String(200, "Hello %s", c.Param("name"))
    	})
    	g.Run()
    }
    

    More or less the example above should show gin starting on localhost:8080 rather than :8080 like in gin 1.5.0

    Expectations

    The expectation is by default, gin should bind the way it previously did

    A project that is setup to show this:

    https://github.com/DarthHater/hello-world/tree/TestBranch

    If you clone that repo, and run docker-compose up, you should see the project start on :8080 as it is using gin 1.5.0, if you switch the go.mod to use gin 1.6.0, you'll see localhost:8080, and if you attempt to hit:

    http://locahost:8080/ping

    You'll see empty response, as although Docker has exposed 8080, it's not making it through to the host application since it is bound to Docker's localhost rather than the machines 0.0.0.0

    Let me know if there are any questions! I was teaching some friends how to use gin, etc... and ran into this while doing so.

    bug 
    opened by DarthHater 30
  • Can we have a new release?

    Can we have a new release?

    I'm using Glide as a dependency manager and I'm using a new feature (ShouldBindUri which I guess it has 30 days merged in Master), can you guys create a new version so we can use those new features?

    opened by martin-dos-santos 30
  • Why there are both 'params' and 'Params' in Context struct

    Why there are both 'params' and 'Params' in Context struct

    I think lower-case params are useless. And the lower-case 'params' can be removed. Only router used the 'params' pointer. If I removed the lower-case 'params', I just have to pass the address of 'Params'. Like this:

    for i, tl := 0, len(t); i < tl; i++ {
    		if t[i].method != httpMethod {
    			continue
    		}
    		root := t[i].root
    		// use the address of c.Params
    		value := root.getValue(rPath, &c.Params, unescape)
    
    		if value.handlers != nil {
    			c.handlers = value.handlers
    			c.fullPath = value.fullPath
    			c.Next()
    			c.writermem.WriteHeaderNow()
    			return
    		}
    		if httpMethod != http.MethodConnect && rPath != "/" {
    			if value.tsr && engine.RedirectTrailingSlash {
    				redirectTrailingSlash(c)
    				return
    			}
    			if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
    				return
    			}
    		}
    		break
    	} 
    
    
    opened by Duslia997 0
  • [BUG] runtime error: slice bounds out of range [:2] with capacity 1

    [BUG] runtime error: slice bounds out of range [:2] with capacity 1

    • With issues:
      • Use the search tool before opening a new issue.
      • Please provide source code and commit sha if you found a bug.
      • Review existing issues and provide feedback or react to them.

    Description

    How to reproduce

    package main
    
    import "github.com/gin-gonic/gin"
    
    func main() {
    	r := gin.Default()
    	r.GET("/:p/a.gif", func(c *gin.Context) {
    		c.JSON(200, gin.H{
    			"message": "pong",
    		})
    	})
    	r.POST("/:p/a.gif", func(c *gin.Context) {
    		c.JSON(200, gin.H{
    			"message": "pong",
    		})
    	})
    	r.GET("/:p/b.gif", func(c *gin.Context) {
    		c.JSON(200, gin.H{
    			"message": "pong",
    		})
    	})
    	r.POST("/:p/b.gif", func(c *gin.Context) {
    		c.JSON(200, gin.H{
    			"message": "pong",
    		})
    	})
    	r.GET("/:p/c.gif", func(c *gin.Context) {
    		c.JSON(200, gin.H{
    			"message": "pong",
    		})
    	})
    	r.POST("/:p/c.gif", func(c *gin.Context) {
    		c.JSON(200, gin.H{
    			"message": "pong",
    		})
    	})
    	r.GET("/:p/all.gif", func(c *gin.Context) {
    		c.JSON(200, gin.H{
    			"message": "pong",
    		})
    	})
    	r.POST("/:p/all.gif", 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")
    }
    
    

    Expectations

    $ curl -X GET localhost:8080/a/p.gif
    404 not found
    

    Actual result

    $ curl -X GET localhost:8080/a/p.gif
    curl: (52) Empty reply from server
    

    Gin traceback:

    [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
    
    [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
     - using env:	export GIN_MODE=release
     - using code:	gin.SetMode(gin.ReleaseMode)
    
    [GIN-debug] GET    /:p/a.gif                 --> main.main.func1 (3 handlers)
    [GIN-debug] POST   /:p/a.gif                 --> main.main.func2 (3 handlers)
    [GIN-debug] GET    /:p/b.gif                 --> main.main.func3 (3 handlers)
    [GIN-debug] POST   /:p/b.gif                 --> main.main.func4 (3 handlers)
    [GIN-debug] GET    /:p/c.gif                 --> main.main.func5 (3 handlers)
    [GIN-debug] POST   /:p/c.gif                 --> main.main.func6 (3 handlers)
    [GIN-debug] GET    /:p/all.gif               --> main.main.func7 (3 handlers)
    [GIN-debug] POST   /:p/all.gif               --> main.main.func8 (3 handlers)
    [GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
    [GIN-debug] Listening and serving HTTP on :8080
    2021/09/22 18:27:14 http: panic serving 127.0.0.1:60804: runtime error: slice bounds out of range [:2] with capacity 1
    goroutine 20 [running]:
    net/http.(*conn).serve.func1()
    	/Users/xinlingchao/sdk/go1.17.1/src/net/http/server.go:1801 +0xb9
    panic({0x136afa0, 0xc000312240})
    	/Users/xinlingchao/sdk/go1.17.1/src/runtime/panic.go:1047 +0x266
    github.com/gin-gonic/gin.(*node).getValue(0x600, {0xc00031222c, 0x203000}, 0xc000128840, 0x0)
    	/Users/xinlingchao/.go/pkg/mod/github.com/gin-gonic/[email protected]/tree.go:481 +0xd70
    github.com/gin-gonic/gin.(*Engine).handleHTTPRequest(0xc000328d00, 0xc0001c0c00)
    	/Users/xinlingchao/.go/pkg/mod/github.com/gin-gonic/[email protected]/gin.go:482 +0x195
    github.com/gin-gonic/gin.(*Engine).ServeHTTP(0xc000328d00, {0x1415bf0, 0xc0003401c0}, 0xc0001c0b00)
    	/Users/xinlingchao/.go/pkg/mod/github.com/gin-gonic/[email protected]/gin.go:445 +0x1c5
    net/http.serverHandler.ServeHTTP({0xc0003440f0}, {0x1415bf0, 0xc0003401c0}, 0xc0001c0b00)
    	/Users/xinlingchao/sdk/go1.17.1/src/net/http/server.go:2878 +0x43b
    net/http.(*conn).serve(0xc00031ae60, {0x1418be0, 0xc000344000})
    	/Users/xinlingchao/sdk/go1.17.1/src/net/http/server.go:1929 +0xb08
    created by net/http.(*Server).Serve
    	/Users/xinlingchao/sdk/go1.17.1/src/net/http/server.go:3033 +0x4e8
    
    

    Environment

    • go version: 1.17.1
    • gin version (or commit ref): 1.7.4
    • operating system: Mac 0S 11.6
    opened by douglarek 1
  • Map validation, contextual validation, validator tags for custom map/slice types

    Map validation, contextual validation, validator tags for custom map/slice types

    This pull request offers a few simple, but very useful improvements:

    1. Adds support for validating maps, including registering tags for map types (as well as slice types).
    2. Adds support for contextual validation. Custom validators (e.g. those registered with (*Validator).RegisterValidationCtx or (*Validator).RegisterStructValidationCtx) will be passed the Gin context (as context.Context). Resolves https://github.com/gin-gonic/gin/issues/2741
    3. Returns slice and map validation errors are regular validator.ValidationErrors, without any custom types. This way callers can handle all validation errors in the same manner. Related to: https://github.com/gin-gonic/gin/pull/2777

    A further improvement would be to merge this PR: https://github.com/gin-gonic/gin/pull/2825. That will enable getting the actual *gin.Context in custom validators, resolving https://github.com/gin-gonic/gin/issues/1489.

    opened by kszafran 1
  • Question : api in websites

    Question : api in websites

    Within an http website, I want to have a special path e.g. /api/xxx bound to API. While other path except /api should be served as request to static web site on disk.

    	router.GET("/api/xxx", func(c *gin.Context) {
    		c.String(http.StatusOK, "Hello xxx")
    	})
    
    	router.Static("/", "./www")
    

    But the above code crashed.

    [GIN-debug] GET    /api/xxx                  --> main.main.func1 (3 handlers)
    [GIN-debug] GET    /*filepath                --> github.com/gin-gonic/gin.(*RouterGroup).createStaticHandler.func1 (3 handlers)
    panic: catch-all conflicts with existing handle for the path segment root in path '/*filepath'
    
    opened by wangjia184 0
  • "panic: runtime error: invalid memory address or nil pointer dereference" when used in a goroutine for paho.mqtt

    Description

    I'm creating an API server with gin with a POST API (json body) to publish a paho.mqtt message and respond back with "200" if there aren't errors.

    POST request -> gin API server -> publish paho.mqtt -> send mqtt message to destination -> return http 200 ok

    As suggested on the doc I copied the gin context with cCp := c.Copy() and then used cCp to send a responde cCp.JSON(...)

    How to reproduce

    package main
    import (
    	"fmt"
    	mqtt "github.com/eclipse/paho.mqtt.golang"
    	"github.com/gin-gonic/gin"
    	"net/http"
    )
    var mqttClient mqtt.Client
    func main() {
    	mqttClient = mqtt.NewClient(mqtt.NewClientOptions().AddBroker("tcp://127.0.0.1:1883").SetClientID("apiServer"))
    	if token := mqttClient.Connect(); token.Wait() && token.Error() != nil {
    		panic(token.Error())
    	}
    	router := gin.Default()
    	router.POST("/sendtomqtt", func (c *gin.Context) {
    		// create copy to be used inside the goroutine
    		cCp := c.Copy()
    		t := mqttClient.Publish("test/test2", 0, false, "testtesttest")
    		go func() {
    			_ = t.Wait()
    			if t.Error() != nil {
    				fmt.Println(t.Error())
    			} else {
    				cCp.JSON(http.StatusOK, gin.H{"message": "sent"})
    			}
    		}()
    	})
    	err := router.Run(":8081")
    	if err != nil {
    		panic(err)
    	}
    }
    

    Expectations

    gin server should't crash with a POST request, even if used to call a mqtt server

    Actual result

    [GIN-debug] POST   /sendtomqtt               --> main.main.func1 (3 handlers)
    [GIN-debug] Listening and serving HTTP on :8081
    
    [GIN] 2021/09/18 - 14:24:41 | 200 |      16.877µs | 192.168.178.128 | POST     "/sendtomqtt"
    panic: runtime error: invalid memory address or nil pointer dereference
    [signal SIGSEGV: segmentation violation code=0x1 addr=0x18 pc=0x131df49]
    
    goroutine 35 [running]:
    github.com/gin-gonic/gin.(*responseWriter).Header(0xc00018ce00)
    	<autogenerated>:1 +0x29
    github.com/gin-gonic/gin/render.writeContentType({0x1d15f70, 0xc00018ce00}, {0x168a770, 0x1, 0x1})
    	/Users/ks89/go/pkg/mod/github.com/gin-gonic/[email protected]/render/render.go:36 +0x3a
    github.com/gin-gonic/gin/render.WriteJSON({0x1d15f70, 0xc00018ce00}, {0x136c980, 0xc000090bd0})
    	/Users/ks89/go/pkg/mod/github.com/gin-gonic/[email protected]/render/json.go:68 +0x4c
    github.com/gin-gonic/gin/render.JSON.Render(...)
    	/Users/ks89/go/pkg/mod/github.com/gin-gonic/[email protected]/render/json.go:55
    github.com/gin-gonic/gin.(*Context).Render(0xc00018ce00, 0xc8, {0x144b680, 0xc000094380})
    	/Users/ks89/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:913 +0xe7
    github.com/gin-gonic/gin.(*Context).JSON(...)
    	/Users/ks89/go/pkg/mod/github.com/gin-gonic/[email protected]/context.go:956
    main.main.func1.1()
    	/Users/ks89/git/gin-with-mqtt/main.go:24 +0x13a
    created by main.main.func1
    	/Users/ks89/git/gin-with-mqtt/main.go:19 +0xc8
    exit status 2
    

    Environment

    • go version: 1.17
    • gin version (or commit ref): 1.7.4
    • operating system: macOS BigSur 11.6
    • github.com/eclipse/paho.mqtt.golang version: 1.3.5
    opened by Ks89 1
  • Context.reset should be locked against keys

    Context.reset should be locked against keys

    Description

    1、 request A interface, pass the gin.Context to the goroutine, which has a lifetime larger than the entire request (downloading the file).
    2、 request B interface(Got the context of old request A, and reset) 3、 At that time, the execution of A's goroutine reached the sixth line of the Context.Set when it was reset:

    func (c *Context) Set(key string, value interface{}) {
    	c.mu.Lock()
    	if c.Keys == nil {
    		c.Keys = make(map[string]interface{})
    	}
    
    	c.Keys[key] = value
    	c.mu.Unlock()
    }
    

    4、 panic: assignment to entry in nil map

    How to reproduce

    func main() {
    	route := gin.Default()
    	route.GET("/a", A)
    	route.GET("/b", B)
    	route.Run(":8902")
    }
    
    
    func A(ctx *gin.Context) {
    	go func(c *gin.Context) {
    		time.Sleep(time.Microsecond)
    		for i := 0; i < 1000000; i++ {
    			c.Set("name", "helios")
    		}
    	}(ctx)
    
    	ctx.String(http.StatusOK, "okA")
    }
    
    func B(ctx *gin.Context) {
    	fmt.Println(ctx.Keys == nil)
    	ctx.String(http.StatusOK, "okB")
    }
    

    With the following script:

    for i in {1..300}
    do
    	curl localhost:8902/a
    	curl localhost:8902/b
    done
    

    Expectations

    no panic

    Actual result

    panic: assignment to entry in nil map
    
    goroutine 1104 [running]:
    github.com/gin-gonic/gin.(*Context).Set(0xc00014af00, 0x160f463, 0x4, 0x154d0a0, 0x16dc340)
            /Users/helios/Desktop/helios/openresource/gin/context.go:246 +0x7c
    main.A.func1(0xc00014af00)
            /Users/helios/Desktop/helios/openresource/gin/examples/main.go:29 +0x72
    created by main.A
            /Users/helios/Desktop/helios/openresource/gin/examples/main.go:26 +0x43
    

    Environment

    • go version: 1.13
    • gin version (or commit ref): 1.6.3
    • operating system:
    opened by helios741 0
  • context.Keys should be private

    context.Keys should be private

    as the number of users increases, the methods you provide will certainly be used by users, with or without commitment.

    Description

    as the title suggests, When the following situation is encountered, "fatal error: concurrent map read and map write" may appear :

    func main() {
    	route := gin.Default()
    	route.GET("/a", A)
    	route.GET("/b", B)
    	route.Run(":8902")
    }
    
    
    func A(ctx *gin.Context) {
    	go func() {
    		time.Sleep(time.Microsecond)
    		for i := 0; i < 1000000; i++ {
    			ctx.Get("age")
    		}
    	}()
    
    	ctx.String(http.StatusOK, "okA")
    }
    
    func B(ctx *gin.Context) {
    	time.Sleep(time.Microsecond)
    	ctx.Set("_CTX_KEY", "ctx")
    	for i := 0; i < 1000000; i++ {
    		ctx.Keys["name"] = "helios"
    	}
    	
    	ctx.String(http.StatusOK, "okB")
    }
    

    If there are unfamiliar people who do not use Context.Set to set the property then there will be a problem. If context.Keys was a private variable then this problem would be avoided, but it seems to be difficult now

    How to reproduce

    for i in {1..300}
    do
    	curl localhost:8902/a
    	curl localhost:8902/b
    done
    

    Expectations

    service ok

    Actual result

    panic

    Environment

    • go version: 1.13
    • gin version (or commit ref): 1.6.3
    • operating system:
    opened by helios741 1
  • why is NoMethod marked as TODO

    why is NoMethod marked as TODO

    Description

    What is the role of NoMethod , it does not seem to be effective

    I found it is marked as TODO :

    // NoMethod sets the handlers called when... TODO.
    func (engine *Engine) NoMethod(handlers ...HandlerFunc) {
    	engine.noMethod = handlers
    	engine.rebuild405Handlers()
    }
    

    How to reproduce

    package main
    
    import (
    	"fmt"
    	"github.com/gin-gonic/gin"
    	"net/http"
    )
    
    func main() {
    	r := gin.Default()
    	r.NoRoute(func(c *gin.Context) {
    		c.String(http.StatusOK, "NoRoute")
    	})
    	r.NoMethod(func(c *gin.Context) {
    		fmt.Println("NoMethod")
    		c.String(http.StatusOK, "NoMethod")
    	})
    	r.POST("/", func(c *gin.Context) {
    		c.String(http.StatusOK, "/")
    	})
    	r.Run(":8080")
    }
    

    Expectations

    $ curl http://localhost:8080
    NoMethod
    
    $ curl -X POST http://localhost:8080
    /
    
    $ curl -X PUT http://localhost:8080
    NoMethod
    

    Actual result

    $ curl http://localhost:8080
    NoRoute
    
    $ curl -X POST http://localhost:8080
    /
    
    $ curl -X PUT http://localhost:8080
    NoRoute
    

    Environment

    • go version: go version go1.17 windows/amd64
    • gin version (or commit ref): v1.7.4
    • operating system: windows/amd64
    opened by togettoyou 3
  • Adding Handler to Route if Not Exists

    Adding Handler to Route if Not Exists

    I want to write a common component that registers routes if they do not yet exist. I would like to create a common component we can use in several of our gin servers so as to avoid boiler plate code. For example, I would like to add a /status endpoint that returns a 200. Some services may implement their own /status endpoint. They may have more complicated health checks that need to happen.

    I would like to run something like this.

    func addCommonRoutes(e *gin.Engine) {
      if doesNotHave("/status", e) {
        e.GET("/status", statusHandler)
      }
      if doesNotHave("/kaboom", e) {
        e.GET("/kaboom", kaboomHandler)
      }
    }
    

    Is there a way to write this? I am aware that attempting to register a handler to the same route multiple times will result in a panic, so I would like to avoid that.

    opened by juansc 0
Releases(v1.7.4)
  • v1.7.3(Aug 2, 2021)

  • v1.7.2(May 21, 2021)

  • v1.7.1(Apr 8, 2021)

  • v1.7.0(Apr 8, 2021)

    BUGFIXES

    • fix compile error from #2572 (#2600)
    • fix: print headers without Authorization header on broken pipe (#2528)
    • fix(tree): reassign fullpath when register new node (#2366)

    ENHANCEMENTS

    • Support params and exact routes without creating conflicts (#2663)
    • chore: improve render string performance (#2365)
    • Sync route tree to httprouter latest code (#2368)
    • chore: rename getQueryCache/getFormCache to initQueryCache/initFormCa (#2375)
    • chore(performance): improve countParams (#2378)
    • Remove some functions that have the same effect as the bytes package (#2387)
    • update:SetMode function (#2321)
    • remove a unused type SecureJSONPrefix (#2391)
    • Add a redirect sample for POST method (#2389)
    • Add CustomRecovery builtin middleware (#2322)
    • binding: avoid 2038 problem on 32-bit architectures (#2450)
    • Prevent panic in Context.GetQuery() when there is no Request (#2412)
    • Add GetUint and GetUint64 method on gin.context (#2487)
    • update content-disposition header to MIME-style (#2512)
    • reduce allocs and improve the render WriteString (#2508)
    • implement ".Unwrap() error" on Error type (#2525) (#2526)
    • Allow bind with a map[string]string (#2484)
    • chore: update tree (#2371)
    • Support binding for slice/array obj [Rewrite] (#2302)
    • basic auth: fix timing oracle (#2609)
    • Add mixed param and non-param paths (port of httprouter#329) (#2663)
    • feat(engine): add trustedproxies and remoteIP (#2632)
    Source code(tar.gz)
    Source code(zip)
  • v1.6.3(May 3, 2020)

  • v1.6.2(Mar 26, 2020)

  • v1.6.1(Mar 23, 2020)

  • v1.6.0(Mar 21, 2020)

    Release Notes

    BREAKING

    • chore(performance): Improve performance for adding RemoveExtraSlash flag (#2159)
    • drop support govendor (#2148)
    • Added support for SameSite cookie flag (#1615)

    FEATURES

    • add yaml negotitation (#2220)
    • FileFromFS (#2112)

    BUGFIXES

    • Unix Socket Handling (#2280)
    • Use json marshall in context json to fix breaking new line issue. Fixes #2209 (#2228)
    • fix accept incoming network connections (#2216)
    • Fixed a bug in the calculation of the maximum number of parameters (#2166)
    • [FIX] allow empty headers on DataFromReader (#2121)
    • Add mutex for protect Context.Keys map (#1391)

    ENHANCEMENTS

    • Add mitigation for log injection (#2277)
    • tree: range over nodes values (#2229)
    • tree: remove duplicate assignment (#2222)
    • chore: upgrade go-isatty and json-iterator/go (#2215)
    • path: sync code with httprouter (#2212)
    • Use zero-copy approach to convert types between string and byte slice (#2206)
    • Reuse bytes when cleaning the URL paths (#2179)
    • tree: remove one else statement (#2177)
    • tree: sync httprouter update (#2173) (#2172) (#2171)
    • tree: sync part httprouter codes and reduce if/else (#2163)
    • use http method constant (#2155)
    • upgrade go-validator to v10 (#2149)
    • Refactor redirect request in gin.go (#1970)
    • Add build tag nomsgpack (#1852)

    DOCS

    • docs(path): improve comments (#2223)
    • Renew README to fit the modification of SetCookie method (#2217)
    • Fix spelling (#2202)
    • Remove broken link from README. (#2198)
    • Update docs on Context.Done(), Context.Deadline() and Context.Err() (#2196)
    • Update validator to v10 (#2190)
    • upgrade go-validator to v10 for README (#2189)
    • Update to currently output (#2188)
    • Fix "Custom Validators" example (#2186)
    • Add project to README (#2165)
    • docs(benchmarks): for gin v1.5 (#2153)
    • Changed wording for clarity in README.md (#2122)

    MISC

    • ci support go1.14 (#2262)
    • chore: upgrade depend version (#2231)
    • Drop support go1.10 (#2147)
    • fix comment in mode.go (#2129)

    Contributors

    Source code(tar.gz)
    Source code(zip)
  • v1.5.0(Nov 27, 2019)

    Feature

    • [NEW] Now you can parse the inline lowercase start structure #1893
    • [NEW] [Break-Backward] Hold matched route full path in the Context #1826
    • [NEW] Add context param query cache #1450
    • [NEW] Add support of multipart multi files #1949
    • [NEW] Support bind http header param #1957
    • [NEW] Support bind unix time #1980
    • [NEW] Support negative Content-Length in DataFromReader #1981
    • [NEW] Add DisallowUnknownFields() in gin.Context.BindJSON() #2028
    • [NEW] Use specific net.Listener with Engine.RunListener() #2023

    Bug fix

    • [FIX] Use DefaultWriter and DefaultErrorWriter for debug messages #1891
    • [FIX] Some code improvements #1909
    • [FIX] Use encode replace json marshal increase json encoder speed #1546
    • [FIX] Fix context.Params race condition on Copy() #1841
    • [FIX] Improve GetQueryMap performance #1918
    • [FIX] Improve get post data #1920
    • [FIX] Use context instead of x/net/context #1922
    • [FIX] Attempt to fix PostForm cache bug #1931
    • [FIX] [Break-Backward] Drop support for go1.8 and go1.9 #1933
    • [FIX] Bugfix for the FullPath feature #1919
    • [FIX] Gin1.5 bytes.Buffer to strings.Builder #1939
    • [FIX] Upgrade github.com/ugorji/go/codec #1969
    • [FIX] Simplify code #2004
    • [FIX] Identify terminal on a RISC-V architecture for auto-colored logs #2019
    • [FIX] [Break-Backward] Context.JSONP() now expects a semicolon (;) at the end #2007
    • [FIX] [Break-Backward] Upgrade validator version to v9 #1015
    • [FIX] Fix some typo #2079 #2080
    • [FIX] Relocate binding body tests #2086
    • [FIX] Use Writer in Context.Status #1606
    • [FIX] Engine.RunUnix() now returns the error if it can't change the file mode #2093
    • [FIX] RouterGroup.StaticFS() leaked files. Now it closes them. #2118
    • [FIX] Context.Request.FormFile leaked file, now it closes it #2114
    • [FIX] Ignore walking on form:"-" mapping #1943
    • [REFACTOR] [Break-Backward] Use encode replace json marshal increase json encoder speed #1546
    Source code(tar.gz)
    Source code(zip)
  • v1.4.0(May 7, 2019)

    Feature

    • [NEW] Support for Go Modules #1569
    • [NEW] Refactor of form mapping multipart requesta #1829
    • [NEW] Supporting file binding #1264
    • [NEW] Add support for mapping arrays #1797
    • [NEW] Make context.Keys available as LogFormatterParams #1779
    • [NEW] Use internal/json for Marshal/Unmarshal #1791
    • [NEW] Support mapping time.Duration #1794
    • [NEW] Refactor form mappings #1749
    • [NEW] Added flag to context.Stream indicates if client disconnected in middle of stream #1252
    • [NEW] Extend context.File to allow for the content-dispositon attachments via a new method context.Attachment #1260
    • [NEW] Add prefix from X-Forwarded-Prefix in redirectTrailingSlash #1238
    • [NEW] Add context.HandlerNames() #1729
    • [NEW] Add response size to LogFormatterParams #1752
    • [NEW] Allow ignoring field on form mapping #1733
    • [NEW] Add a function to force color in console output. #1724
    • [NEW] Binding for URL Params #1694
    • [NEW] Add LoggerWithFormatter method #1677
    • [NEW] RunFd method to run http.Server through a file descriptor #1609
    • [NEW] Yaml binding support #1618
    • [NEW] Add PureJSON renderer #694
    • [NEW] Set default time format in form binding #1487
    • [NEW] Upgrade dependency libraries #1491

    Bug fix

    • [FIX] Truncate Latency precision in long running request #1830
    • [FIX] IsTerm flag should not be affected by DisableConsoleColor method. #1802
    • [FIX] Readme updates #1793 #1788 1789
    • [FIX] StaticFS: Fixed Logging two log lines on 404. #1805, #1804
    • [FIX] Moved examples to stand alone Repo #1775
    • [FIX] Support HTTP content negotiation wildcards #1112
    • [FIX] Pass MaxMultipartMemory when FormFile is called #1600
    • [FIX] LoadHTML* tests #1559
    • [FIX] Removed use of sync.pool from HandleContext #1565
    • [FIX] Format output log to os.Stderr #1571
    • [FIX] Make logger use a yellow background and a darkgray text for legibility #1570
    • [FIX] Remove sensitive request information from panic log. #1370
    • [FIX] log.Println() does not print timestamp #829 #1560
    • [FIX] Add missing copyright and update if/else #1497
    • [FIX] Update msgpack usage #1498
    • [FIX] Use protobuf on render #1496
    • [FIX] Add support for Protobuf format response #1479
    • [FIX] Add BindXML and ShouldBindXML #1485
    • [FIX] CI testing updates #1671 #1670 #1682 #1669
    • [FIX] StaticFS(): Send 404 when path does not exist #1663
    • [FIX] Handle nil body for JSON binding #1638
    • [FIX] Support bind uri param #1612
    • [FIX] recovery: fix issue with syscall import on google app engine #1640
    • [FIX] Make sure the debug log contains line breaks #1650
    • [FIX] Panic stack trace being printed during recovery of broken pipe #1089 #1259
    • [FIX] Context.Next() - recheck len of handlers on every iteration. #1745
    • [FIX] Fix all errcheck warnings #1739 #1653
    • [FIX] Change color methods to public in the defaultLogger. #1771
    • [FIX] Update writeHeaders method to use http.Header.Set #1722
    • [FIX] context.Copy() race condition #1020
    Source code(tar.gz)
    Source code(zip)
  • v1.3.0(Aug 14, 2018)

    CHANGELOG

    Gin 1.3.0

    Source code(tar.gz)
    Source code(zip)
  • v1.2(Jul 2, 2017)

    • [NEW] Switch from godeps to govendor
    • [NEW] Add support for Let's Encrypt via gin-gonic/autotls
    • [NEW] Improve README examples and add extra at examples folder
    • [NEW] Improved support with App Engine
    • [NEW] Add custom template delimiters, see #860
    • [NEW] Add Template Func Maps, see #962
    • [NEW] Add *context.Handler(), see #928
    • [NEW] Add *context.GetRawData()
    • [NEW] Add *context.GetHeader() (request)
    • [NEW] Add *context.AbortWithStatusJSON() (JSON content type)
    • [NEW] Add *context.Keys type cast helpers
    • [NEW] Add *context.ShouldBindWith()
    • [NEW] Add *context.MustBindWith()
    • [NEW] Add *engine.SetFuncMap()
    • [DEPRECATE] On next release: *context.BindWith(), see #855
    • [FIX] Refactor render
    • [FIX] Reworked tests
    • [FIX] logger now supports cygwin
    • [FIX] Use X-Forwarded-For before X-Real-Ip
    • [FIX] time.Time binding (#904)
    Source code(tar.gz)
    Source code(zip)
  • v1.1.4(Dec 4, 2016)

  • v1.1.3(Dec 3, 2016)

  • v1.1(Dec 3, 2016)

    v1.1 (#751)

    • Implement QueryArray and PostArray methods
    • Refactor GetQuery and GetPostForm
    • Removed additional Iota

    I think assigning iota to each constant is not required

    • Add 1.7 test.
    • Add codecov.io
    • corrected a typo in README
    • remove coveralls services.

    Signed-off-by: Bo-Yi Wu [email protected]

    • Update TravisCI to Gitter webhook
    • Add codecov.yml Gitter webhook
    • Changed imports to gopkg instead of github in README (#733)
    • Add contribution guide
    • Update go get for stable version

    In the future, github default branch will be develop so running go get github.com/gin-gonic/gin will pull latest code from develop.

    • Changed imports to gopkg instead of github in README
    • Update README.md
    • Logger: skip ANSI color commands if output is not a tty

    Special note: 👏 Thanks to @appleboy and @tboerger for their awesome work managing PRs and issues for this version. This just only started :wink:

    Source code(tar.gz)
    Source code(zip)
  • v1.0-rc.2(Oct 12, 2016)

    • This tag / release points to the latest commit as of today in develop.
    • Added a new maintainer to the organization @appleboy

    (Expect a final v1.0 very soon, for now you can use this release as the latest stable work available)

    Source code(tar.gz)
    Source code(zip)
  • v1.0rc1(May 22, 2015)

    • [PERFORMANCE] Zero allocation router
    • [PERFORMANCE] Faster JSON, XML and text rendering
    • [PERFORMANCE] Custom hand optimized HttpRouter for Gin
    • [PERFORMANCE] Misc code optimizations. Inlining, tail call optimizations
    • [NEW] Built-in support for golang.org/x/net/context
    • [NEW] Any(path, handler). Create a route that matches any path
    • [NEW] Refactored rendering pipeline (faster and static typeded)
    • [NEW] Refactored errors API
    • [NEW] IndentedJSON() prints pretty JSON
    • [NEW] Added gin.DefaultWriter
    • [NEW] UNIX socket support
    • [NEW] RouterGroup.BasePath is exposed
    • [NEW] JSON validation using go-validate-yourself (very powerful options)
    • [NEW] Completed suite of unit tests
    • [NEW] HTTP streaming with c.Stream()
    • [NEW] StaticFile() creates a router for serving just one file.
    • [NEW] StaticFS() has an option to disable directory listing.
    • [NEW] StaticFS() for serving static files through virtual filesystems
    • [NEW] Server-Sent Events native support
    • [NEW] WrapF() and WrapH() helpers for wrapping http.HandlerFunc and http.Handler
    • [NEW] Added LoggerWithWriter() middleware
    • [NEW] Added RecoveryWithWriter() middleware
    • [NEW] Added DefaultPostFormValue()
    • [NEW] Added DefaultFormValue()
    • [NEW] Added DefaultParamValue()
    • [FIX] BasicAuth() when using custom realm
    • [FIX] Bug when serving static files in nested routing group
    • [FIX] Redirect using built-in http.Redirect()
    • [FIX] Logger when printing the requested path
    • [FIX] Documentation typos
    • [FIX] Context.Engine renamed to Context.engine
    • [FIX] Better debugging messages
    • [FIX] ErrorLogger
    • [FIX] Debug HTTP render
    • [FIX] Refactored binding and render modules
    • [FIX] Refactored Context initialization
    • [FIX] Refactored BasicAuth()
    • [FIX] NoMethod/NoRoute handlers
    • [FIX] Hijacking http
    • [FIX] Better support for Google App Engine (using log instead of fmt)
    Source code(tar.gz)
    Source code(zip)
:zap: Go web framework benchmark

go-web-framework-benchmark This benchmark suite aims to compare the performance of Go web frameworks. It is inspired by Go HTTP Router Benchmark but t

smallnest 1.6k Sep 24, 2021
Swagger + Gin = SwaGin, a web framework based on Gin and Swagger

Swagger + Gin = SwaGin Introduction SwaGin is a web framework based on Gin and Swagger, which wraps Gin and provides built-in swagger api docs and req

long2ice 15 Aug 31, 2021
Swagger + Gin = SwaGin, a web framework based on Gin and Swagger

Swagger + Gin = SwaGin Introduction SwaGin is a web framework based on Gin and Swagger, which wraps Gin and provides built-in swagger api docs and req

long2ice 15 Aug 31, 2021
Opinionated Go starter with gin for REST API, logrus for logging, viper for config with added graceful shutdown

go-gin-starter An opinionated starter for Go Backend projects using: gin-gonic/gin as the REST framework logrus for logging viper for configs Docker f

Udaya Prakash 55 Sep 22, 2021
Yet Another REST Framework

YARF: Yet Another REST Framework YARF is a fast micro-framework designed to build REST APIs and web services in a fast and simple way. Designed after

null 62 Jun 30, 2021
Gzip Middleware for Go

An out-of-the-box, also customizable gzip middleware for Gin and net/http.

LI Zhennan 119 Jul 13, 2021
letgo is an open-source, high-performance web framework for the Go programming language.

high-performance Lightweight web framework for the Go programming language. golang web framework,高可用golang web框架,go语言 web框架 ,go web

wjp 171 Sep 18, 2021
golang crud restful api with gorm , gin and mysql DB

crud restful api with golang , gorm , gin and mysql this api does a simple CRUD operations on a single table mysql Database . this is build on top off

Brahim Bessrour 13 Mar 25, 2021
基于Golang的框架Gin开发,项目结构和理念参考Laravel。现支持:MySQL、Redis、MVC、拦截器、助手函数、fresh热更、swagger-UI、tpl模版输出、安全的Api。

GinLaravel的构建和运行周期与Beego、Vue、React、Laravel、ThinkPHP、Django等都会有类似的引导思路、参数设置、插件扩展、服务部署、代码统一性、生态护城河等。

方圆 22 Sep 16, 2021
An ideally refined web framework for Go.

Air An ideally refined web framework for Go. High-performance? Fastest? Almost all web frameworks are using these words to tell people that they are t

Aofei Sheng 407 Sep 2, 2021
An ideally refined web framework for Go.

Air An ideally refined web framework for Go. High-performance? Fastest? Almost all web frameworks are using these words to tell people that they are t

Aofei Sheng 406 Sep 20, 2021
🍐 Elegant Golang REST API Framework

An Elegant Golang Web Framework Goyave is a progressive and accessible web application framework focused on REST APIs, aimed at making backend develop

Goyave 892 Sep 22, 2021
用来显示 markdown 文档的,基于 gin 框架的, go 语言开发的博客

Bro Qiang 博客 示例: broqiang.com (个人博客,正在使用) 源码: github.com/broqiang/mdblog 提示 此项目已经由 dep 管理更换为 mod 管理 环境 开发环境 Ubuntu 18.04 服务器环境 CentOS 7.6 其他环境没有测试过,不确

broqiang 286 Sep 15, 2021
The jin is a simplified version of the gin web framework that can help you quickly understand the core principles of a web framework.

jin About The jin is a simplified version of the gin web framework that can help you quickly understand the core principles of a web framework. If thi

null 7 Sep 19, 2021
Flamingo Framework and Core Library. Flamingo is a go based framework for pluggable web projects. It is used to build scalable and maintainable (web)applications.

Flamingo Framework Flamingo is a web framework based on Go. It is designed to build pluggable and maintainable web projects. It is production ready, f

Flamingo 232 Sep 20, 2021
Gerasimos (Makis) Maropoulos 21.2k Sep 18, 2021
Rocinante is a gin inspired web framework built on top of net/http.

Rocinante Rocinante is a gin inspired web framework built on top of net/http. ⚙️ Installation $ go get -u github.com/fskanokano/rocinante-go ⚡️ Quicks

null 3 Jul 27, 2021
⚡️ Express inspired web framework written in Go

Fiber is an Express inspired web framework built on top of Fasthttp, the fastest HTTP engine for Go. Designed to ease things up for fast development w

Fiber 15.4k Sep 22, 2021
re:Web enables classic web applications to run on AWS Lambda.

re:Web re:Web enables classic web applications to run on AWS Lambda. re:Web interfaces with the Lambda Runtime API. It translates API Gateway requests

null 71 Jul 4, 2021