Gin is the most popular Web framework in Golang, with high performance routing. The main features described on the official website include fast, middleware support, crash handling, JSON validation, and routing group support, which can be compared to node’s KOA framework.

Quick start

Installation:

go get -u github.com/gin-gonic/gin
Copy the code

Return a JSON route:

package main

import (

   "github.com/gin-gonic/gin"

   "net/http"

)



func main(a) {

   r := gin.Default()

   r.GET("/someJson".func(c *gin.Context) {

      data := map[string]interface{} {"lang": "go lang"."tag": "<br>",

      }

      c.JSON(http.StatusOK, data)

   })

   r.Run(": 8000")}Copy the code

Gin. Default is used to enable logger and recovery middleware by Default. It can be seen from the source code that it is equivalent to using Use to enable the two middleware after calling New function:

Use the New method:

package main

import (

   "github.com/gin-gonic/gin"

   "net/http"

)



func main(a) {

   r := gin.New()

   r.Use(gin.Logger(), gin.Recovery())

   r.GET("/someJson".func(c *gin.Context) {

      data := map[string]interface{} {"lang": "go lang"."tag": "<br>",

      }

      c.JSON(http.StatusOK, data)

   })

   r.Run(": 8000")}Copy the code

Routes and routing groups

Gin supports get, POST, patch, DELETE, PUT, options, HEAD, and any. Any supports get, POST, patch, DELETE, PUT, Options, and HEAD. Gin provides uppercase methods for these HTTP methods, and you can see from gin’s source code that these are RouterGroup methods:

These methods are shortcuts to Handle, which also use the group. Handle method, which checks the string of the httpMethod passed in:

We change the above r.set method to Handle as follows:

package main

import (

   "github.com/gin-gonic/gin"

   "net/http"

)



func main(a) {

   r := gin.New()

   r.Use(gin.Logger(), gin.Recovery())

 r.Handle(http.MethodGet, "/someJson".func(c *gin.Context) {

      data := map[string]interface{} {"lang": "go lang"."tag": "<br>",

      }

      c.JSON(http.StatusOK, data)

   })

   r.Run(": 8000")}Copy the code

In practice, we often have scenarios of routing API versions and business modules. In GIN, this can be done by routing groups, i.e. Group methods:

package main

import (

   "github.com/gin-gonic/gin"

   "net/http"

)





func main(a) {

   r := gin.Default()

   v1 := r.Group("/api/v1")

   v1.GET("/getUser".func(c *gin.Context) {

      c.JSON(http.StatusOK, gin.H{

         "name": "golang"."id": "1",

      })

   })

   r.Run(": 8000")}Copy the code

You can see from the source that the Group method returns a new RouterGroup with the corresponding routePath calculation as a basePath

When processing routes, the Handle method calls calculateAbsolutePath method to calculate the final route path

Routing parameters

Gin routes are based on Httprouter. As with KOA, the route parameter is :param. The context param method is used to obtain the corresponding value, for example: httprouter

package main



import (

   "github.com/gin-gonic/gin"

   "net/http"

)



func main(a) {

   r := gin.Default()

   r.GET("/user/:id".func(c *gin.Context) {

      id := c.Param("id")

      c.JSON(http.StatusOK, gin.H{

         "user": id,

      })

   })

   r.Run(": 8000")}Copy the code

ByName. C. params.byname is essentially a slice that stores parameters

Route parameters can also match all routes starting with *, for example:

package main



import (

   "fmt"

   "github.com/gin-gonic/gin"

   "net/http"

)



func main(a) {

   r := gin.Default()

   r.GET("/article/*id".func(c *gin.Context) {

      id := c.Param("id")

      c.JSON(http.StatusOK, gin.H{

         "article": id,

      })

   })

   r.Run(": 8000")}Copy the code

This code matches all of the following routes:

/aritcle/123

/article/123/info

/aritcle/123/author/info
Copy the code

Get and Post parameters

The most common ones in development are to pass data to the server via get (Query string) and POST (HTTP body) parameters. Gin gets the corresponding GET parameters from the Context’s Query:

package main



import (

   "github.com/gin-gonic/gin"

   "net/http"

)



func main(a)  {

   r := gin.Default()

   r.GET("/user".func(c *gin.Context) {

      id := c.Query("id")

      c.JSON(http.StatusOK, gin.H{

         "id": id,

      })

   })

   r.Run(": 8000")}Copy the code

The context GetQuery method is called:

Query is stored internally by a map, defined as map[string][] String, which is essentially retrieved from the context’s c.equest.url.query () method:

Gin is parsed internally by the parseQuery method, and the returned value is a map with string as the key and string array as the value

If you want to set a default value for query if it does not exist, you can use the DefaultQuery method, which also uses the GetQuery method inside the method, and the default value if it does not exist

package main



import (

   "github.com/gin-gonic/gin"

   "net/http"

)



func main(a)  {

   r := gin.Default()

   r.GET("/user".func(c *gin.Context) {

      id := c.DefaultQuery("id"."456")

      c.JSON(http.StatusOK, gin.H{

         "id": id,

      })

   })

   r.Run(": 8000")}Copy the code

When sending data to the server, we usually use the POST method, which is stored in the HTTP body in the form of form-data. In GIN, we can obtain the corresponding value through the PostForm method:

