Go Web framework | Gin introduction, one-day tutorial

Posted by coditoergosum on Mon, 03 Jan 2022 16:21:49 +0100

Gin framework

 

Basic installation

1. First, you need to install Go (version 1.10 + is required), and then you can use the following Go command to install Gin.

go get -u github.com/gin-gonic/gin

2. Import it into your code:

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

Usage example:

package main

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

func main() {
    // 1. Create a route
   r := gin.Default()
   // 2. Binding routing rules and executing functions
   // gin.Context encapsulates request and response
   r.GET("/", func(c *gin.Context) {
      c.String(http.StatusOK, "hello World!")
   })
   // 3. The listening port is 8080 by default
   // Run("no port number is specified in it, and the default is 8080") 
   r.Run(":8000")
}
Copy code

Routing usage

The essence of routing is prefix tree, which is used to realize the function of routing. It is suggested to use postman for test learning, which saves time and effort

Basic use

The setting of routing path follows Restful style (URL positioning and HTTP description operation):

// Set route
router := gin.Default()
// The first parameter is: path; The second parameter is: specific operation func(c *gin.Context)
router.GET("/Get", getting)
router.POST("/Post", posting)
router.PUT("/Put", putting)
router.DELETE("/Delete", deleting)
// Port 8080 is started by default
router.Run()
Copy code

Routing packet

// Both routing groups can be accessed. Braces are used to ensure the specification
v1 := r.Group("/v1")
{
    // Access through localhost:8080/v1/hello, and so on
    v1.GET("/hello", sayHello)
    v1.GET("/world", sayWorld)
}
v2 := r.Group("/v2")
{
    v2.GET("/hello", sayHello)
    v2.GET("/world", sayWorld)
}
r.Run(":8080")
Copy code

Mass routing implementation

When there are many routes, it is recommended to follow the following steps:

  1. Establish routes package and split different modules into multiple go files
  2. Each file provides a method that registers and implements all routes
  3. After that, the main method is registered by calling the method of the file
// Here is the method of opening a router under the routers package
func LoadRouter(e *gin.Engine) {
    e.Group("v1")
    {
        v1.GET("/post", postHandler)
  		v1.GET("/get", getHandler)
    }
  	...
}
Copy code

main file implementation:

func main() {
    r := gin.Default()
    // Call this method to register
    routers.LoadRouter(r)
    routers.LoadRouterXXX(r) // There are more representatives
    r.Run()
}
Copy code

If the scale continues to expand, there is a better way to deal with it (it is recommended not to be too large and split the service):

When the project scale is larger, we can follow the following steps:

  1. Establish routers package and divide modules (packages) internally. Each package has a router.go file, which is responsible for the routing registration of the module
├── routers
│   │
│   ├── say
│   │   ├── sayWorld.go
│   │   └── router.go
│   │
│   ├── hello
│   │   ├── helloWorld.go
│   │   └── router.go
│   │
│   └── setup_router.go
│   
└── main.go
 Copy code
  1. Setup setup_router.go file and write the following methods:
type Register func(*gin.Engine)

func Init(routers ...Register) *gin.Engine {
	// Register routing
	rs := append([]Register{}, routers...)

	r := gin.New()
	// Traversal call method
	for _, register := range rs {
		register(r)
	}
	return r
}
Copy code
  1. main.go writes the route to be registered as follows to initialize the route:
func main() {
    // Set the routing configuration that needs to be loaded
    r := routers.Init(
		say.Routers,
		hello.Routers, // There can be more than one in the back
	)
	r.Run(":8080")
}
Copy code

Get parameters

Path parameters

: only 1 can be matched, * can match any number

// This rule can match the format of / user/xxx, but cannot match the format of / user / or / user
router.GET("/user/:name", func(c *gin.Context) {
    name := c.Param("name")
    c.String(http.StatusOK, "Hello %s", name)
})

// This rule can match both / user/xxx / format and / user/xxx/other1/other2 format. Note that it can only be used at the end
// Note that if there is no match at the end (there is no /, if there is /, it is still a match), the route without this parameter will be used first, such as the one above (independent of the code order)
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)
})
Copy code

Get method

  • The URL parameters can be obtained through the DefaultQuery() or Query() methods
  • DefaultQuery() returns the default value if the parameter does not exist, and Query() returns an empty string if it does not exist
r.GET("/user", func(c *gin.Context) {
    //Specify default values
    name := c.DefaultQuery("name", "normal")
    //Get specific value
    age := c.Query("age")
    c.String(http.StatusOK, fmt.Sprintf("hello %s, your age is %s", name, age))
})
Copy code

