kandi background
Explore Kits

gin | HTTP web framework written in Go | Web Framework library

 by   gin-gonic Go Version: v1.7.7 License: MIT

 by   gin-gonic Go Version: v1.7.7 License: MIT

Download this library from

kandi X-RAY | gin Summary

gin is a Go library typically used in Server, Web Framework, Framework applications. gin has no bugs, it has no vulnerabilities, it has a Permissive License and it has medium support. You can download it from GitHub.
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.
Support
Support
Quality
Quality
Security
Security
License
License
Reuse
Reuse

kandi-support Support

  • gin has a medium active ecosystem.
  • It has 56154 star(s) with 6354 fork(s). There are 1362 watchers for this library.
  • It had no major release in the last 12 months.
  • There are 389 open issues and 1386 have been closed. On average issues are closed in 73 days. There are 104 open pull requests and 0 closed requests.
  • It has a neutral sentiment in the developer community.
  • The latest version of gin is v1.7.7
gin Support
Best in #Web Framework
Average in #Web Framework
gin Support
Best in #Web Framework
Average in #Web Framework

quality kandi Quality

  • gin has 0 bugs and 0 code smells.
gin Quality
Best in #Web Framework
Average in #Web Framework
gin Quality
Best in #Web Framework
Average in #Web Framework

securitySecurity

  • gin has no vulnerabilities reported, and its dependent libraries have no vulnerabilities reported.
  • gin code analysis shows 0 unresolved vulnerabilities.
  • There are 0 security hotspots that need review.
gin Security
Best in #Web Framework
Average in #Web Framework
gin Security
Best in #Web Framework
Average in #Web Framework

license License

  • gin is licensed under the MIT License. This license is Permissive.
  • Permissive licenses have the least restrictions, and you can use them in most projects.
gin License
Best in #Web Framework
Average in #Web Framework
gin License
Best in #Web Framework
Average in #Web Framework

buildReuse

  • gin releases are available to install and integrate.
  • Installation instructions, examples and code snippets are available.
  • It has 12776 lines of code, 933 functions and 85 files.
  • It has medium code complexity. Code complexity directly impacts maintainability of the code.
gin Reuse
Best in #Web Framework
Average in #Web Framework
gin Reuse
Best in #Web Framework
Average in #Web Framework
Top functions reviewed by kandi - BETA

kandi's functional review helps you automatically verify the functionalities of the libraries and avoid rework.
Currently covering the most popular Java, JavaScript and Python libraries. See a Sample Here

Get all kandi verified functions for this library.

Get all kandi verified functions for this library.

gin Key Features

Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin.

Installation

copy iconCopydownload iconDownload
$ go get -u github.com/gin-gonic/gin

Quick start

copy iconCopydownload iconDownload
# assume the following codes in example.go file
$ cat example.go

Build with json replacement

copy iconCopydownload iconDownload
$ go build -tags=jsoniter .

Build without

copy iconCopydownload iconDownload
$ go build -tags=nomsgpack .

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

copy iconCopydownload iconDownload
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

copy iconCopydownload iconDownload
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) {
		b := c.FullPath() == "/user/:name/*action" // true
		c.String(http.StatusOK, "%t", b)
	})

	// This handler will add a new router for /user/groups.
	// Exact routes are resolved before param routes, regardless of the order they were defined.
	// Routes starting with /user/groups are never interpreted as /user/:name/... routes
	router.GET("/user/groups", func(c *gin.Context) {
		c.String(http.StatusOK, "The available groups are [...]")
	})

	router.Run(":8080")
}

Querystring parameters

copy iconCopydownload iconDownload
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

copy iconCopydownload iconDownload
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

copy iconCopydownload iconDownload
POST /post?id=1234&page=1 HTTP/1.1
Content-Type: application/x-www-form-urlencoded

name=manu&message=this_is_great

Map as querystring or postform parameters

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

names[first]=thinkerou&names[second]=tianou

Upload files

copy iconCopydownload iconDownload
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("Filename")
		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")
}

Grouping routes

copy iconCopydownload iconDownload
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

copy iconCopydownload iconDownload
r := gin.New()

Using middleware

copy iconCopydownload iconDownload
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

copy iconCopydownload iconDownload
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

copy iconCopydownload iconDownload
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

copy iconCopydownload iconDownload
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")
}

Controlling Log output coloring

copy iconCopydownload iconDownload
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")
}

Model binding and validation

copy iconCopydownload iconDownload
// 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 (
	//	<?xml version="1.0" encoding="UTF-8"?>
	//	<root>
	//		<user>manu</user>
	//		<password>123</password>
	//	</root>)
	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")
}

Custom Validators

copy iconCopydownload iconDownload
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()})
	}
}

Only Bind Query String

copy iconCopydownload iconDownload
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

copy iconCopydownload iconDownload
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#L88
        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")
}

Bind Uri

copy iconCopydownload iconDownload
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.Error()})
			return
		}
		c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID})
	})
	route.Run(":8088")
}

Bind Header

copy iconCopydownload iconDownload
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

copy iconCopydownload iconDownload
...

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

...

Multipart/Urlencoded binding

copy iconCopydownload iconDownload
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")
}