package main

import (

   "github.com/gin-gonic/gin"

   "net/http"

)



func main(a) {

   r := gin.Default()

   r.POST("/user".func(c *gin.Context) {

      id := c.PostForm("id")

      c.JSON(http.StatusOK, gin.H{

         "code": 200."id": id,

      })

   })

   r.Run(": 8000")}Copy the code

Similar to query, DefaultPostForm is used to set the corresponding default value, which is obtained internally by GetPostForm:

id := c.DefaultPostForm("id"."456")
Copy the code

Cookies and HTTP headers

Cookies are often obtained and set in the development process. You can obtain the corresponding cookie value through C. Cookie and set cookies using C. setcookie:

package main

import (

   "github.com/gin-gonic/gin"

   "net/http"

)



func main(a)  {

   r := gin.Default()

   r.GET("/user".func(c *gin.Context) {

      session, _ := c.Cookie("session")

      c.SetCookie("site_cookie"."cookie1".3600."/"."localhost".false.true)

      c.JSON(http.StatusOK, gin.H{

         "code": 200."session": session,

      })

   })

   r.Run(": 8000")}Copy the code

Cookie reads and parses the HTTP header cookie field:

Http headers can be obtained in the form of c. getheader (key) :

package main

import (

   "github.com/gin-gonic/gin"

   "net/http"

)



func main(a) {

   r := gin.Default()

   r.GET("/user".func(c *gin.Context) {

      lang := c.GetHeader("lang")

      c.JSON(http.StatusOK, gin.H{

         "code": 200."lang": lang,

      })

   })

   r.Run(": 8000")}Copy the code

C. equest.header. get (); c. equest.header. get ();

To set the HTTP response Header, use the Header method:

c.Header("user"."golang")
Copy the code

This is actually set via the Header struct of the HTTP package:

redirect

The gin framework has the Redirect method to help you Redirect the context. You can also modify the URL of the Context request and continue processing the context:

package main

import (

   "github.com/gin-gonic/gin"

   "net/http"

)



func main(a) {

   r := gin.Default()

   r.GET("/info".func(c *gin.Context) {

      c.Redirect(http.StatusMovedPermanently, "/user")

   })

   r.GET("/article".func(c *gin.Context) {

      c.Request.URL.Path = "/user"

      r.HandleContext(c)

   })

   r.GET("/user".func(c *gin.Context) {

      c.JSON(http.StatusOK, gin.H{

         "code": 200."data": "user",

      })

   })

   r.Run(": 8000")}Copy the code

Redirect is also essentially a Redirect method that calls HTTP packages:

Static resources and template engines

Setting up Static resources and template engines is a basic capability of a Web server. Gin is set up using Static, StaticFS, and StaticFile routing methods:

package main

import (

   "github.com/gin-gonic/gin"

   "net/http"

)



func main(a) {

   r := gin.Default()

   r.Static("/"."./public")

   r.StaticFile("/"."./public")

   r.StaticFS("/", http.Dir("./public"))

   r.Run(": 8000")}Copy the code

Static is also internally implemented via the StaticFS method:

Golang has a template engine standard library, HTTP/Template, which is also used internally by gin. This library is similar to the standard template engine we use, using beard expressions as variables. Gin uses the LoadHTMLGlob method to load templates. Using C.HTML to indicate processing using a template engine, the source code can be found in Gin’s Render /html.go:

package main

import (

   "github.com/gin-gonic/gin"

   "net/http"

)



func main(a) {

   r := gin.Default()



   r.LoadHTMLGlob("./template/*")



   r.GET("/index".func(c *gin.Context) {

      c.HTML(http.StatusOK, "index.tmpl", gin.H{

         "title": "golang",

      })

   })



   r.Run(": 8000")}Copy the code
<html>

<h1>

    {{ .title }}

</h1>

</html>
Copy the code

Context

Gin’s context runs through the entire process of an HTTP request. It is analogous to koA’s context. Context is also one of the most core objects in GIN.

 / Context is the most important part of gin. It allows us to pass variables between middleware,

 // manage the flow, validate the JSON of a request and render a JSON response for example.

type Context struct {

   writermem responseWriter

   Request *http.Request

   Writer ResponseWriter



   Params Params

   handlers HandlersChain

   index    int8

   fullPath string



   engine *Engine

   params *Params



   // This mutex protect Keys map

 mu sync.RWMutex



   // Keys is a key/value pair exclusively for the context of each request.

 Keys map[string]interface{}



   // Errors is a list of errors attached to all the handlers/middlewares who used this context.

 Errors errorMsgs



   // Accepted defines a list of manually accepted formats for content negotiation.

 Accepted []string



   // queryCache use url.ParseQuery cached the param query result from c.Request.URL.Query()

 queryCache url.Values



   // formCache use url.ParseQuery cached PostForm contains the parsed form data from POST, PATCH,

 // or PUT body parameters.

 formCache url.Values



   // SameSite allows a server to define a cookie attribute making it impossible for

 // the browser to send this cookie along with cross-site requests.

 sameSite http.SameSite

}
Copy the code

Query, postForm, param, header, cookie, etc., all hang on context. Context also provides basic metadata access, Get and Set. This allows us to hang the data we need (especially for use across middleware) on the context, which is essentially stored on C.keys:

package main

import (

   "github.com/gin-gonic/gin"

   "net/http"

)



func main(a)  {

   r := gin.Default()

   r.GET("/user".func(c *gin.Context) {

      c.Set("key"."value")

      val, _ := c.Get("key")

      c.JSON(http.StatusOK, gin.H{

         "code": 200."key": val,

      })

   })

   r.Run(": 8000")}Copy the code

In addition to Get storage, GIN also has built-in MustGet (panic if there is no such thing) and values for type assertions such as GetString, GetBool, GetInt, etc

Metadata can be accessed in the following ways:



 / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /

 /******** METADATA MANAGEMENT********/

 / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /



func (c *Context) Set(key string, value interface{}) {}



 // Get returns the value for the given key, ie: (value, true).

 // If the value does not exists it returns (nil, false)

func (c *Context) Get(key string) (value interface{}, exists bool) {}



 // MustGet returns the value for the given key if it exists, otherwise it panics.

func (c *Context) MustGet(key string) interface{} {}



 // GetString returns the value associated with the key as a string.

func (c *Context) GetString(key string) (s string) {}



 // GetBool returns the value associated with the key as a boolean.

func (c *Context) GetBool(key string) (b bool) {}



 // GetInt returns the value associated with the key as an integer.

func (c *Context) GetInt(key string) (i int) {}



 // GetInt64 returns the value associated with the key as an integer.

func (c *Context) GetInt64(key string) (i64 int64) {}



 // GetUint returns the value associated with the key as an unsigned integer.

func (c *Context) GetUint(key string) (ui uint){}// GetUint64 returns the value associated with the key as an unsigned integer.

func (c *Context) GetUint64(key string) (ui64 uint64) {}



 // GetFloat64 returns the value associated with the key as a float64.

func (c *Context) GetFloat64(key string) (f64 float64) {}



 // GetTime returns the value associated with the key as time.

func (c *Context) GetTime(key string) (t time.Time) {}



 // GetDuration returns the value associated with the key as a duration.

func (c *Context) GetDuration(key string) (d time.Duration) {}



 // GetStringSlice returns the value associated with the key as a slice of strings.

func (c *Context) GetStringSlice(key string) (ss []string) {}



 // GetStringMap returns the value associated with the key as a map of interfaces.

func (c *Context) GetStringMap(key string) (sm map[string]interface{}) {}



 // GetStringMapString returns the value associated with the key as a map of strings.

func (c *Context) GetStringMapString(key string) (sms map[string]string) {}



 // GetStringMapStringSlice returns the value associated with the key as a map to a slice of strings.

func (c *Context) GetStringMapStringSlice(key string) (smss map[string] []string) {}
Copy the code

Gin calls query, postForm, param and other request Data Input Data. Gin’s source code shows that this part of the method mainly includes query, postForm, Param and other information acquisition and parameter binding methods, source code is as follows:



 / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /

 /************ INPUT DATA ************/

 / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /



 // Param returns the value of the URL param.

 // It is a shortcut for c.Params.ByName(key)

 // router.GET("/user/:id", func(c *gin.Context) {

 // // a GET request to /user/john

 // id := c.Param("id") // id == "john"

 / /})

func (c *Context) Param(key string) string {

   return c.Params.ByName(key)

}



 // Query returns the keyed url query value if it exists,

 // otherwise it returns an empty string `("")`.

 // It is shortcut for `c.Request.URL.Query().Get(key)`

 // GET /path? id=1234&name=Manu&value=

 // c.Query("id") == "1234"

 // c.Query("name") == "Manu"

 // c.Query("value") == ""

 // c.Query("wtf") == ""

func (c *Context) Query(key string) string {

   value, _ := c.GetQuery(key)

   return value

}



 // DefaultQuery returns the keyed url query value if it exists,

 // otherwise it returns the specified defaultValue string.

 // See: Query() and GetQuery() for further information.

 // GET /? name=Manu&lastname=

 // c.DefaultQuery("name", "unknown") == "Manu"

 // c.DefaultQuery("id", "none") == "none"

 // c.DefaultQuery("lastname", "none") == ""

func (c *Context) DefaultQuery(key, defaultValue string) string {

   if value, ok := c.GetQuery(key); ok {

      return value

   }

   return defaultValue

}



 // GetQuery is like Query(), it returns the keyed url query value

 // if it exists `(value, true)` (even when the value is an empty string),

 // otherwise it returns `("", false)`.

 // It is shortcut for `c.Request.URL.Query().Get(key)`

 // GET /? name=Manu&lastname=

 // ("Manu", true) == c.GetQuery("name")

 // ("", false) == c.GetQuery("id")

 // ("", true) == c.GetQuery("lastname")

func (c *Context) GetQuery(key string) (string.bool) {

   if values, ok := c.GetQueryArray(key); ok {

      return values[0], ok

   }

   return "".false

}



 // QueryArray returns a slice of strings for a given query key.

 // The length of the slice depends on the number of params with the given key.

func (c *Context) QueryArray(key string) []string {

   values, _ := c.GetQueryArray(key)

   return values

}