Post method

r.POST("/form", func(c *gin.Context) {
    // Set defaults
    types := c.DefaultPostForm("type", "post")
    username := c.PostForm("username")
    password := c.PostForm("password")
    // You can also use Query to realize the combination of Get + Post
    name := c.Query("name")
    c.JSON(200, gin.H{
        "username": username,
        "password": password,
        "types":    types,
        "name": 	name,
    })
})
Copy code

File acquisition

Single file acquisition:

// Limit upload size for forms (default 32 MiB)
r.MaxMultipartMemory = 8 << 20 // 8 MiB
r.POST("/upload", func(c *gin.Context) {
    file, err := c.FormFile("file")
    if err != nil {
        c.String(500, "Error uploading file")
    }

    // Upload to specified path
    c.SaveUploadedFile(file, "C:/desktop/"+file.Filename)
    c.String(http.StatusOK, "fileName:", file.Filename)
})
Copy code

Multiple file acquisition (only the core part is shown):

// Get MultipartForm
form, err := c.MultipartForm()
if err != nil {
    c.String(http.StatusBadRequest, fmt.Sprintf("get err %s", err.Error()))
}

// Get all files
files := form.File["files"]
for _, file := range files {
    // Save one by one
    fmt.Println(file.Filename)
}
c.String(200, fmt.Sprintf("upload ok %d files", len(files)))
Copy code

Receive processing

The following examples are based on this structure:

type Login struct {
   // binding:"required" if the received value is null, an error will be reported
   User    string `form:"username" json:"user" uri:"user" xml:"user" binding:"required"`
   Pssword string `form:"password" json:"password" uri:"password" xml:"password" binding:"required"`
}
Copy code

Set check

If the required field is not received, the error log will tell:

Error:Field validation for 'User' failed on the 'required' tag

Except in the tag setting range, for example

binding:"required,gt=10"  => Represents that the value needs to be greater than 10
time_format:"2006-01-02" time_utc:"1" => Time format
 Copy code

You can also * * customize the verification method: * * gopkg in/go-playground/validator. V8, to be improved

Content type binding (recommended)

When using the Bind method, it should be noted that the tag of the structure should be set first