XML, JSON, YAML and ProtoBuf rendering

copy iconCopydownload iconDownload
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")
}

Serving static files

copy iconCopydownload iconDownload
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

copy iconCopydownload iconDownload
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

copy iconCopydownload iconDownload
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

copy iconCopydownload iconDownload
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")
}

Redirects

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

Custom Middleware

copy iconCopydownload iconDownload
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

copy iconCopydownload iconDownload
// 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

copy iconCopydownload iconDownload
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

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

Support Let's Encrypt

copy iconCopydownload iconDownload
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"))
}

Run multiple service using Gin

copy iconCopydownload iconDownload
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

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

Build a single binary with templates

copy iconCopydownload iconDownload
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
}

Bind form-data request with custom struct

copy iconCopydownload iconDownload
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()
}

Try to bind body into different structs

copy iconCopydownload iconDownload
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 {
    ...
  }
}

Bind form-data request with custom struct and custom tag

copy iconCopydownload iconDownload
const (
	customerTag = "url"
	defaultMemory = 32 << 20
)

type customerBinding struct {}

func (customerBinding) Name() string {
	return "form"
}

func (customerBinding) Bind(req *http.Request, obj interface{}) error {
	if err := req.ParseForm(); err != nil {
		return err
	}
	if err := req.ParseMultipartForm(defaultMemory); err != nil {
		if err != http.ErrNotMultipart {
			return err
		}
	}
	if err := binding.MapFormWithTag(obj, req.Form, customerTag); err != nil {
		return err
	}
	return validate(obj)
}

func validate(obj interface{}) error {
	if binding.Validator == nil {
		return nil
	}
	return binding.Validator.ValidateStruct(obj)
}

// Now we can do this!!!
// FormA is a external type that we can't modify it's tag
type FormA struct {
	FieldA string `url:"field_a"`
}

func ListHandler(s *Service) func(ctx *gin.Context) {
	return func(ctx *gin.Context) {
		var urlBinding = customerBinding{}
		var opt FormA
		err := ctx.MustBindWith(&opt, urlBinding)
		if err != nil {
			...
		}
		...
	}
}

http2 server push

copy iconCopydownload iconDownload
package main

import (
	"html/template"
	"log"

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

var html = template.Must(template.New("https").Parse(`
<html>
<head>
  <title>Https Test</title>
  <script src="/assets/app.js"></script>
</head>
<body>
  <h1 style="color:red;">Welcome, Ginner!</h1>
</body>
</html>
`))

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

copy iconCopydownload iconDownload
[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)

Set and get a cookie

copy iconCopydownload iconDownload
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()
}

Don't trust all proxies

copy iconCopydownload iconDownload
import (
	"fmt"

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

func main() {

	router := gin.Default()
	router.SetTrustedProxies([]string{"192.168.1.2"})

	router.GET("/", func(c *gin.Context) {
		// If the client is 192.168.1.2, use the X-Forwarded-For
		// header to deduce the original client IP from the trust-
		// worthy parts of that header.
		// Otherwise, simply return the direct client IP
		fmt.Printf("ClientIP: %s\n", c.ClientIP())
	})
	router.Run()
}

Testing

copy iconCopydownload iconDownload
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")
}

Go: How to send a File from AWS S3 via GIN-Router as binary stream to the browser?

copy iconCopydownload iconDownload
ctx.DataFromReader(200, response.ContentLength, *response.ContentType, response.Body, nil)

Query on json / jsonb column super slow. Can I use an index?

copy iconCopydownload iconDownload
ALTER TABLE data
  ALTER COLUMN value SET COMPRESSION lz4;
SELECT pg_column_compression(value) FROM data LIMIT 10;
ALTER TABLE data
  ADD COLUMN name text GENERATED ALWAYS AS (value::json ->> 'name') STORED
, ADD COLUMN mnemonic text GENERATED ALWAYS AS (value::json ->> 'mnemonic') STORED
...
SELECT name, mnemonic, ... FROM data;
ALTER TABLE data
  ALTER COLUMN value SET COMPRESSION lz4;
SELECT pg_column_compression(value) FROM data LIMIT 10;
ALTER TABLE data
  ADD COLUMN name text GENERATED ALWAYS AS (value::json ->> 'name') STORED
, ADD COLUMN mnemonic text GENERATED ALWAYS AS (value::json ->> 'mnemonic') STORED
...
SELECT name, mnemonic, ... FROM data;
ALTER TABLE data
  ALTER COLUMN value SET COMPRESSION lz4;
SELECT pg_column_compression(value) FROM data LIMIT 10;
ALTER TABLE data
  ADD COLUMN name text GENERATED ALWAYS AS (value::json ->> 'name') STORED
, ADD COLUMN mnemonic text GENERATED ALWAYS AS (value::json ->> 'mnemonic') STORED
...
SELECT name, mnemonic, ... FROM data;
ALTER TABLE data
  ALTER COLUMN value SET COMPRESSION lz4;
SELECT pg_column_compression(value) FROM data LIMIT 10;
ALTER TABLE data
  ADD COLUMN name text GENERATED ALWAYS AS (value::json ->> 'name') STORED