func (c *Context) initQueryCache(a) {

   if c.queryCache == nil {

      ifc.Request ! =nil {

         c.queryCache = c.Request.URL.Query()

      } else {

         c.queryCache = url.Values{}

      }

   }

}



 // GetQueryArray returns a slice of strings for a given query key, plus

 // a boolean value whether at least one value exists for the given key.

func (c *Context) GetQueryArray(key string) ([]string.bool) {

   c.initQueryCache()

   if values, ok := c.queryCache[key]; ok && len(values) > 0 {

      return values, true

   }

   return []string{}, false

}



 // QueryMap returns a map for a given query key.

func (c *Context) QueryMap(key string) map[string]string {

   dicts, _ := c.GetQueryMap(key)

   return dicts

}



 // GetQueryMap returns a map for a given query key, plus a boolean value

 // whether at least one value exists for the given key.

func (c *Context) GetQueryMap(key string) (map[string]string.bool) {

   c.initQueryCache()

   return c.get(c.queryCache, key)

}



 // PostForm returns the specified key from a POST urlencoded form or multipart form

 // when it exists, otherwise it returns an empty string `("")`.

func (c *Context) PostForm(key string) string {

   value, _ := c.GetPostForm(key)

   return value

}



 // DefaultPostForm returns the specified key from a POST urlencoded form or multipart form

 // when it exists, otherwise it returns the specified defaultValue string.

 // See: PostForm() and GetPostForm() for further information.

func (c *Context) DefaultPostForm(key, defaultValue string) string {

   if value, ok := c.GetPostForm(key); ok {

      return value

   }

   return defaultValue

}



 // GetPostForm is like PostForm(key). It returns the specified key from a POST urlencoded

 // form or multipart form when it exists `(value, true)` (even when the value is an empty string),

 // otherwise it returns ("", false).

 // For example, during a PATCH request to update the user's email:

 // [email protected] --> ("[email protected]", true) := GetPostForm("email") // set email to "[email protected]"

 // email= --> ("", true) := GetPostForm("email") // set email to ""

 // --> ("", false) := GetPostForm("email") // do nothing with email

func (c *Context) GetPostForm(key string) (string.bool) {

   if values, ok := c.GetPostFormArray(key); ok {

      return values[0], ok

   }

   return "".false

}



 // PostFormArray returns a slice of strings for a given form key.

 // The length of the slice depends on the number of params with the given key.

func (c *Context) PostFormArray(key string) []string {

   values, _ := c.GetPostFormArray(key)

   return values

}



func (c *Context) initFormCache(a) {

   if c.formCache == nil {

      c.formCache = make(url.Values)

      req := c.Request

 iferr := req.ParseMultipartForm(c.engine.MaxMultipartMemory); err ! =nil {

         iferr ! = http.ErrNotMultipart { debugPrint("error on parse multipart form array: %v", err)

         }

      }

      c.formCache = req.PostForm

 }

}



 // GetPostFormArray returns a slice of strings for a given form key, plus

 // a boolean value whether at least one value exists for the given key.

func (c *Context) GetPostFormArray(key string) ([]string.bool) {

   c.initFormCache()

   if values := c.formCache[key]; len(values) > 0 {

      return values, true

   }

   return []string{}, false

}



 // PostFormMap returns a map for a given form key.

func (c *Context) PostFormMap(key string) map[string]string {

   dicts, _ := c.GetPostFormMap(key)

   return dicts

}



 // GetPostFormMap returns a map for a given form key, plus a boolean value

 // whether at least one value exists for the given key.

func (c *Context) GetPostFormMap(key string) (map[string]string.bool) {

   c.initFormCache()

   return c.get(c.formCache, key)

}



 // get is an internal method and returns a map which satisfy conditions.

func (c *Context) get(m map[string] []string, key string) (map[string]string.bool) {

   dicts := make(map[string]string)

   exist := false

   for k, v := range m {

      if i := strings.IndexByte(k, '['); i >= 1 && k[0:i] == key {

         if j := strings.IndexByte(k[i+1:].'] '); j >= 1 {

            exist = true

            dicts[k[i+1:][:j]] = v[0]}}}return dicts, exist

}



 // FormFile returns the first file for the provided form key.

func (c *Context) FormFile(name string) (*multipart.FileHeader, error) {

   if c.Request.MultipartForm == nil {

      iferr := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory); err ! =nil {

         return nil, err

      }

   }

   f, fh, err := c.Request.FormFile(name)

   iferr ! =nil {

      return nil, err

   }

   f.Close()

   return fh, err

}



 // MultipartForm is the parsed multipart form, including file uploads.

func (c *Context) MultipartForm(a) (*multipart.Form, error) {

   err := c.Request.ParseMultipartForm(c.engine.MaxMultipartMemory)

   return c.Request.MultipartForm, err

}



 // SaveUploadedFile uploads the form file to specific dst.

func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error {

   src, err := file.Open()

   iferr ! =nil {

      return err

   }

   defer src.Close()



   out, err := os.Create(dst)

   iferr ! =nil {

      return err

   }

   defer out.Close()



   _, err = io.Copy(out, src)

   return err

}



 // Bind checks the Content-Type to select a binding engine automatically,

 // Depending the "Content-Type" header different bindings are used:

 // "application/json" --> JSON binding

 // "application/xml" --> XML binding

 // otherwise --> returns an error.

 // It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.

 // It decodes the json payload into the struct specified as a pointer.

 // It writes a 400 error and sets Content-Type header "text/plain" in the response if input is not valid.

