Gin practice

Time:2021-6-14

Gin in action

Simple use of 1 gin

package main

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

func main() {
   //The main function of the default method is to instantiate an engine with logging and fault recovery middleware.
   R: = gin. Default() // instantiates a gin object
   //Define request
   //Define the route of a get request. The first parameter is the route address, which is the relative path in the browser,
   //Parameter 2 is an anonymous function, which is used for business logic processing.
   r.GET("/login", func(c *gin.Context) {
      c. JSON (200, gin. H {// JSON) content can be constructed through the H method provided by gin, which is very convenient.
         "MSG": "login" // call the JSON method to return data. The operation of JSON is very simple. The first parameter is the status code, and the second parameter is the content of JSON.
      })
   })
   //The run method will eventually call the listen and serve method of the built-in HTTP library to listen to the port. If the parameters are not transferred, the default listening port is 80,
   //You can also change the address and port through parameters.
   r.Run(":12005")
}

2 RESTful API

Restful is a kind of design lattice and development pattern of the network response program. Each URI represents a kind of resource. The client can add, delete, put and get the resource through four kinds of request patterns: post, delete, put and get.

Similarly, besides these four verbs, the gin framework also provides us with patch, option, head and so on. You can see the detailsrentergroup.goThe number of pieces IRoutes Connect to

// IRoutes defines all router handle interface.
type IRoutes interface {
   Use(...HandlerFunc) IRoutes

   Handle(string, string, ...HandlerFunc) IRoutes
   Any(string, ...HandlerFunc) IRoutes
   GET(string, ...HandlerFunc) IRoutes
   POST(string, ...HandlerFunc) IRoutes
   DELETE(string, ...HandlerFunc) IRoutes
   PATCH(string, ...HandlerFunc) IRoutes
   PUT(string, ...HandlerFunc) IRoutes
   OPTIONS(string, ...HandlerFunc) IRoutes
   HEAD(string, ...HandlerFunc) IRoutes

   StaticFile(string, string) IRoutes
   Static(string, string) IRoutes
   StaticFS(string, http.FileSystem) IRoutes
}

For example, interface:

func main() {
   router := gin.Default()
   //The first parameter of the request verb is the request path, and the second parameter is the function used for logical processing
   router.POST("/article", func(c *gin.Context) {
      c.String(200, "article post")
   })
   router.DELETE("/article", func(c *gin.Context) {
      c.String(200, "article delete")
   })
    
    router.GET("/article/:id/:action", func(c *gin.Context) {
        id := c.Param("id")
        action := c.Param("action")
        fmt.Printf("2 /article/:id->%s, action:%s\n", id, action)
        c.String(200, id+" "+action)
    })

    router.Run(":8080")
}
  • Access URL through Web
  • Use the curl command to access the URL

    /Test method
    // curl -X PUT http://localhost:8080/article
    // curl -X POST http://localhost:8080/article
    // curl -X GET http://localhost:8080/article
    // curl -X DELETE http://localhost:8080/article

Routing parameters

: Routing

This kind of matching pattern is exact matching, only ⼀ matching patterns can be matched

visit:http://localhost:8080/users/123

Output: 123

func main() {
   r := gin.Default()
   r.GET("/users/:id", func(c *gin.Context) {
      id := c.Param("id")
      c.String(200, "The user id is  %s", id)
   })
   r.Run(":8080")
}

*Routing

There is also an unusual parameter, which is a * sign type parameter, indicating that it matches all parameters,The result is a path string starting with ⼀

visit:http://localhost:8080/users/123

Output / 123

func main() {
   r := gin.Default()
   r.GET("/users/*id", func(c *gin.Context) {
      id := c.Param("id")
      c.String(200, "The user id is  %s", id)
   })
   r.Run(":8080")
}

Special notes

visithttp://localhost: 8080 / users will be redirected tohttp://localhost: 8080 / users /, the root cause is that / users does not have a matching route, but there is a route matching / users /, so it will be redirected to / users /, as follows:

func main() {
   r := gin.Default()
   r.GET("/users/*id", func(c *gin.Context) {
      id := c.Param("id")
      c.String(200, "The user id is  %s", id)
   })
}

Disable redirection

r.RedirectTrailingSlash = false

After adding the above settings, accesshttp://localhost: 8080 / users, the access is unsuccessful, because there is no server to process the URL

3 gin get query parameters

For example:

http://127.0.0.1:8080/users?k1=v1&k2=v2

With? As a starting point, after thek=v&k1=v1&k2=v2Such strings are query parameters