, ADD COLUMN mnemonic text GENERATED ALWAYS AS (value::json ->> 'mnemonic') STORED
...
SELECT name, mnemonic, ... FROM data;

More elegant way of validate body in go-gin

copy iconCopydownload iconDownload
type User struct {
    UserId            string                     `form:"user_id"`
    Name              string                     `form:"name"`
}

func (user *User) Validate() errors.RestError {
    if _, err := uuid.Parse(id); err != nil {
        return errors.BadRequestError("user_id not a valid uuid")
    }
    return nil
}
func GetUser(c *gin.Context) {

    // Bind query model
    var q User
    if err := c.ShouldBindQuery(&q); err != nil {
        restError := errors.BadRequestError(err.Error())
        c.JSON(restError.Status, restError)
        return
    }

    // Validate request
    if err := q.Validate(); err != nil {
        c.JSON(err.Status, err)
        return
    }

    // Business logic goes here
}

type UserId struct {
    Id string
}

func (userid *UserId) Validate() errors.RestError {
    if _, err := uuid.Parse(id); err != nil {
        return errors.BadRequestError("user_id not a valid uuid")
    }
    return nil
}

type User struct {
    UserId
    Name string 
}

func (user *User) Validate() errors.RestError {
    if err := user.UserId.Validate(); err != nil {
        return err
    }

    // Do some other validation
    
    return nil
}
type RestError struct {
    Message string `json:"message"`
    Status  int    `json:"status"`
    Error   string `json:"error"`
}

func BadRequestError(message string) *RestError {
    return &RestError{
        Message: message,
        Status:  http.StatusBadRequest,
        Error:   "Invalid Request",
    }
}
type User struct {
    UserId            string                     `form:"user_id"`
    Name              string                     `form:"name"`
}

func (user *User) Validate() errors.RestError {
    if _, err := uuid.Parse(id); err != nil {
        return errors.BadRequestError("user_id not a valid uuid")
    }
    return nil
}
func GetUser(c *gin.Context) {

    // Bind query model
    var q User
    if err := c.ShouldBindQuery(&q); err != nil {
        restError := errors.BadRequestError(err.Error())
        c.JSON(restError.Status, restError)
        return
    }

    // Validate request
    if err := q.Validate(); err != nil {
        c.JSON(err.Status, err)
        return
    }

    // Business logic goes here
}

type UserId struct {
    Id string
}

func (userid *UserId) Validate() errors.RestError {
    if _, err := uuid.Parse(id); err != nil {
        return errors.BadRequestError("user_id not a valid uuid")
    }
    return nil
}

type User struct {
    UserId
    Name string 
}

func (user *User) Validate() errors.RestError {
    if err := user.UserId.Validate(); err != nil {
        return err
    }

    // Do some other validation
    
    return nil
}
type RestError struct {
    Message string `json:"message"`
    Status  int    `json:"status"`
    Error   string `json:"error"`
}

func BadRequestError(message string) *RestError {
    return &RestError{
        Message: message,
        Status:  http.StatusBadRequest,
        Error:   "Invalid Request",
    }
}
type User struct {
    UserId            string                     `form:"user_id"`
    Name              string                     `form:"name"`
}

func (user *User) Validate() errors.RestError {
    if _, err := uuid.Parse(id); err != nil {
        return errors.BadRequestError("user_id not a valid uuid")
    }
    return nil
}
func GetUser(c *gin.Context) {

    // Bind query model
    var q User
    if err := c.ShouldBindQuery(&q); err != nil {
        restError := errors.BadRequestError(err.Error())
        c.JSON(restError.Status, restError)
        return
    }

    // Validate request
    if err := q.Validate(); err != nil {
        c.JSON(err.Status, err)
        return
    }

    // Business logic goes here
}

type UserId struct {
    Id string
}

func (userid *UserId) Validate() errors.RestError {
    if _, err := uuid.Parse(id); err != nil {
        return errors.BadRequestError("user_id not a valid uuid")
    }
    return nil
}

type User struct {
    UserId
    Name string 
}

func (user *User) Validate() errors.RestError {
    if err := user.UserId.Validate(); err != nil {
        return err
    }

    // Do some other validation
    
    return nil
}
type RestError struct {
    Message string `json:"message"`
    Status  int    `json:"status"`
    Error   string `json:"error"`
}

func BadRequestError(message string) *RestError {
    return &RestError{
        Message: message,
        Status:  http.StatusBadRequest,
        Error:   "Invalid Request",
    }
}
type User struct {
    UserId            string                     `form:"user_id"`
    Name              string                     `form:"name"`
}

func (user *User) Validate() errors.RestError {
    if _, err := uuid.Parse(id); err != nil {
        return errors.BadRequestError("user_id not a valid uuid")
    }
    return nil
}
func GetUser(c *gin.Context) {

    // Bind query model
    var q User
    if err := c.ShouldBindQuery(&q); err != nil {
        restError := errors.BadRequestError(err.Error())
        c.JSON(restError.Status, restError)
        return
    }

    // Validate request
    if err := q.Validate(); err != nil {
        c.JSON(err.Status, err)
        return
    }

    // Business logic goes here
}