func (c *Context) Bind(obj interface{}) error {

   b := binding.Default(c.Request.Method, c.ContentType())

   return c.MustBindWith(obj, b)

}



 // BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON).

func (c *Context) BindJSON(obj interface{}) error {

   return c.MustBindWith(obj, binding.JSON)

}



 // BindXML is a shortcut for c.MustBindWith(obj, binding.BindXML).

func (c *Context) BindXML(obj interface{}) error {

   return c.MustBindWith(obj, binding.XML)

}



 // BindQuery is a shortcut for c.MustBindWith(obj, binding.Query).

func (c *Context) BindQuery(obj interface{}) error {

   return c.MustBindWith(obj, binding.Query)

}



 // BindYAML is a shortcut for c.MustBindWith(obj, binding.YAML).

func (c *Context) BindYAML(obj interface{}) error {

   return c.MustBindWith(obj, binding.YAML)

}



 // BindHeader is a shortcut for c.MustBindWith(obj, binding.Header).

func (c *Context) BindHeader(obj interface{}) error {

   return c.MustBindWith(obj, binding.Header)

}



 // BindUri binds the passed struct pointer using binding.Uri.

 // It will abort the request with HTTP 400 if any error occurs.

func (c *Context) BindUri(obj interface{}) error {

   iferr := c.ShouldBindUri(obj); err ! =nil {

      c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck

 return err

   }

   return nil

}



 // MustBindWith binds the passed struct pointer using the specified binding engine.

 // It will abort the request with HTTP 400 if any error occurs.

 // See the binding package.

func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error {

   iferr := c.ShouldBindWith(obj, b); err ! =nil {

      c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck

 return err

   }

   return nil

}



 // ShouldBind checks the Content-Type to select a binding engine automatically,

 // Depending the "Content-Type" header different bindings are used:

 // "application/json" --> JSON binding

 // "application/xml" --> XML binding

 // otherwise --> returns an error

 // It parses the request's body as JSON if Content-Type == "application/json" using JSON or XML as a JSON input.

 // It decodes the json payload into the struct specified as a pointer.

 // Like c.Bind() but this method does not set the response status code to 400 and abort if the json is not valid.

func (c *Context) ShouldBind(obj interface{}) error {

   b := binding.Default(c.Request.Method, c.ContentType())

   return c.ShouldBindWith(obj, b)

}



 // ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON).

func (c *Context) ShouldBindJSON(obj interface{}) error {

   return c.ShouldBindWith(obj, binding.JSON)

}



 // ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML).

func (c *Context) ShouldBindXML(obj interface{}) error {

   return c.ShouldBindWith(obj, binding.XML)

}



 // ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query).

func (c *Context) ShouldBindQuery(obj interface{}) error {

   return c.ShouldBindWith(obj, binding.Query)

}



 // ShouldBindYAML is a shortcut for c.ShouldBindWith(obj, binding.YAML).

func (c *Context) ShouldBindYAML(obj interface{}) error {

   return c.ShouldBindWith(obj, binding.YAML)

}



 // ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header).

func (c *Context) ShouldBindHeader(obj interface{}) error {

   return c.ShouldBindWith(obj, binding.Header)

}



 // ShouldBindUri binds the passed struct pointer using the specified binding engine.

func (c *Context) ShouldBindUri(obj interface{}) error {

   m := make(map[string] []string)

   for _, v := range c.Params {

      m[v.Key] = []string{v.Value}

   }

   return binding.Uri.BindUri(m, obj)

}



 // ShouldBindWith binds the passed struct pointer using the specified binding engine.

 // See the binding package.

func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {

   return b.Bind(c.Request, obj)

}



 // ShouldBindBodyWith is similar with ShouldBindWith, but it stores the request

 // body into the context, and reuse when it is called again.

 //

 // NOTE: This method reads the body before binding. So you should use

 // ShouldBindWith for better performance if you need to call only once.

func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (err error) {

   var body []byte

   if cb, ok := c.Get(BodyBytesKey); ok {

      if cbb, ok := cb.([]byte); ok {

         body = cbb

      }

   }

   if body == nil {

      body, err = ioutil.ReadAll(c.Request.Body)

      iferr ! =nil {

         return err

      }

      c.Set(BodyBytesKey, body)

   }

   return bb.BindBody(body, obj)

}



 // ClientIP implements a best effort algorithm to return the real client IP.

 // It called c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not.

 // If it's it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]).

 // If the headers are nots syntactically valid OR the remote IP does not correspong to a trusted proxy,

 // the remote IP (coming form Request.RemoteAddr) is returned.

func (c *Context) ClientIP(a) string {

   if c.engine.AppEngine {

      if addr := c.requestHeader("X-Appengine-Remote-Addr"); addr ! ="" {

         return addr

      }

   }



   remoteIP, trusted := c.RemoteIP()

   if remoteIP == nil {

      return ""

   }



   iftrusted && c.engine.ForwardedByClientIP && c.engine.RemoteIPHeaders ! =nil {

      for _, headerName := range c.engine.RemoteIPHeaders {

         ip, valid := validateHeader(c.requestHeader(headerName))

         if valid {

            return ip

         }

      }

   }

   return remoteIP.String()

}



 // RemoteIP parses the IP from Request.RemoteAddr, normalizes and returns the IP (without the port).

 // It also checks if the remoteIP is a trusted proxy or not.

 // In order to perform this validation, it will see if the IP is contained within at least one of the CIDR blocks

 // defined in Engine.TrustedProxies