r.POST("/loginJSON", func(c *gin.Context) {
    // Declare received variables
    var login Login

    // Default binding form format
    if err := c.Bind(&login); err != nil {
        // Automatically infer according to the content type in the request header
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    // Output results
    c.JSON(http.StatusOK, gin.H{
        "status":   "200",
        "user":     login.User,
        "password": login.Password,
    })
})
Copy code

Specify json binding

Use the ShouldBindJSON method provided by Context. Note that the data sent can only be json

r.POST("/loginJSON", func(c *gin.Context) {
    // Declare received variables
    var json Login
    
    // Parse the data in the body of the request into the structure in json format
    if err := c.ShouldBindJSON(&json); err != nil {
        // If the sent is not in json format, the output: "error": "invalid character '-' in numeric literal"
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    
    // Output results
    c.JSON(http.StatusOK, gin.H{
        "status":   "200",
        "user":     json.User,
        "password": json.Password,
    })
})
Copy code

Response processing

Data return type

There are three common response data types: JSON, XML, and YAML

// 1.JSON
r.GET("/someJSON", func(c *gin.Context) {
    c.JSON(200, gin.H{
        "message": "Json",
        "status":  200,
    })
})
// 2.XML
r.GET("/someXML", func(c *gin.Context) {
    c.XML(200, gin.H{"message": "abc"})
})
// 3.YAML
r.GET("/someYAML", func(c *gin.Context) {
    c.YAML(200, gin.H{"name": "zhangsan"})
})
// 4.protobuf
r.GET("/someProtoBuf", func(c *gin.Context) {
    reps := []int64{1, 2}
    data := &protoexample.Test{
        Reps:  reps,
    }
    c.ProtoBuf(200, data)
})
Copy code

redirect

r.GET("/index", func(c *gin.Context) {
	c.Redirect(http.StatusMovedPermanently, "https://www.baidu.com")
})
Copy code

Asynchronous execution

r.GET("/long_async", func(c *gin.Context) {
    // We need a copy
    copyContext := c.Copy()
    // Asynchronous processing
    go func() {
        time.Sleep(3 * time.Second)
        log.Println("Asynchronous execution:" + copyContext.Request.URL.Path)
        // Note that the redirection task cannot be performed here, or panic
    }()
})
Copy code

Session control

cookie related

r.GET("/getCookie", func(c *gin.Context) {
    // Gets whether the client carries a cookie
    cookie, err := c.Cookie("key_cookie")
    if err != nil {
        cookie = "cookie"
        c.SetCookie("key_cookie", "value_cookie", // Parameters 1 and 2: Key & value
                    60,          // Parameter 3: lifetime (seconds)
                    "/",         // Parameter 4: Directory
                    "localhost", // Parameter 5: domain name
                    false,       // Parameter 6: security related - smart access via https
                    true,        // Parameter 7: security related - whether to allow others to obtain their own cookie s through js
                   )
    }
    fmt.Printf("cookie The values are: %s\n", cookie)
})
Copy code

session correlation

  1. Import package: go get -u "GitHub. COM / gin contrib / sessions"
  2. Add session middleware (the content in the following section will expand the middleware without anxiety)
  3. Use Get / Set + Save
package main

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

func main() {
	r := gin.Default()
    
    // Be careful not to disclose the key
	store := cookie.NewStore([]byte("secret"))
	//Add session middleware to the route
	r.Use(sessions.Sessions("mySession", store))

	r.GET("/setSession", func(c *gin.Context) {
		// Set session
		session := sessions.Default(c)
		session.Set("key", "value")
		session.Save()
	})

	r.GET("/getSession", func(c *gin.Context) {
		// Get session
		session := sessions.Default(c)
		v := session.Get("key")
		fmt.Println(v)
	})

	r.Run(":8080")
}

Copy code

token related

Generally, for the sake of distribution and security, we will adopt better methods, such as using token authentication to achieve cross domain access, avoid CSRF attacks, and share among multiple services.

middleware

Students who have studied Java can compare middleware to interceptor. Its function is to do some business in advance when processing specific route requests, and to perform some operations after business execution. Such as identity verification and log printing.

Middleware is divided into global middleware and routing middleware. The difference is that the former will act on all routes.

Actually, use Router: = gin Default() defines a route with Logger() and Recovery() by default.

Default Middleware

Gin itself also provides some middleware for us to use:

func BasicAuth(accounts Accounts) HandlerFunc // identity authentication 
func BasicAuthForRealm(accounts Accounts, realm string) HandlerFunc
func Bind(val interface{}) HandlerFunc //Intercept request parameters and bind them
func ErrorLogger() HandlerFunc       //Error log processing
func ErrorLoggerT(typ ErrorType) HandlerFunc //Custom type error log processing
func Logger() HandlerFunc //Logging
func LoggerWithConfig(conf LoggerConfig) HandlerFunc
func LoggerWithFormatter(f LogFormatter) HandlerFunc
func LoggerWithWriter(out io.Writer, notlogged ...string) HandlerFunc
func Recovery() HandlerFunc
func RecoveryWithWriter(out io.Writer) HandlerFunc
func WrapF(f http.HandlerFunc) HandlerFunc //Http Handlerfunc packaged as middleware
func WrapH(h http.Handler) HandlerFunc //Http The handler is wrapped into middleware
 Copy code

Custom Middleware

The way to customize the middleware is very simple. We only need to implement a function to return gin Parameters of handlerfunc type:

// HandlerFunc is essentially a function, and the input parameter is * gin Context
// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)
Copy code

Sample code, complete log printing (output customer ip + send request):

func MyLogMiddleWare() gin.HandlerFunc {
	return func(c *gin.Context) {
		fmt.Println("[MyLog] user ip:", c.ClientIP())
		fmt.Println("[MyLog] user request:", c.Request)
	}
}

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

	// Configuration Middleware
	r.Use(MyLogMiddleWare())

	// Register routing
	r.GET("/say", func(c *gin.Context) {
		c.String(200, "request: %s", c.Request)
	})

	r.Run(":8080")
}
Copy code

Middleware control method

gin provides two functions Abort() and Next(), which are different from each other:

  1. The next() function will skip the logic after next() in the current middleware, and execute the remaining logic after the execution of the next middleware
  2. The abort() function terminates the middleware execution after the current middleware, but it will execute the subsequent logic of the current middleware

Examples to better understand:

The order of middleware registration is m1, m2 and m3. If next():

The order of execution is

  1. In front of m1's next(), m2's next(), m3's next()
  2. Business logic
  3. Next() of m3, next() of m2, and next() of m1.

If Abort() is called in the middle of m2, m3 and business logic will not be executed, and only the next() of m2 and the next() of m1 will be executed.

Local Middleware

If our customized middleware only needs to be used on a route, we only need to use this method on the route. You can see the essence from the GET() method.

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

Middleware usage:

//Local middleware usage
r.GET("/test", MyLogMiddleWare(), func(c *gin.Context) {
    // Page reception
    c.JSON(200, gin.H{"success": "ok"})
})

// Add middleware based on grouping
v1 := r.Group("v1", MyLogMiddleWare())
// It can also be written like this
// v1.Use(MyLogMiddleWare())
v1.GET("/c1", func(c *gin.Context) {
    // Page reception
    c.JSON(200, gin.H{"request": "ok"})
})
v1.GET("/c2", func(c *gin.Context) {
    // Page reception
    c.JSON(200, gin.H{"request": "ok"})
})
Copy code

Handling follow-up work

We can also use middleware to handle the follow-up work, and skillfully use next () to realize the follow-up work.

func CalcTimeMiddleWare() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		c.Next()
		// Statistical time
		since := time.Since(start)
		fmt.Println("Program time:", since)
	}
}

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

	// Register routing
	r.GET("/time", CalcTimeMiddleWare(), func(c *gin.Context) {
		time.Sleep(2 * time.Second)
		c.String(200, "ok")
	})

	r.Run(":8080")
}
Copy code