type UserId struct {
    Id string
}

func (userid *UserId) Validate() errors.RestError {
    if _, err := uuid.Parse(id); err != nil {
        return errors.BadRequestError("user_id not a valid uuid")
    }
    return nil
}

type User struct {
    UserId
    Name string 
}

func (user *User) Validate() errors.RestError {
    if err := user.UserId.Validate(); err != nil {
        return err
    }

    // Do some other validation
    
    return nil
}
type RestError struct {
    Message string `json:"message"`
    Status  int    `json:"status"`
    Error   string `json:"error"`
}

func BadRequestError(message string) *RestError {
    return &RestError{
        Message: message,
        Status:  http.StatusBadRequest,
        Error:   "Invalid Request",
    }
}

Is it possible to update the log level of a zap logger at runtime?

copy iconCopydownload iconDownload
    // will be actually initialized and changed at run time 
    // based on your business logic
    var infoEnabled bool 

    errorUnlessEnabled := zap.LevelEnablerFunc(func(level zapcore.Level) bool {
        // true: log message at this level
        // false: skip message at this level
        return level >= zapcore.ErrorLevel || infoEnabled
    })

    core := zapcore.NewCore(
        zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
        os.Stdout,
        errorUnlessEnabled,
    )
    logger := zap.New(core)

    logger.Info("foo") // not logged
    
    infoEnabled = true

    logger.Info("foo again") // logged
atom := zap.NewAtomicLevel()

// To keep the example deterministic, disable timestamps in the output.
encoderCfg := zap.NewProductionEncoderConfig()
encoderCfg.TimeKey = ""

logger := zap.New(zapcore.NewCore(
    zapcore.NewJSONEncoder(encoderCfg),
    zapcore.Lock(os.Stdout),
    atom,
))
defer logger.Sync()

logger.Info("info logging enabled")
atom.SetLevel(zap.ErrorLevel)
logger.Info("info logging disabled")

What is the use of test mode in Gin

copy iconCopydownload iconDownload
    r := gin.New()
    if gin.Mode() == gin.TestMode {
        r.GET("/test", func(c *gin.Context) {
            c.String(418, "I don't exist in production")
        })
    }

Can't load html file with LoadHTMLGlob in production build. It's working in development

copy iconCopydownload iconDownload
[Service]
Type=simple
Restart=always
RestartSec=5s
WorkingDirectory=/path/to/your/project //add this line
ExecStart={my_project_build_file_path}

outline scatterplot/barplot with line graph with categorical data and groups in ggplot

copy iconCopydownload iconDownload
library(ggplot2)
library(dplyr)
library(forcats)

data %>%
  mutate(group = factor(group),
        country = fct_rev(fct_inorder(country)),
        rank = as.numeric(country)) %>% 
  ggplot(aes(rank, count, colour = group)) +
  geom_point(position = position_jitter(width = 0.1, height = 0.05),
             alpha = 0.5) +
  geom_smooth(formula = y ~ x, method = glm,
              method.args = list(family = poisson), alpha = 0.2,
              aes(fill = after_scale(colour))) +
  theme_bw() +
  scale_colour_manual(values = c("orange", "deepskyblue4")) +
  ggtitle("Counts per group according to per capita GDP") +
  labs(x = "Country rank by GDP per capita",
       y = "Count (jittered for clarity)")
data <- data %>%
  mutate(group = factor(group),
        country = fct_rev(fct_inorder(country)),
        rank = as.numeric(country)) 

hull <- data %>%
  group_by(group) %>% 
  slice(chull(rank, count))

ggplot(data, aes(rank, count)) +
  geom_polygon(aes(colour = group, fill = after_scale(colour)),
               alpha = 0.3, data = hull) +
  geom_point(aes(colour = group)) +
  theme_bw() +
  scale_colour_manual(values = c("orange", "deepskyblue4")) +
  ggtitle("Counts per group according to per capita GDP") +
  labs(x = "Country rank by GDP per capita",
       y = "Count (jittered for clarity)")
library(tidyr)

data %>%
  mutate(group = factor(group),
        country = fct_inorder(country),
        rank = as.numeric(country)) %>%
  pivot_wider(names_from = group, values_from = count) %>% 
  mutate(difference = `1` - `2`) %>%
  ggplot(aes(rank, difference)) +
  geom_hline(yintercept = 0, linetype = 2) +
  geom_point() +
  geom_smooth(aes(group = 1), method = lm, formula = y~x,
              colour = "red", fill = "orange", alpha = 0.2) +
  theme_bw() +
  ggtitle("Counts per group according to per capita GDP") +
  labs(x = "Country rank by GDP per capita",
       y = "Difference in counts between group 1 and group 2")
library(ggplot2)
library(dplyr)
library(forcats)

data %>%
  mutate(group = factor(group),
        country = fct_rev(fct_inorder(country)),
        rank = as.numeric(country)) %>% 
  ggplot(aes(rank, count, colour = group)) +
  geom_point(position = position_jitter(width = 0.1, height = 0.05),
             alpha = 0.5) +
  geom_smooth(formula = y ~ x, method = glm,
              method.args = list(family = poisson), alpha = 0.2,
              aes(fill = after_scale(colour))) +
  theme_bw() +
  scale_colour_manual(values = c("orange", "deepskyblue4")) +
  ggtitle("Counts per group according to per capita GDP") +
  labs(x = "Country rank by GDP per capita",
       y = "Count (jittered for clarity)")