func (c *Context) RemoteIP(a) (net.IP, bool) {

   ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr))

   iferr ! =nil {

      return nil.false

   }

   remoteIP := net.ParseIP(ip)

   if remoteIP == nil {

      return nil.false

   }



   ifc.engine.trustedCIDRs ! =nil {

      for _, cidr := range c.engine.trustedCIDRs {

         if cidr.Contains(remoteIP) {

            return remoteIP, true}}}return remoteIP, false

}



func validateHeader(header string) (clientIP string, valid bool) {

   if header == "" {

      return "".false

   }

   items := strings.Split(header, ",")

   for i, ipStr := range items {

      ipStr = strings.TrimSpace(ipStr)

      ip := net.ParseIP(ipStr)

      if ip == nil {

         return "".false

      }



      // We need to return the first IP in the list, but,

 // we should not early return since we need to validate that

 // the rest of the header is syntactically valid

 if i == 0 {

         clientIP = ipStr

         valid = true}}return

}



 // ContentType returns the Content-Type header of the request.

func (c *Context) ContentType(a) string {

   return filterFlags(c.requestHeader("Content-Type"))}// IsWebsocket returns true if the request headers indicate that a websocket

 // handshake is being initiated by the client.

func (c *Context) IsWebsocket(a) bool {

   if strings.Contains(strings.ToLower(c.requestHeader("Connection")), "upgrade") &&

      strings.EqualFold(c.requestHeader("Upgrade"), "websocket") {

      return true

   }

   return false

}



func (c *Context) requestHeader(key string) string {

   return c.Request.Header.Get(key)

}
Copy the code

Gin, such as cookie, header, body access and C. json, C. HTML, determines the output RENDERING type to be classified as RESPONSE RENDERING. The source code for each method is as follows:



 / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /

 /******** RESPONSE RENDERING ********/

 / * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /



 // bodyAllowedForStatus is a copy of http.bodyAllowedForStatus non-exported function.

func bodyAllowedForStatus(status int) bool {

   switch {

   case status >= 100 && status <= 199:

      return false

   case status == http.StatusNoContent:

      return false

   case status == http.StatusNotModified:

      return false

   }

   return true

}



 // Status sets the HTTP response code.

func (c *Context) Status(code int) {

   c.Writer.WriteHeader(code)

}



 // Header is a intelligent shortcut for c.Writer.Header().Set(key, value).

 // It writes a header in the response.

 // If value == "", this method removes the header `c.Writer.Header().Del(key)`

func (c *Context) Header(key, value string) {

   if value == "" {

      c.Writer.Header().Del(key)

      return

   }

   c.Writer.Header().Set(key, value)

}



 // GetHeader returns value from request headers.

func (c *Context) GetHeader(key string) string {

   return c.requestHeader(key)

}



 // GetRawData return stream data.

func (c *Context) GetRawData(a) ([]byte, error) {

   return ioutil.ReadAll(c.Request.Body)

}



 // SetSameSite with cookie

func (c *Context) SetSameSite(samesite http.SameSite) {

   c.sameSite = samesite

}



 // SetCookie adds a Set-Cookie header to the ResponseWriter's headers.

 // The provided cookie must have a valid Name. Invalid cookies may be

 // silently dropped.

func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) {

   if path == "" {

      path = "/"

   }

   http.SetCookie(c.Writer, &http.Cookie{

      Name:     name,

      Value:    url.QueryEscape(value),

      MaxAge:   maxAge,

      Path:     path,

      Domain:   domain,

      SameSite: c.sameSite,

      Secure:   secure,

      HttpOnly: httpOnly,

   })

}



 // Cookie returns the named cookie provided in the request or

 // ErrNoCookie if not found. And return the named cookie is unescaped.

 // If multiple cookies match the given name, only one cookie will

 // be returned.

func (c *Context) Cookie(name string) (string, error) {

   cookie, err := c.Request.Cookie(name)

   iferr ! =nil {

      return "", err

   }

   val, _ := url.QueryUnescape(cookie.Value)

   return val, nil

}



 // Render writes the response headers and calls render.Render to render data.

func (c *Context) Render(code int, r render.Render) {

   c.Status(code)



   if! bodyAllowedForStatus(code) { r.WriteContentType(c.Writer) c.Writer.WriteHeaderNow()return

   }



   iferr := r.Render(c.Writer); err ! =nil {

      panic(err)

   }

}



 // HTML renders the HTTP template specified by its file name.

 // It also updates the HTTP code and sets the Content-Type as "text/html".

 // See http://golang.org/doc/articles/wiki/

func (c *Context) HTML(code int, name string, obj interface{}) {

   instance := c.engine.HTMLRender.Instance(name, obj)

   c.Render(code, instance)

}



 // IndentedJSON serializes the given struct as pretty JSON (indented + endlines) into the response body.

 // It also sets the Content-Type as "application/json".

 // WARNING: we recommend to use this only for development purposes since printing pretty JSON is

 // more CPU and bandwidth consuming. Use Context.JSON() instead.