In the above case, there are two parameter key value pairs, which are connected by & and

k1=v1
k2=v2

You can use the following interfaces in the gin framework to get the actual parameter values

//3-2-url-param.go URL parameter acquisition
package main

import (
   "fmt"

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

func main() {
   r := gin.Default()
   r.GET("/", func(c *gin.Context) {
      c.DefaultQuery("id", "0")
      Value, OK: = C. getquery ("Id") // it is suitable for judging whether the parameter exists

      if ok {
         fmt.Println("id:", value)
      } else {
         fmt.Println("id: nil")
      }

      c.String(200, c.DefaultQuery("wechat", "default baidu_org"))
   })
   r.Run(":8080")
}

The actual implementation of getquery is as follows:

func (c *Context) GetQuery(key string) (string, bool) {
   if values, ok := c.GetQueryArray(key); ok {
      return values[0], ok
   }
   return "", false
}

The specific implementation of defaultquery also calls getquery

func (c *Context) DefaultQuery(key, defaultValue string) string {
   if value, ok := c.GetQuery(key); ok {
      return value
   }
   return defaultValue
}

The difference between getquery and query

If you pass in the key value in getquery, you will return value, OK. If ok is true, then value has a value

Query is a direct return string

You can use getquery instead of query. The underlying implementation of getquery ⽅ method is actually c.request.url.query (). Get (key). All parameter key value pairs are obtained through URL. URL. Query()

Take a closer look at how getquery is used

//In essence, it is called getqueryarray of ⽤ to get the ⼀ th value in the array
func (c *Context) GetQuery(key string) (string, bool) {
    if values, ok := c.GetQueryArray(key); ok {
        return values[0], ok
    }
    return "", false
}

func (c *Context) GetQueryArray(key string) ([]string, bool) {
    c. Getquerycache() // get the cache, which is the key to cache all key value pairs
    if values, ok := c.queryCache[key]; ok && len(values) > 0 {
        return values, true
    }
    return []string{}, false
}

func (c *Context) getQueryCache() {
   if c.queryCache == nil {
      c.queryCache = c.Request.URL.Query()
   }
}

The method of C. request. URL. Query () is to? K = V & K1 = V1 & K2 = V2

map[string][]stringTherefore, it consumes a lot of performance. This is why gin adopts the caching method and improves the performance. This is also the reason why gin has become the fastest performing golang web framework.

4 receive array and map

QueryArray

For example, in the actual business, URL ⼤ is like this? A = B & A = C & A = D, the key values are all the same, but the corresponding values are not.

This kind of URL query parameter is an array. How can we get them in gin?

//Access in browser http://localhost : 8080 /? Media = blog & Media = wechat you will see the following information:
// ["blog","wechat"]
func main() {
   r := gin.Default()
   r.GET("/", func(c *gin.Context) {
      fmt.Println("media:", c.QueryArray("media"))
      c.JSON(200, c.QueryArray("media"))
   })
   r.Run(":8080")
}

The queryarray ⽅ method also has the corresponding getqueryarray ⽅ method. The difference is that it returns whether the corresponding key exists

QueryMap

Convert the URL query parameters in the full format to a map

For example, visit:http://localhost:8080/?ids[0]=a&ids[1]=b&ids[2]=c

Output: {“0”: “a”, “1”: “B”, “2”: “C”}

func main() {

   r := gin.Default()

   r.GET("/", func(c *gin.Context) {
      fmt.Println("map:", c.QueryMap("ids"))
      c.JSON(200, c.QueryMap("ids"))
   })
   r.Run(":8080")
}

The principle and specific source code implementation of querymap are as follows

// 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.getQueryCache()
   return c.get(c.queryCache, 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
}

5 form

To be added

6 upload

Upload a single file formfile

Test directory HTML file source code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    < title > login
</head>
<body>
    <form action="http://127.0.0.1:8080/upload" method="post" enctype="multipart/form-data">
        head portrait:
        <input type="file" name="file">
        <br>
        < input type = submit "value = submit" >
    </form>
</body>
</html>
func main() {
   //1 to create a route, two middleware logger() and recovery() are used by default
   r := gin.Default()
   //Limit upload size to form (default 32 MIB)
   r.MaxMultipartMemory = 8 << 20 // 8 MiB
   r.Static("/", "./test")
   //2 binding routing rules,
   //In this paper, we propose a new method, which encapsulates request and repose
   r.POST("/upload", func(c *gin.Context) {
      

      file, _ := c.FormFile("file")
      log.Println("file:", file.Filename)
      c. Saveuploadedfile (file, ". / +" test / "+ file. File name) // upload the file to the specified path
      c.String(200, fmt.Sprintf("%s upload file!", file.Filename))
   })
   //3 monitoring port, default to 8080
   r.Run(":8080")
}

To upload multiple files is to cycle through the list of files on the basis of uploading a single file

The HTML file in public is


<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Multiple file upload</title>
</head>
<body>
<h1>Upload multiple files with fields</h1>

<form action="/upload" method="post" enctype="multipart/form-data">
    Name: <input type="text" name="name"><br>
    Email: <input type="email" name="email"><br>
    Files: <input type="file" name="files" multiple><br><br>
    <input type="submit" value="Submit">
</form>
</body>
</html>
func main() {
   router := gin.Default()
   // Set a lower memory limit for multipart forms (default is 32 MiB)
   router.MaxMultipartMemory = 8 << 20 // 8 MiB
   router.Static("/", "./public")
   router.POST("/upload", func(c *gin.Context) {

      name := c.PostForm("name")
      email := c.PostForm("email")

      // Multipart form
      form, err := c.MultipartForm()
      if err != nil {
         c.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", err.Error()))
         return
      }
      files := form.File["files"]

      for _, file := range files {
         log.Println("file:", file.Filename)
         filename := filepath.Base(file.Filename)
         if err := c.SaveUploadedFile(file, filename); err != nil {
            c.String(http.StatusBadRequest, fmt.Sprintf("upload file err: %s", err.Error()))
            return
         }
      }

      c.String(http.StatusOK, fmt.Sprintf("Uploaded successfully %d files with fields name=%s and email=%s.", len(files), name, email))
   })
   router.Run(":8080")
}

7 packet routing

If it is based on modularization, put the same module on the platform; if it is based on version, put the same version of API on the platform, so as to make it easy to use. In some frameworks, packet routing is also called a namespace

URL grouping, which can be divided into versions and so on

func main() {
    r := gin.Default()
    //Routing group registration middleware method 1:
    Xx1group: = r.group ("/ xx1", func (c * gin. Context) {FMT. Println ("/ xx1")})
    {
        xx1Group.GET("/index", func(c *gin.Context) {
            c.JSON(http.StatusOK, gin.H{"msg": "xx1Group"})
        })
        xx1Group.GET("/index2", func(c *gin.Context) {
            c.JSON(http.StatusOK, gin.H{"msg": "2222xx1Group"})
        })
    }
    //Routing group registration middleware method 2:
    xx2Group := r.Group("/xx2")
    xx2Group.Use(authMiddleware(true))
    {
        xx2Group.GET("/index", func(c *gin.Context) {
            c.JSON(http.StatusOK, gin.H{"msg": "xx2Group"})
        })
    }
    r.Run(":8080")
}

Routing Middleware

Through the definition of group ⽅ method, we can see that it can receive two parameters:

func (group RouterGroup) Group(relativePath string, handlers …HandlerFunc) RouterGroup

The first is our registered packet routing (namespace); The ⼆ is the ⼀…HandlerFuncIt can be understood as the middleware of this packet routing, so the routing under this packet routing will call it when it is executed

As shown in the above code, accessing xx1 / index2 or xx1 / index will print out / xx1 middleware

Packet routing nesting

It is consistent with the above grouping

Principle analysis

Take get for example

Note that the ⼀ parameter relativepath, which is the ⼀ relative path, that is, the ⼀ relative path we pass to gin, so whose relative path is it?

func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
   return group.handle(http.MethodGet, relativePath, handlers)
}

Through this sentence absolutepath: = group. Calculateabsolutepath (relative path) code, we can see that it is relative to the current group (⽅ method receiver). Now let’s not look at the source code of the calculateabsolutepath ⽅ method for the moment. Let’s go back to the group ⽣ method for packet routing.

func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
   absolutePath := group.calculateAbsolutePath(relativePath)
   handlers = group.combineHandlers(handlers)
   group.engine.addRoute(httpMethod, absolutePath, handlers)
   return group.returnObj()
}
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
   return &RouterGroup{
      Handlers: group.combineHandlers(handlers),
      basePath: group.calculateAbsolutePath(relativePath),
      engine:   group.engine,
   }
}