data <- data %>%
  mutate(group = factor(group),
        country = fct_rev(fct_inorder(country)),
        rank = as.numeric(country)) 

hull <- data %>%
  group_by(group) %>% 
  slice(chull(rank, count))

ggplot(data, aes(rank, count)) +
  geom_polygon(aes(colour = group, fill = after_scale(colour)),
               alpha = 0.3, data = hull) +
  geom_point(aes(colour = group)) +
  theme_bw() +
  scale_colour_manual(values = c("orange", "deepskyblue4")) +
  ggtitle("Counts per group according to per capita GDP") +
  labs(x = "Country rank by GDP per capita",
       y = "Count (jittered for clarity)")
library(tidyr)

data %>%
  mutate(group = factor(group),
        country = fct_inorder(country),
        rank = as.numeric(country)) %>%
  pivot_wider(names_from = group, values_from = count) %>% 
  mutate(difference = `1` - `2`) %>%
  ggplot(aes(rank, difference)) +
  geom_hline(yintercept = 0, linetype = 2) +
  geom_point() +
  geom_smooth(aes(group = 1), method = lm, formula = y~x,
              colour = "red", fill = "orange", alpha = 0.2) +
  theme_bw() +
  ggtitle("Counts per group according to per capita GDP") +
  labs(x = "Country rank by GDP per capita",
       y = "Difference in counts between group 1 and group 2")
library(ggplot2)
library(dplyr)
library(forcats)

data %>%
  mutate(group = factor(group),
        country = fct_rev(fct_inorder(country)),
        rank = as.numeric(country)) %>% 
  ggplot(aes(rank, count, colour = group)) +
  geom_point(position = position_jitter(width = 0.1, height = 0.05),
             alpha = 0.5) +
  geom_smooth(formula = y ~ x, method = glm,
              method.args = list(family = poisson), alpha = 0.2,
              aes(fill = after_scale(colour))) +
  theme_bw() +
  scale_colour_manual(values = c("orange", "deepskyblue4")) +
  ggtitle("Counts per group according to per capita GDP") +
  labs(x = "Country rank by GDP per capita",
       y = "Count (jittered for clarity)")
data <- data %>%
  mutate(group = factor(group),
        country = fct_rev(fct_inorder(country)),
        rank = as.numeric(country)) 

hull <- data %>%
  group_by(group) %>% 
  slice(chull(rank, count))

ggplot(data, aes(rank, count)) +
  geom_polygon(aes(colour = group, fill = after_scale(colour)),
               alpha = 0.3, data = hull) +
  geom_point(aes(colour = group)) +
  theme_bw() +
  scale_colour_manual(values = c("orange", "deepskyblue4")) +
  ggtitle("Counts per group according to per capita GDP") +
  labs(x = "Country rank by GDP per capita",
       y = "Count (jittered for clarity)")
library(tidyr)

data %>%
  mutate(group = factor(group),
        country = fct_inorder(country),
        rank = as.numeric(country)) %>%
  pivot_wider(names_from = group, values_from = count) %>% 
  mutate(difference = `1` - `2`) %>%
  ggplot(aes(rank, difference)) +
  geom_hline(yintercept = 0, linetype = 2) +
  geom_point() +
  geom_smooth(aes(group = 1), method = lm, formula = y~x,
              colour = "red", fill = "orange", alpha = 0.2) +
  theme_bw() +
  ggtitle("Counts per group according to per capita GDP") +
  labs(x = "Country rank by GDP per capita",
       y = "Difference in counts between group 1 and group 2")

golang: serving net.Conn using a gin router

copy iconCopydownload iconDownload
type listener struct {
    ch   chan net.Conn
    addr net.Addr
}

// newListener creates a channel listener. The addr argument
// is the listener's network address.
func newListener(addr net.Addr) *listener {
    return &listener{
        ch:   make(chan net.Conn),
        addr: addr,
    }
}

func (l *listener) Accept() (net.Conn, error) {
    c, ok := <-l.ch
    if !ok {
        return nil, errors.New("closed")
    }
    return c, nil
}

func (l *listener) Close() error { return nil }

func (l *listener) Addr() net.Addr { return l.addr }
func (l *listener) Handle(c net.Conn) error {
    l.ch <- c
    return nil
}
  s := newListener(someAddr)
  router := gin.New()
  router.GET(...)
  router.POST(...)
  err := http.Serve(s, router)
  if err != nil {
      // handle error
  }
type listener struct {
    ch   chan net.Conn
    addr net.Addr
}

// newListener creates a channel listener. The addr argument
// is the listener's network address.
func newListener(addr net.Addr) *listener {
    return &listener{
        ch:   make(chan net.Conn),
        addr: addr,
    }
}

func (l *listener) Accept() (net.Conn, error) {
    c, ok := <-l.ch
    if !ok {
        return nil, errors.New("closed")
    }
    return c, nil
}