func (c *Context) IndentedJSON(code int, obj interface{}) {

   c.Render(code, render.IndentedJSON{Data: obj})

}



 // SecureJSON serializes the given struct as Secure JSON into the response body.

 // Default prepends "while(1)," to response body if the given struct is array values.

 // It also sets the Content-Type as "application/json".

func (c *Context) SecureJSON(code int, obj interface{}) {

   c.Render(code, render.SecureJSON{Prefix: c.engine.secureJSONPrefix, Data: obj})

}



 // JSONP serializes the given struct as JSON into the response body.

 // It adds padding to response body to request data from a server residing in a different domain than the client.

 // It also sets the Content-Type as "application/javascript".

func (c *Context) JSONP(code int, obj interface{}) {

   callback := c.DefaultQuery("callback"."")

   if callback == "" {

      c.Render(code, render.JSON{Data: obj})

      return

   }

   c.Render(code, render.JsonpJSON{Callback: callback, Data: obj})

}



 // JSON serializes the given struct as JSON into the response body.

 // It also sets the Content-Type as "application/json".

func (c *Context) JSON(code int, obj interface{}) {

   c.Render(code, render.JSON{Data: obj})

}



 // AsciiJSON serializes the given struct as JSON into the response body with unicode to ASCII string.

 // It also sets the Content-Type as "application/json".

func (c *Context) AsciiJSON(code int, obj interface{}) {

   c.Render(code, render.AsciiJSON{Data: obj})

}



 // PureJSON serializes the given struct as JSON into the response body.

 // PureJSON, unlike JSON, does not replace special html characters with their unicode entities.

func (c *Context) PureJSON(code int, obj interface{}) {

   c.Render(code, render.PureJSON{Data: obj})

}



 // XML serializes the given struct as XML into the response body.

 // It also sets the Content-Type as "application/xml".

func (c *Context) XML(code int, obj interface{}) {

   c.Render(code, render.XML{Data: obj})

}



 // YAML serializes the given struct as YAML into the response body.

func (c *Context) YAML(code int, obj interface{}) {

   c.Render(code, render.YAML{Data: obj})

}



 // ProtoBuf serializes the given struct as ProtoBuf into the response body.

func (c *Context) ProtoBuf(code int, obj interface{}) {

   c.Render(code, render.ProtoBuf{Data: obj})

}



 // String writes the given string into the response body.

func (c *Context) String(code int, format string, values ...interface{}) {

   c.Render(code, render.String{Format: format, Data: values})

}



 // Redirect returns a HTTP redirect to the specific location.

func (c *Context) Redirect(code int, location string) {

   c.Render(- 1, render.Redirect{

      Code:     code,

      Location: location,

      Request:  c.Request,

   })

}



 // Data writes some data into the body stream and updates the HTTP code.

func (c *Context) Data(code int, contentType string, data []byte) {

   c.Render(code, render.Data{

      ContentType: contentType,

      Data:        data,

   })

}



 // DataFromReader writes the specified reader into the body stream and updates the HTTP code.

func (c *Context) DataFromReader(code int, contentLength int64, contentType string, reader io.Reader, extraHeaders map[string]string) {

   c.Render(code, render.Reader{

      Headers:       extraHeaders,

      ContentType:   contentType,

      ContentLength: contentLength,

      Reader:        reader,

   })

}



 // File writes the specified file into the body stream in an efficient way.

func (c *Context) File(filepath string) {

   http.ServeFile(c.Writer, c.Request, filepath)

}



 // FileFromFS writes the specified file from http.FileSystem into the body stream in an efficient way.

func (c *Context) FileFromFS(filepath string, fs http.FileSystem) {

   defer func(old string) {

      c.Request.URL.Path = old

   }(c.Request.URL.Path)



   c.Request.URL.Path = filepath



   http.FileServer(fs).ServeHTTP(c.Writer, c.Request)

}



 // FileAttachment writes the specified file into the body stream in an efficient way

 // On the client side, the file will typically be downloaded with the given filename

func (c *Context) FileAttachment(filepath, filename string) {

   c.Writer.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename="%s"", filename))

   http.ServeFile(c.Writer, c.Request, filepath)

}



 // SSEvent writes a Server-Sent Event into the body stream.

func (c *Context) SSEvent(name string, message interface{}) {

   c.Render(- 1, sse.Event{

      Event: name,

      Data:  message,

   })

}



 // Stream sends a streaming response and returns a boolean

 // indicates "Is client disconnected in middle of stream"

func (c *Context) Stream(step func(w io.Writer) bool) bool {

   w := c.Writer

 clientGone := w.CloseNotify()

   for {

      select {

      case <-clientGone:

         return true

      default:

         keepOpen := step(w)

         w.Flush()

         if! keepOpen {return false}}}}Copy the code

Http Request

Using HTTP packages in Golang makes it easy to implement a simple server as follows:

package main



import (

   "fmt"

   "log"

   "net/http"

)