Output results:

Program time: 2.0002348s
[GIN] 2021/09/26 - 15:40:48 | 200 |    2.0002348s |             ::1 | GET      "/time"
Copy code

Authentication Middleware

cookie based authentication middleware can also be implemented.

Core code:

func AuthMiddleWare() gin.HandlerFunc {
	return func(c *gin.Context) {
		// Get the client cookie and verify it
		if cookie, err := c.Cookie("key_cookie"); err == nil {
			if cookie == "value_cookie" { // If this condition is met, it is passed
				return
			}
		}
		// Return error
		c.JSON(http.StatusUnauthorized, gin.H{"error": "err"})
		// If the verification fails, subsequent function processing will not be called
		c.Abort()
	}
}
Copy code

Whether the test procedure is executed correctly:

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

	// Simulate login
	r.GET("/loginIn", func(c *gin.Context) {
		// Gets whether the client carries a cookie
		_, err := c.Cookie("key_cookie")
		if err != nil {
			c.SetCookie("key_cookie", "value_cookie", // Parameters 1 and 2: Key & value
				10,          // Parameter 3: lifetime (seconds)
				"/",         // Parameter 4: Directory
				"localhost", // Parameter 5: domain name
				false,       // Parameter 6: security related - smart access via https
				true,        // Parameter 7: security related - whether to allow others to obtain their own cookie s through js
			)
			c.String(200, "login success")
			return
		}
		c.String(200, "already login")
	})

	// Try to access and add identity authentication middleware. If you have logged in, you can execute it
	r.GET("/sayHello", AuthMiddleWare(), func(c *gin.Context) {
		c.String(200, "Hello World!")
	})

	r.Run(":8080")
}
Copy code

Test steps:

  1. First, visit localhost:8080/sayHello directly without logging in. Because no cookie is detected, {"error":"err"} will be displayed
  2. Next, visit localhost:8080/loginIn. The first visit will display: login success. Within the validity period of 10s, the second visit will display: already login
  3. Within the validity period, visit localhost:8080/sayHello, and Hello World! Will be displayed!, On behalf of successful login
  4. When the validity period expires, visit localhost:8080/sayHello again, and {"error":"err"} will be displayed, indicating that the identity has expired

Gin project structure

# Gin project structure
│
├── config 					// Configuration module
├── tools 					// Tool module
├── vendor 					// The project relies on other open source project directories
│
├── database				// Database module
│   └── mysql.go
│
├── middleware 				// Middleware module
│   └── auth.go
│
├── routers 				// Routing module, similar to controller and setup_router unified registration
│   ├── say 
│   │   ├── say_world.go
│   │   └── router.go
│   └── setup_router.go
│  
├── model	 				// Data model module, struct & database statement (p_xx can also be merged into p)
│   ├── p_model.go
│   └── p_sql.go
│
├── service 				// Service module, routing module & reusable logic encapsulation
│   ├── say_service
│   │   └── say_world.go
│ 
└── main.go 				// Master file, calling: database initialization, route registration, configuration file initialization
 Copy code

I hope I can help you, to be continued!

For those who need to get free materials, add a little assistant vx: soxwv # to get materials for free!

Topics: Java Front-end Programmer architecture