func (l *listener) Close() error { return nil }

func (l *listener) Addr() net.Addr { return l.addr }
func (l *listener) Handle(c net.Conn) error {
    l.ch <- c
    return nil
}
  s := newListener(someAddr)
  router := gin.New()
  router.GET(...)
  router.POST(...)
  err := http.Serve(s, router)
  if err != nil {
      // handle error
  }
type listener struct {
    ch   chan net.Conn
    addr net.Addr
}

// newListener creates a channel listener. The addr argument
// is the listener's network address.
func newListener(addr net.Addr) *listener {
    return &listener{
        ch:   make(chan net.Conn),
        addr: addr,
    }
}

func (l *listener) Accept() (net.Conn, error) {
    c, ok := <-l.ch
    if !ok {
        return nil, errors.New("closed")
    }
    return c, nil
}

func (l *listener) Close() error { return nil }

func (l *listener) Addr() net.Addr { return l.addr }
func (l *listener) Handle(c net.Conn) error {
    l.ch <- c
    return nil
}
  s := newListener(someAddr)
  router := gin.New()
  router.GET(...)
  router.POST(...)
  err := http.Serve(s, router)
  if err != nil {
      // handle error
  }
type listener struct {
    ch   chan net.Conn
    addr net.Addr
}

// newListener creates a channel listener. The addr argument
// is the listener's network address.
func newListener(addr net.Addr) *listener {
    return &listener{
        ch:   make(chan net.Conn),
        addr: addr,
    }
}

func (l *listener) Accept() (net.Conn, error) {
    c, ok := <-l.ch
    if !ok {
        return nil, errors.New("closed")
    }
    return c, nil
}

func (l *listener) Close() error { return nil }

func (l *listener) Addr() net.Addr { return l.addr }
func (l *listener) Handle(c net.Conn) error {
    l.ch <- c
    return nil
}
  s := newListener(someAddr)
  router := gin.New()
  router.GET(...)
  router.POST(...)
  err := http.Serve(s, router)
  if err != nil {
      // handle error
  }
type listener struct {
    ch   chan net.Conn
    addr net.Addr
}

// newListener creates a channel listener. The addr argument
// is the listener's network address.
func newListener(addr net.Addr) *listener {
    return &listener{
        ch:   make(chan net.Conn),
        addr: addr,
    }
}

func (l *listener) Accept() (net.Conn, error) {
    c, ok := <-l.ch
    if !ok {
        return nil, errors.New("closed")
    }
    return c, nil
}

func (l *listener) Close() error { return nil }

func (l *listener) Addr() net.Addr { return l.addr }
func (l *listener) Handle(c net.Conn) error {
    l.ch <- c
    return nil
}
  s := newListener(someAddr)
  router := gin.New()
  router.GET(...)
  router.POST(...)
  err := http.Serve(s, router)
  if err != nil {
      // handle error
  }

Postgres choosing a query plan that is more expensive by its own estimates

copy iconCopydownload iconDownload
CREATE INDEX X1 ON books (meta, id) INCLUDE  (title, authors);
CREATE INDEX X2 ON photos (id, bookId);
SELECT  books.id, books.title, books.authors, books.meta
FROM    books
        LEFT OUTER JOIN photos 
           ON photos.bookId = books.id
WHERE  photos.id IS NULL
       AND books.id NOT IN (19643405,19702275,19784617,28454289,28491188,28491190,28491205,28521585,28521596,28521627,28521638,28521649,28521658,28521678,28521680,28521689,28521700,28518165,28515245,28515256,28515288,28515299,28515310,28515342,28515353,28515364,28515407,28515736,28518100,28518219,28518273,28518370,28518424,28518478,28518489)
   --  AND books.meta NOT LIKE '[REDACTED-1]%' --> useless because books.meta LIKE '[REDACTED-3]%'
   --  AND books.meta != '[REDACTED-2]'        --> useless because books.meta LIKE '[REDACTED-3]%'
       AND books.meta LIKE '[REDACTED-3]%'
LIMIT 10;
CREATE LOCAL TEMPORARY TABLE temp_books_NOT_IN
(id INT PRIMARY KEY);

INSERT INTO temp_books_NOT_IN VALUES 
(19643405),
(19702275),
(19784617),
(28454289),
(28491188),
(28491190),
(28491205),
(28521585),
(28521596),
(28521627),
(28521638),
(28521649),
(28521658),
(28521678),
(28521680),
(28521689),
(28521700),
(28518165),
(28515245),
(28515256),
(28515288),
(28515299),
(28515310),
(28515342),
(28515353),
(28515364),
(28515407),
(28515736),
(28518100),
(28518219),
(28518273),
(28518370),
(28518424),
(28518478),
(28518489);

SELECT  books.id, books.title, books.authors, books.meta
FROM    books
        LEFT OUTER JOIN photos 
           ON photos.bookId = books.id
WHERE  photos.id IS NULL
       AND books.meta LIKE '[REDACTED-3]%'
       AND books.id NOT IN (SELECT id FROM temp_books_NOT_IN)