It should be noted that the gin. Engine generated by gin. Default () actually contains ⼀ routergroups (nested combinations), so it can ⽤ routergroup ⽅ method. The group ⽅ method has become a * routergroup, the most important of which is basepath. Its value is group. Calculateabsolute path (relative path), which is similar to the method we just suspended. In this case, let’s take a look at this method.

func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
   return joinPaths(group.basePath, relativePath)
}

Gin Middleware

The gin framework allows developers to add hook functions to users in the process of processing requests. This hook function is called middleware. Middleware is suitable for dealing with some common business logic, such as login authentication, permission verification, data sorting, records, time-consuming statistics, etc

In gin, we can build a * engine with default middleware through the default functions provided by gin.

 r := gin.Default()

The default function will bind two ready middleware by default, namely logger and recovery, to help us print log output and painc processing.

func Default() *Engine {
    debugPrintWARNINGDefault()
    engine := New()
    engine.Use(Logger(), Recovery())
    return engine
}

From this, we can see that gin’s middleware is set through the use ⽅ method, which receives ⼀ variable parameters, so we can set multiple middleware at the same time.

func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
   engine.RouterGroup.Use(middleware...)
   engine.rebuild404Handlers()
   engine.rebuild405Handlers()
   return engine
}

In fact, it is a handlerfunc defined by gin, which is often used in our gin