func main(a) {

   http.HandleFunc("/user".func(writer http.ResponseWriter, request *http.Request) {

      //writer.Write()

 _, err :=writer.Write([]byte("hello world"))

      iferr! =nil {

         fmt.Println(err)

      }

   })

   err := http.ListenAndServe(": 8000".nil)

   iferr ! =nil {

      log.Fatal("ListenAndServe: ", err)

   }

}
Copy the code

As with GIN, both http.ResponseWriter and http.Request objects are involved in the routing handler. In gin, http.ResponseWriter is wrapped in a *ResponseWriter object, while http.Request is directly attached to the context:

The middleware

Gin’s middleware, similar to KOA’s middleware, is an Onion model centered around the handler that ultimately processes requests, called the Main handler, and the other middleware handlers. Each Middleware handle can be divided into two parts, with incoming requests on the left and outgoing requests on the right, and the split point is next, which is essentially to execute the function chain. Each middleware follows the principle of “first in, last out” :

This model can be reflected by customizing two globally used middleware as follows:

package main



import (

"fmt"

"github.com/gin-gonic/gin"

"net/http"

)



func m1(a) gin.HandlerFunc{

   return func(c *gin.Context) {

      fmt.Println("m1 start")

      c.Next()

      fmt.Println("m1 end")}}func m2(a) gin.HandlerFunc {

   return func(c *gin.Context) {

      fmt.Println("m2 start")

      c.Next()

      fmt.Println("m2 end")}}func main(a) {

   r := gin.New()

   r.Use(m1())

   r.Use(m2())

   r.GET("/user".func(c *gin.Context) {

      c.JSON(http.StatusOK, gin.H{

         "code": 200."data": "user",

      })

   })



   r.Run(": 8000")}Copy the code

When the request comes in, the output will be M1 start, M2 start, M2 end, m1 start:

In the most commonly used gin.Default above, Logger and Recovery are used as global middleware by Default, and log and panic are respectively used for processing. This method of directly using engine’s Use method to mount middleware will make the middleware play a global role

Similar to KOA, we can mount middleware on routes and routing groups so that the middleware will only work on matched routes, as follows, M1 will only work on user and M2 will only work on /article:

package main



import (

   "fmt"

   "github.com/gin-gonic/gin"

   "net/http"

)



func m1(a) gin.HandlerFunc{

   return func(c *gin.Context) {

      fmt.Println("m1 start")

      c.Next()

      fmt.Println("m1 end")}}func m2(a) gin.HandlerFunc {

   return func(c *gin.Context) {

      fmt.Println("m2 start")

      c.Next()

      fmt.Println("m2 end")}}func main(a) {

   r := gin.New()

   userRouter := r.Group("/user", m1())

   userRouter.GET("/info".func(c *gin.Context) {

      c.JSON(http.StatusOK, gin.H{

         "code": 200."data": "info",

      })

   })

   r.GET("/article", m2(), func(c *gin.Context) {

      c.JSON(http.StatusOK, gin.H{

         "code": 200."data": "article",

      })

   })



   r.Run(": 8000")}Copy the code

In practical development, we often need to customize middleware. A middleware is essentially a handler function that takes *gin.Context as a parameter. Handler handler handler handler handler handler handler handler handler handler handler handler

package main



import (

   "fmt"

   "github.com/gin-gonic/gin"

   "net/http"

)



func middle1(a) gin.HandlerFunc{

   return func(c *gin.Context) {

      fmt.Println("middleware 1")

      c.Next()

   }

}



func middle2(c *gin.Context) {

   fmt.Println("middleware 2")

   c.Next()

}



func main(a)  {

   r := gin.Default()

   r.Use(middle1())

   r.Use(middle2)

   r.GET("/user".func(c *gin.Context) {

      c.JSON(http.StatusOK, gin.H{

         "data": "user",

      })

   })



   r.Run(": 8000")}Copy the code

In addition to Next, middleware can also use Abort, AbortWithStatus, AbortWithStatusJSON, AbortWithError methods to intercept requests, which can be used for authentication, parameter authentication and other pre-processes as follows: If the requested header has no token, the request will be intercepted and the request will be returned with no permission:

package main



import (

   "github.com/gin-gonic/gin"

   "net/http"

)



func auth(a) gin.HandlerFunc{

   return func(c *gin.Context) {

      token := c.GetHeader("token")

      if token == "" {

         c.AbortWithStatusJSON(401, gin.H{

            "message": "No access",

         })

      }

      c.Next()

   }

}



func main(a)  {

   r := gin.Default()

   r.Use(auth())

   r.GET("/user".func(c *gin.Context) {

      c.JSON(http.StatusOK, gin.H{

         "data": "user",

      })

   })

   r.Run(": 8000")}Copy the code

A list of officially maintained and externally maintained middleware can be found under gin’s official documentation: github.com/gin-gonic/c…

Scaffolding work

  • vsouza/go-gin-boilerplate
  • Github.com/Gourouting/…
  • Github.com/doublesouth…

Open source repository for reference

  • Github.com/izghua/go-b…
  • Github.com/thoas/picfi…

reference

www.kancloud.cn/liuqing_wil…

www.haohongfan.com/post/2019-0…

Mp.weixin.qq.com/s/oi8TudWVK…

Github.com/FlowerWrong…

The Top 85 Gin Open Source Projects