No Description

田欧 7180f2ba62 not use tmp var (#1032) 8 years ago
binding 8f861946b0 style(msgpack): remove redundant comments (#1027) 8 years ago
examples 93b3a0d7ec feat(context): add SaveUploadedFile func. (#1022) 8 years ago
fixtures a40699e07f add FuncMap testing 8 years ago
ginS 9ee5afff48 fix markdown render style (#943) 8 years ago
render 8436a9d829 fix(render): remove repeated static check. (#998) 8 years ago
vendor 65a6dd46a5 chore(vendor): update jsonite rev, #1010 (#1012) 8 years ago
.gitignore 46220b726d feat: support Let's Encrypt tls. 8 years ago
.travis.yml e38955615a Merge pull request #520 from 178inaba/travis-import_path 8 years ago
AUTHORS.md b8be9df642 docs: add graceful-shutdown example for go 1.8 (#835) 8 years ago
BENCHMARKS.md b8be9df642 docs: add graceful-shutdown example for go 1.8 (#835) 8 years ago
CHANGELOG.md bea012a17f docs(changelog): add #962 template func maps 8 years ago
CODE_OF_CONDUCT.md 22ee916dfe docs(conduct): add code of conduct 8 years ago
CONTRIBUTING.md 1923b35983 update document format. (#964) 8 years ago
LICENSE b6bd5b0d9f Add MIT license 11 years ago
Makefile 1e1e4fc867 Add Makefile to check the following thing (#947) 8 years ago
README.md 30cfa590bb dos(upload): fix alignment (#1030) 8 years ago
auth.go b539606eed use return not use else (#1028) 8 years ago
auth_test.go a91893d22b Fixes auth test 10 years ago
benchmarks_test.go 4ad3baf44e Add license for some files (#940) 8 years ago
codecov.yml 32cab500ec v1.1 (#751) 9 years ago
context.go 7180f2ba62 not use tmp var (#1032) 8 years ago
context_appengine.go 4ad3baf44e Add license for some files (#940) 8 years ago
context_test.go 93b3a0d7ec feat(context): add SaveUploadedFile func. (#1022) 8 years ago
debug.go ee7b912a24 Updating IsDebugging docs to remove redudent words. (#868) 8 years ago
debug_test.go 735748b359 fix(tests): TestDebugPrintLoadTemplate use assert.Regep instead of assert.Equal (#973) 8 years ago
deprecated.go 114b71868a refactor(deprecated): remove getcookie 8 years ago
doc.go ecf1b37850 add doc.go (#970) 8 years ago
errors.go e23842ecab fix json sort the map keys 8 years ago
errors_test.go e23842ecab fix json sort the map keys 8 years ago
fs.go b985857899 update func comment (#981) 8 years ago
gin.go 199bbf2ae5 refactor(gin): use return not use else for reducing indent (#1031) 8 years ago
gin_integration_test.go 1e1e4fc867 Add Makefile to check the following thing (#947) 8 years ago
gin_test.go 6d071c1d36 Add load html file and func map. 8 years ago
githubapi_test.go 3c3526f1f1 fix test and build 9 years ago
logger.go e31cbdf241 feat(logger): show query string in logger. (#999) close #988 8 years ago
logger_test.go e31cbdf241 feat(logger): show query string in logger. (#999) close #988 8 years ago
middleware_test.go 9a79e3f144 fix(import): switch sse import from gopkg to github 8 years ago
mode.go fb7448f081 feat(binding): add UseNumber() in gin.Context.BindJSON() (#997) close #368 8 years ago
mode_test.go fb7448f081 feat(binding): add UseNumber() in gin.Context.BindJSON() (#997) close #368 8 years ago
path.go df67c64479 fix cleanPath spell (#969) 8 years ago
path_test.go 80942e67a8 docs(license): add julienschmidt/httprouter license URL 8 years ago
recovery.go 61fae4997d Improves documentation 10 years ago
recovery_test.go 4c4444b160 Write header immediately in AbortWithStatus() 9 years ago
response_writer.go b985857899 update func comment (#981) 8 years ago
response_writer_test.go 0cb52ccef7 Improves unit test coverage 10 years ago
routergroup.go d535fcd598 separate type define (#975) 8 years ago
routergroup_test.go fc5e355724 BasePath is not longer an exported field, but a method instead 10 years ago
routes_test.go 436d8e2af9 Improve serveError code coverage (#980) 8 years ago
test_helpers.go 92ddc7d240 docs(test): add missing inline docs 8 years ago
tree.go b985857899 update func comment (#981) 8 years ago
tree_test.go 80942e67a8 docs(license): add julienschmidt/httprouter license URL 8 years ago
utils.go 7eb943e70a Use single line (#985) 8 years ago
utils_test.go 9fd8aff56e tests: make path assertions aware of vendoring 10 years ago
wercker.yml 295201dad2 Adds wercker.yml 10 years ago

README.md

Gin Web Framework

Build Status codecov Go Report Card GoDoc

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

Gin console logger

# 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
}
# run example.go and visit 0.0.0.0: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)
BenchmarkAce_GithubAll 10000 109482 13792 167
BenchmarkBear_GithubAll 10000 287490 79952 943
BenchmarkBeego_GithubAll 3000 562184 146272 2092
BenchmarkBone_GithubAll 500 2578716 648016 8119
BenchmarkDenco_GithubAll 20000 94955 20224 167
BenchmarkEcho_GithubAll 30000 58705 0 0
BenchmarkGin_GithubAll 30000 50991 0 0
BenchmarkGocraftWeb_GithubAll 5000 449648 133280 1889
BenchmarkGoji_GithubAll 2000 689748 56113 334
BenchmarkGoJsonRest_GithubAll 5000 537769 135995 2940
BenchmarkGoRestful_GithubAll 100 18410628 797236 7725
BenchmarkGorillaMux_GithubAll 200 8036360 153137 1791
BenchmarkHttpRouter_GithubAll 20000 63506 13792 167
BenchmarkHttpTreeMux_GithubAll 10000 165927 56112 334
BenchmarkKocha_GithubAll 10000 171362 23304 843
BenchmarkMacaron_GithubAll 2000 817008 224960 2315
BenchmarkMartini_GithubAll 100 12609209 237952 2686
BenchmarkPat_GithubAll 300 4830398 1504101 32222
BenchmarkPossum_GithubAll 10000 301716 97440 812
BenchmarkR2router_GithubAll 10000 270691 77328 1182
BenchmarkRevel_GithubAll 1000 1491919 345553 5918
BenchmarkRivet_GithubAll 10000 283860 84272 1079
BenchmarkTango_GithubAll 5000 473821 87078 2470
BenchmarkTigerTonic_GithubAll 2000 1120131 241088 6052
BenchmarkTraffic_GithubAll 200 8708979 2664762 22390
BenchmarkVulcan_GithubAll 5000 353392 19894 609
BenchmarkZeus_GithubAll 2000 944234 300688 2648

(1): Total Repetitions
(2): Single Repetition Duration (ns/op)
(3): Heap Memory (B/op)
(4): Average Allocations per Repetition (allocs/op)

Gin v1. stable

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

Start using it

  1. Download and install it:
$ go get github.com/gin-gonic/gin
  1. Import it in your code:
import "github.com/gin-gonic/gin"
  1. (Optional) Import net/http. This is required for example if using constants such as http.StatusOK.
import "net/http"

Use a vendor tool like Govendor

  1. go get govendor
$ go get github.com/kardianos/govendor
  1. Create your project folder and cd inside
$ mkdir -p ~/go/src/github.com/myusername/project && cd "$_"
  1. Vendor init your project and add gin
$ govendor init
$ govendor fetch github.com/gin-gonic/gin@v1.2
  1. Copy a starting template inside your project
$ curl https://raw.githubusercontent.com/gin-gonic/gin/master/examples/basic/main.go > main.go
  1. Run your project
$ go run main.go

API Examples

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

func main() {
	// Disable Console Color
	// gin.DisableConsoleColor()

	// 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 neither /user/ or /user
	router.GET("/user/:name", func(c *gin.Context) {
		name := c.Param("name")
		c.String(http.StatusOK, "Hello %s", name)
	})

	// However, this one will match /user/john/ and also /user/john/send
	// If no other routers match /user/john, it will redirect to /user/john/
	router.GET("/user/:name/*action", func(c *gin.Context) {
		name := c.Param("name")
		action := c.Param("action")
		message := name + " is " + action
		c.String(http.StatusOK, message)
	})

	router.Run(":8080")
}

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

Upload files

Single file

References issue #774 and detail example code.

func main() {
	router := gin.Default()
	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 "file=@/Users/appleboy/test.zip" \
  -H "Content-Type: multipart/form-data"

Multiple files

See the detail example code.

func main() {
	router := gin.Default()
	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[]=@/Users/appleboy/test1.zip" \
  -F "upload[]=@/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

r := gin.Default()

Using middleware

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

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

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

	// Authorization group
	// authorized := r.Group("/", AuthRequired())
	// exactly the same as:
	authorized := r.Group("/")
	// per group middleware! in this case we use the custom created
	// AuthRequired() middleware just in the "authorized" group.
	authorized.Use(AuthRequired())
	{
		authorized.POST("/login", loginEndpoint)
		authorized.POST("/submit", submitEndpoint)
		authorized.POST("/read", readEndpoint)

		// nested group
		testing := authorized.Group("testing")
		testing.GET("/analytics", analyticsEndpoint)
	}

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

Model binding and validation

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

Gin uses go-playground/validator.v8 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".

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

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

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

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

	// Example for binding JSON ({"user": "manu", "password": "123"})
	router.POST("/loginJSON", func(c *gin.Context) {
		var json Login
		if c.BindJSON(&json) == nil {
			if json.User == "manu" && json.Password == "123" {
				c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
			} else {
				c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
			}
		}
	})

	// Example for binding a HTML form (user=manu&password=123)
	router.POST("/loginForm", func(c *gin.Context) {
		var form Login
		// This will infer what binder to use depending on the content-type header.
		if c.Bind(&form) == nil {
			if form.User == "manu" && form.Password == "123" {
				c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
			} else {
				c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
			}
		}
	})

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

Bind Query String

See the detail information.

package main

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

type Person struct {
	Name    string `form:"name"`
	Address string `form:"address"`
}

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.Bind(&person) == nil {
		log.Println(person.Name)
		log.Println(person.Address)
	}

	c.String(200, "Success")
}

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.Bind(&fakeForm)
    c.JSON(200, gin.H{"color": fakeForm.Colors})
}

...

form.html

<form action="/" method="POST">
    <p>Check some colors</p>
    <label for="red">Red</label>
    <input type="checkbox" name="colors[]" value="red" id="red" />
    <label for="green">Green</label>
    <input type="checkbox" name="colors[]" value="green" id="green" />
    <label for="blue">Blue</label>
    <input type="checkbox" name="colors[]" value="blue" id="blue" />
    <input type="submit" />
</form>

result:

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

Multipart/Urlencoded binding

package main

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

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

func main() {
	router := gin.Default()
	router.POST("/login", func(c *gin.Context) {
		// you can bind multipart form with explicit binding declaration:
		// c.MustBindWith(&form, binding.Form)
		// or you can simply use autobinding with Bind method:
		var form LoginForm
		// in this case proper binding will be automatically selected
		if c.Bind(&form) == nil {
			if form.User == "user" && form.Password == "password" {
				c.JSON(200, gin.H{"status": "you are logged in"})
			} else {
				c.JSON(401, gin.H{"status": "unauthorized"})
			}
		}
	})
	router.Run(":8080")
}

Test it with:

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

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

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

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

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.tmpl</p>
</html>
{{ end }}

templates/users/index.tmpl

{{ define "users/index.tmpl" }}
<html><h1>
	{{ .title }}
</h1>
<p>Using users/index.tmpl</p>
</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

main.go

	...
	
	func formatAsDate(t time.Time) string {
		year, month, day := t.Date()
		return fmt.Sprintf("%d/%02d/%02d", year, month, day)
	}
	
	...
	
	router.SetFuncMap(template.FuncMap{
		"formatAsDate": formatAsDate,
	})
	
	...
	
	router.GET("/raw", func(c *Context) {
		c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{
			"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
		})
	})
	
	...

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:

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

Both internal and external locations are supported.

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": "foo@bar.com", "phone": "123433"},
	"austin": gin.H{"email": "austin@example.com", "phone": "666"},
	"lena":   gin.H{"email": "lena@guapa.com", "phone": "523443"},
}

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

	// Group using gin.BasicAuth() middleware
	// gin.Accounts is a shortcut for map[string]string
	authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{
		"foo":    "bar",
		"austin": "1234",
		"lena":   "hello2",
		"manu":   "4321",
	}))

	// /admin/secrets endpoint
	// hit "localhost:8080/admin/secrets
	authorized.GET("/secrets", func(c *gin.Context) {
		// get user, it was set by the BasicAuth middleware
		user := c.MustGet(gin.AuthUserKey).(string)
		if secret, ok := secrets[user]; ok {
			c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret})
		} else {
			c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("})
		}
	})

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

Goroutines inside a middleware

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

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

	r.GET("/long_async", func(c *gin.Context) {
		// create copy to be used inside the goroutine
		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))
}

Graceful restart or stop

Do you want to graceful restart or stop your web server? There are some ways this can be done.

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

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

An alternative to endless:

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

package main

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

	go func() {
		// service connections
		if err := srv.ListenAndServe(); err != nil {
			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)
	signal.Notify(quit, os.Interrupt)
	<-quit
	log.Println("Shutdown Server ...")

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := srv.Shutdown(ctx); err != nil {
		log.Fatal("Server Shutdown:", err)
	}
	log.Println("Server exist")
}

Users Sourcegraph

Awesome project lists using Gin web framework.

  • drone: Drone is a Continuous Delivery platform built on Docker, written in Go
  • gorush: A push notification server written in Go.