r.GET("/", func(c *gin.Context) {
    fmt.Println("HandlerFunc") 
    c.JSON(200, "HandlerFunc")
    })

This part of func (c * gin. Context) is actually a handlerfunc

Implementation of HTTP basic authorization in middleware

HTTP basic authorization is an authentication case of HTTP. It requests the message header to contain the server’s certificate to verify the user’s agent identity through authorization. The format is as follows:

Authorization: Basic <credentials>

If the authentication is not successful, the server returns 401 unauthorized status code and WWW authenticate message header to the client

Further authentication of account name and password.

In gin, it provides us with gin. Basicauth to help us build a basic authentication middleware, which is convenient for our development.

Basic authentication middleware can be used in packet routing to authenticate under specific URL

func main() {
   r := gin.Default()
   r.Use(gin.BasicAuth(gin.Accounts{
      "admin": "123456",
   }))
   

   r.GET("/", func(c *gin.Context) {
      body, _ := ioutil.ReadAll(c.Request.Body)
      fmt.Println("---body--- \r\n " + string(body))
      fmt.Println("---header--- \r\n")
      for k, v := range c.Request.Header {
         fmt.Println(k, v)
      }
      Fmt.println
      c. JSON (200, "home page")
   })

   r.Run(":8080")
}

Gin practice

Middleware considerations

gin.Default()

Gin. Default() enables the logger and recovery middleware by default. The logger middleware writes the log to gin. Defaultwriter, even if gin is configured_ MODE=release。 The recovery middleware will recover any panic. If there is a panic, it will write a ⼊ 500 response code. If you don’t want to have two default middleware, you can make thegin.New()Create a new route without any default middleware.

Making “goroutine” in gin Middleware

When starting a new goroutine in middleware or handler, you cannot make the original top-down (c * gin. Context), but you must make its read-only copy (C. copy ())

Gin practice

Understanding of c.next() in the framework of c.gin

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

   mid1 := func(c *gin.Context) {
      fmt.Println("mid1 start")
      c.Next()
      fmt.Println("mid1 end")
   }
   mid2 := func(c *gin.Context) {
      fmt.Println("mid2 start")
      c.Next()
      fmt.Println("mid2 end")
   }
   mid3 := func(c *gin.Context) {
      fmt.Println("mid3 start")
      c.Next()
      fmt.Println("mid3 end")
   }
   router.Use(mid1, mid2)
   router.Use(mid3)
   router.GET("/index", func(c *gin.Context) {
      fmt.Println("process get request")
      c.JSON(http.StatusOK, "hello")
      fmt.Println("JSON after") //
      //C. next () // it's useless to add it here
   })

   router.Run(":8080")
}
  • Normal write next is as follows print, similar to recursive, onion model

    mid1 start
    mid2 start
    mid3 start
    process get request
    JSON after
    mid3 end
    mid2 end
    mid1 end
  • If C. next () of the three middleware is commented out, the execution is as follows, and each middleware is called in sequence

    mid1 start
    mid1 end
    mid2 start
    mid2 end
    mid3 start
    mid3 end
    process get request
    JSON after
  • Write c.next() only in M1

    mid1 start
    mid2 start
    mid2 end
    mid3 start
    mid3 end
    process get request
    JSON after
    mid1 end

Conclusion:

The last get routing processing function can be understood as the last middleware. In the case of not calling ⽤ C. abort(), all middleware will be executed.When a middleware calls C. next (), the whole process will produce nested relationship. If a middlewareC. abort (),Then the middleware will return directly after it is finished, and the later middleware will not call

8 JSON, struct, XML, yaml, protobuf rendering

Response to various data formats

func main() {
    r := gin.Default()
    //1. JSON response
    r.GET("/someJSON", func(c *gin.Context) {
        c.JSON(200, gin.H{"message": "someJSON", "status": 200})
    })
    //2. Structural response
    r.GET("/someStruct", func(c *gin.Context) {
        var msg struct {
            Name    string
            Message string
            Number  int
        }
        msg.Name = "root"
        msg.Message = "message"
        msg.Number = 123
        c.JSON(200, msg)
    })

    //3. XML
    r.GET("/someXML", func(c *gin.Context) {
        c.XML(200, gin.H{"message": "abc"})
    })

    //4. Yaml response
    r.GET("/someYAML", func(c *gin.Context) {
        c.YAML(200, gin.H{"name": "you"})
    })

    //5. Protobuf format, an efficient storage and reading tool developed by Google
    r.GET("/someProtoBuf", func(c *gin.Context) {
        reps := []int64{int64(1), int64(2)}
        //Defining data
        label := "label"
        //Transfer protobuf format data
        data := &protoexample.Test{
            Label: &label,
            Reps:  reps,
        }
        c.ProtoBuf(200, data)
    })

    r.Run(":8080")
}

9 HTML template rendering

  • Gin ⽀ loads HTML template, configures and returns response data according to template parameters, which is essentially string replacement
  • Loadhtmlglob () method can load template

Normal rendering of HTML template

Gin practice

func main() {
   r := gin.Default()
   r.LoadHTMLGlob("view/*")
   r.GET("/index", func(c *gin.Context) {
      c. HTML (http.statusok, "index. HTML", gin. H {"title": "I'm gin", "name": "you"})
   })
   r.GET("/", func(c *gin.Context) {
      c. HTML (http.statusok, "index. HTML", gin. H {"title": "I'm gin", "name": "you"})
   })
   r.Run(":8080")
}

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{.title}}</title>
</head>
<body bgcolor="#E6E600">
<h1>{{.title}}</h1>
name : {{.name}}
</body>
</html>

Separating the beginning and end of HTML file

Gin practice

func main() {
   r := gin.Default()
   r.LoadHTMLGlob("view2/**/*")
   r.GET("/index", func(c *gin.Context) {
      c. HTML (http.statusok, "user / index. HTML", gin. H {"title": "I'm gin", "name": "you2"})
   })
   r.Run()
}

index.html

{{ define "user/index.html" }}
    {{template "public/header" .}}
    name: {{.name}}
    {{template "public/footer" .}}
{{ end }}

header.html

{{define "public/header"}}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{.title}}</title>
</head>
<body>
{{end}}

footer.html

{{define "public/footer"}}
      </body>
      </html>
  {{end}}

url redirection

visithttp://127.0.0.1: 8080 / will automatically redirect tohttp://127.0.0.1:8080/index

func main() {
   r := gin.Default()
   r.LoadHTMLGlob("view/*")
   r.GET("/index", func(c *gin.Context) {
      c. HTML (http.statusok, "index. HTML", gin. H {"title": "I'm gin", "name": "you"})
   })
   r.GET("/", func(c *gin.Context) {
      c. Redirect (http.statusmovedpermanently, "/ index") // redirect
   })
   r.Run(":8080")
}

Static parts record

If you need a static part, you can define a static part record

r.Static("/assets", "./assets")

10 asynchronous coroutine

  • Goroutine mechanism can implement asynchronous processing easily
  • In addition, when starting a new goroutine, you should not make it original up and down, you must make it read-only copy.
func main() {
   r := gin.Default()
   //1. Asynchronous
   r.GET("/long_async", func(c *gin.Context) {
      //We need to make a copy
      copyContext := c.Copy()
      //Asynchronous processing
      go func() {
         time.Sleep(3 * time.Second)
         Log. Println ("asynchronous execution): + copycontext. Request. URL. Path)
         // copyContext.JSON(200, gin.H{"message": "someJSON", "status": 200})
      }()
   })

   //2. Synchronization
   r.GET("/long_sync", func(c *gin.Context) {
      time.Sleep(3 * time.Second)
      Log. Println ("synchronous execution): + c.request. URL. Path)
   })
   r.Run()
}

Author: Little Devil boy Nezha