LIMIT 10;
CREATE INDEX X1 ON books (meta, id) INCLUDE  (title, authors);
CREATE INDEX X2 ON photos (id, bookId);
SELECT  books.id, books.title, books.authors, books.meta
FROM    books
        LEFT OUTER JOIN photos 
           ON photos.bookId = books.id
WHERE  photos.id IS NULL
       AND books.id NOT IN (19643405,19702275,19784617,28454289,28491188,28491190,28491205,28521585,28521596,28521627,28521638,28521649,28521658,28521678,28521680,28521689,28521700,28518165,28515245,28515256,28515288,28515299,28515310,28515342,28515353,28515364,28515407,28515736,28518100,28518219,28518273,28518370,28518424,28518478,28518489)
   --  AND books.meta NOT LIKE '[REDACTED-1]%' --> useless because books.meta LIKE '[REDACTED-3]%'
   --  AND books.meta != '[REDACTED-2]'        --> useless because books.meta LIKE '[REDACTED-3]%'
       AND books.meta LIKE '[REDACTED-3]%'
LIMIT 10;
CREATE LOCAL TEMPORARY TABLE temp_books_NOT_IN
(id INT PRIMARY KEY);

INSERT INTO temp_books_NOT_IN VALUES 
(19643405),
(19702275),
(19784617),
(28454289),
(28491188),
(28491190),
(28491205),
(28521585),
(28521596),
(28521627),
(28521638),
(28521649),
(28521658),
(28521678),
(28521680),
(28521689),
(28521700),
(28518165),
(28515245),
(28515256),
(28515288),
(28515299),
(28515310),
(28515342),
(28515353),
(28515364),
(28515407),
(28515736),
(28518100),
(28518219),
(28518273),
(28518370),
(28518424),
(28518478),
(28518489);

SELECT  books.id, books.title, books.authors, books.meta
FROM    books
        LEFT OUTER JOIN photos 
           ON photos.bookId = books.id
WHERE  photos.id IS NULL
       AND books.meta LIKE '[REDACTED-3]%'
       AND books.id NOT IN (SELECT id FROM temp_books_NOT_IN)
LIMIT 10;
CREATE INDEX X1 ON books (meta, id) INCLUDE  (title, authors);
CREATE INDEX X2 ON photos (id, bookId);
SELECT  books.id, books.title, books.authors, books.meta
FROM    books
        LEFT OUTER JOIN photos 
           ON photos.bookId = books.id
WHERE  photos.id IS NULL
       AND books.id NOT IN (19643405,19702275,19784617,28454289,28491188,28491190,28491205,28521585,28521596,28521627,28521638,28521649,28521658,28521678,28521680,28521689,28521700,28518165,28515245,28515256,28515288,28515299,28515310,28515342,28515353,28515364,28515407,28515736,28518100,28518219,28518273,28518370,28518424,28518478,28518489)
   --  AND books.meta NOT LIKE '[REDACTED-1]%' --> useless because books.meta LIKE '[REDACTED-3]%'
   --  AND books.meta != '[REDACTED-2]'        --> useless because books.meta LIKE '[REDACTED-3]%'
       AND books.meta LIKE '[REDACTED-3]%'
LIMIT 10;
CREATE LOCAL TEMPORARY TABLE temp_books_NOT_IN
(id INT PRIMARY KEY);

INSERT INTO temp_books_NOT_IN VALUES 
(19643405),
(19702275),
(19784617),
(28454289),
(28491188),
(28491190),
(28491205),
(28521585),
(28521596),
(28521627),
(28521638),
(28521649),
(28521658),
(28521678),
(28521680),
(28521689),
(28521700),
(28518165),
(28515245),
(28515256),
(28515288),
(28515299),
(28515310),
(28515342),
(28515353),
(28515364),
(28515407),
(28515736),
(28518100),
(28518219),
(28518273),
(28518370),
(28518424),
(28518478),
(28518489);

SELECT  books.id, books.title, books.authors, books.meta
FROM    books
        LEFT OUTER JOIN photos 
           ON photos.bookId = books.id
WHERE  photos.id IS NULL
       AND books.meta LIKE '[REDACTED-3]%'
       AND books.id NOT IN (SELECT id FROM temp_books_NOT_IN)
LIMIT 10;

How to preserve path params after handling a request with router.Any and wildcard

copy iconCopydownload iconDownload
func main() {
    internalEngine := gin.New()
    internalEngine.GET("/internal/dosomething1", func(c *gin.Context) { c.JSON(200, "ok") })
    internalEngine.GET("/internal/dosomething2/:id", func(c *gin.Context) { c.JSON(200, c.Param("id")) })

    mainEngine := gin.New()
    mainEngine.Any("/*uri", func(c *gin.Context) {
        path := c.Param("uri")
        if strings.HasPrefix(path, "/internal/") {
            uri := strings.Split(path, "/")
            switch uri[2] {
            case "dosomething1", "dosomething2":
                internalEngine.HandleContext(c)
            }
        } else {
            doExternal(c)
        }
    })
    mainEngine.Run(":8800")
}
http://localhost:8855/internal/dosomething2/1234
"1234"
func main() {
    internalEngine := gin.New()
    internalEngine.GET("/internal/dosomething1", func(c *gin.Context) { c.JSON(200, "ok") })
    internalEngine.GET("/internal/dosomething2/:id", func(c *gin.Context) { c.JSON(200, c.Param("id")) })

    mainEngine := gin.New()
    mainEngine.Any("/*uri", func(c *gin.Context) {
        path := c.Param("uri")
        if strings.HasPrefix(path, "/internal/") {
            uri := strings.Split(path, "/")
            switch uri[2] {
            case "dosomething1", "dosomething2":
                internalEngine.HandleContext(c)
            }
        } else {
            doExternal(c)
        }
    })
    mainEngine.Run(":8800")
}
http://localhost:8855/internal/dosomething2/1234
"1234"
func main() {
    internalEngine := gin.New()
    internalEngine.GET("/internal/dosomething1", func(c *gin.Context) { c.JSON(200, "ok") })
    internalEngine.GET("/internal/dosomething2/:id", func(c *gin.Context) { c.JSON(200, c.Param("id")) })

    mainEngine := gin.New()
    mainEngine.Any("/*uri", func(c *gin.Context) {
        path := c.Param("uri")
        if strings.HasPrefix(path, "/internal/") {
            uri := strings.Split(path, "/")
            switch uri[2] {
            case "dosomething1", "dosomething2":
                internalEngine.HandleContext(c)
            }
        } else {
            doExternal(c)
        }
    })
    mainEngine.Run(":8800")
}
http://localhost:8855/internal/dosomething2/1234
"1234"

Community Discussions

Trending Discussions on gin
  • Go: How to send a File from AWS S3 via GIN-Router as binary stream to the browser?
  • Query on json / jsonb column super slow. Can I use an index?
  • More elegant way of validate body in go-gin
  • Is it possible to update the log level of a zap logger at runtime?
  • What is the use of test mode in Gin
  • Can't load html file with LoadHTMLGlob in production build. It's working in development
  • outline scatterplot/barplot with line graph with categorical data and groups in ggplot
  • golang: serving net.Conn using a gin router
  • Postgres choosing a query plan that is more expensive by its own estimates
  • How to preserve path params after handling a request with router.Any and wildcard
Trending Discussions on gin

QUESTION

Go: How to send a File from AWS S3 via GIN-Router as binary stream to the browser?

Asked 2022-Apr-02 at 15:42

How can I send a file, that I've got received from S3, to Gin as binary response?

Lets say, I have the following code to obtain an image from S3 bucket:

response, err := i.s3Client.GetObject(context.TODO(), &s3.GetObjectInput{
    Bucket: aws.String("test"),
    Key:    aws.String(filename),
})

How can I pipe this response into the response context of Gin-router?

I could do something like:

body, err := ioutil.ReadAll(response.Body)
if err != nil {
    ctx.JSON(http.StatusBadRequest, err)
    return
}

But how to tell gin to serve this as output? What comes in my mind but won't work is:

ctx.Header("Content-Type", *response.ContentType)
ctx.Header("Cache-control", "max-age="+strconv.Itoa(60*60*24*365))
ctx.Write(body)

Anything I can do here?

ANSWER

Answered 2022-Apr-02 at 15:42

You're almost done:

Instead of ctx.Write use this one:

ctx.DataFromReader(200, response.ContentLength, *response.ContentType, response.Body, nil)

Source https://stackoverflow.com/questions/71718946

Community Discussions, Code Snippets contain sources that include Stack Exchange Network

Vulnerabilities

No vulnerabilities reported

Install gin

To install Gin package, you need to install Go and set your Go workspace first.
The first need Go installed (version 1.13+ is required), then you can use the below Go command to install Gin.
Import it in your code:
(Optional) Import net/http. This is required for example if using constants such as http.StatusOK.
Gin uses encoding/json as default json package but you can change it by build from other tags.
Gin enables MsgPack rendering feature by default. But you can disable this feature by specifying nomsgpack build tag. This is useful to reduce the binary size of executable files. See the detail information.
You can build a server into a single binary containing templates by using go-assets. See a complete example in the https://github.com/gin-gonic/examples/tree/master/assets-in-binary directory.

Support

example for 1-line LetsEncrypt HTTPS servers. example for custom autocert manager.

DOWNLOAD this Library from

Find, review, and download reusable Libraries, Code Snippets, Cloud APIs from
over 430 million Knowledge Items
Find more libraries
Reuse Solution Kits and Libraries Curated by Popular Use Cases
Explore Kits

Save this library and start creating your kit

Explore Related Topics

Share this Page

share link
Consider Popular Web Framework Libraries
Try Top Libraries by gin-gonic
Compare Web Framework Libraries with Highest Support
Compare Web Framework Libraries with Highest Quality
Compare Web Framework Libraries with Highest Security
Compare Web Framework Libraries with Permissive License
Compare Web Framework Libraries with Highest Reuse
Find, review, and download reusable Libraries, Code Snippets, Cloud APIs from
over 430 million Knowledge Items
Find more libraries
Reuse Solution Kits and Libraries Curated by Popular Use Cases
Explore Kits

Save this library and start creating your kit

  • © 2022 Open Weaver Inc.