Web is Easy,Let's try Gin

I quick start

Download the package of gin: go get GitHub com/gin-gonic/gin

The hello world written by gin is as follows:

package main

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

func main() {
	router := gin.Default() 
	router.GET("/hello", func(context *gin.Context) { // Register a path handler
		context.Writer.WriteString("Hello!")
	})
	router.Run(":80") // The boot port is set to 80
}

Visit:

The native HTTP startup method of go is started through the net/http package through HTTP There is a service structure called http. Addr, which is used to start various services in the service body of HTTP Server Listenandserve also called Server Listenandserve, the router above is actually an engine, or a processor, and the Run method actually calls http Listenandserve() method, but the second parameter is the router itself. If the native method is not passed, it is a default defaultservermux, http Handlefunc actually registers something in this mux.

II Different kinds of Http request methods

gin supports a series of Restful request modes of Get Post Put Delete, which are used as follows:

package main

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

func main() {
	router := gin.Default()
	router.GET("/", func(context *gin.Context) {
		context.Writer.WriteString("GET method")
	})
	router.POST("/", func(context *gin.Context) {
		context.Writer.WriteString("POST method")
	})
	router.PUT("/", func(context *gin.Context) {
		context.Writer.WriteString("PUT method")
	})
	router.DELETE("/", func(context *gin.Context) {
		context.Writer.WriteString("DELETE method")
	})
	router.Run(":80")
}

III Get parameters at different locations

3.1 get query parameters

The query parameter is the key value pair after the question mark in the url:

package main

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

func main() {
	router := gin.Default()
	router.GET("/", func(context *gin.Context) {
		// If you don't have this parameter and want to give a default value, you can use DefaultQuery("name", "default value")
		name := context.Query("name") // Get parameters, URL: http://localhost?name=jerry
		context.Writer.WriteString("hello " + name) //  Output hello jerry
	})
	router.Run(":80")
}

3.2 get form parameters of Post

package main

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

func main() {
	router := gin.Default()
	router.POST("/", func(context *gin.Context) {
		name := context.PostForm("name")
		context.Writer.WriteString("hello " + name)
	})
	router.Run(":80")
}

3.3 obtaining path parameters

Is a part of the url path. For example, the path can be written as follows: http://localhost/jerry
jerry is the parameter of the request. The method of obtaining jerry is as follows:

package main

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

func main() {
	router := gin.Default()
	router.GET("/:name", func(context *gin.Context) { // This kind of matching belongs to precise matching, and * is fuzzy matching. Precise matching must have value here. Fuzzy matching can be omitted, and it can be filled in later
		name := context.Param("name")
		context.Writer.WriteString("hello " + name)
	})
	router.Run(":80")
}

3.4 getting json data

package main

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

func main() {
	router := gin.Default()
	router.POST("/:name", func(context *gin.Context) {
		nameBytes, _ := context.GetRawData() // Get the string of json. If you want to use json Unmarshal (namebytes, & obj) is parsed
		context.Writer.WriteString("hello " + string(nameBytes))
	})
	router.Run(":80")
}

3.5 parameter binding

The above calling method is actually troublesome in actual use, especially the binding method of json. You can use context Shouldbind (& obj) method is automatically bound to an object for use. It also has a corresponding context Shouldbindquery() and context The shouldbindjson () method can use

IV route

4.1 General routing

Just like the above

If you want all request methods (get, post, delete, etc.) to be processed under this path, you can use any method:

func main() {
	router := gin.Default()
	router.Any("/hello", func(context *gin.Context) { // In this way, all hello requests will match regardless of the method
		context.Writer.WriteString("hello")
	})
	router.Run(":80")
}

404 the processing method can be solved with NoRoute, and the parameter is a func(context *gin.Context)

4.2 routing packet

A route with a common prefix can be divided into a routing group, as follows:

package main

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

func main() {
	router := gin.Default()
	group := router.Group("/groups")
	{
		group.GET("/hello", func(context *gin.Context) {
			context.Writer.WriteString("hi")
		})
	}
	router.Run(":80")
}

visit http://localhost/groups/hello OK, group Get is prefixed with groups by default. Groups also have Get Post and other request methods, and can be nested. There is no default prefix for router

V middleware

You need to implement a HandlerFunc type function, which is what the above processing function looks like, and then use router Use (func)

func timeDurationMiddleware(context *gin.Context) {
	now := time.Now().Unix()
	context.Next() // tag
	timeCost := time.Now().Unix() - now
	context.Writer.WriteString(strconv.Itoa(int(timeCost)))
}
func main() {
	router := gin.Default()
	router.Use(timeDurationMiddleware)
	group := router.Group("/groups")
	{
		group.GET("/hello", func(context *gin.Context) {
			time.Sleep(time.Second + 1)
			context.Writer.WriteString("hi ")
		})
	}
	router.Run(":80")
}

In this way, a middleware that records the call time is implemented. The Next() function means that the following handlerFunc is executed first and accessed in the defined order http://localhost/groups/hello First go to the timeDurationMiddleware middleware registered globally, and then go to the processing function of Hello request, then call context Next will stop at the position of tag annotation to execute the following processing function, and then go back to execute the code under tag, which is similar to a stack process. Another function is context Abort() means that the following middleware and processing functions will not be used after the current function is executed.

The Default of gin defaults to two middleware. You can use gin New() to get an engine handler without middleware

Vi Custom Log

It's troublesome. I haven't sorted out the principle yet. I'll supplement it later~

VII Parameter verification

gin comes with validation verification. When using it, you need to add tag and use ShouldBind:

type People struct {
	Name string `json:"name" binding:"required"`
}

func main() {
	router := gin.Default()
	group := router.Group("/groups")
	{
		group.POST("/hello", func(context *gin.Context) {
			p := new(People)
			if err := context.ShouldBindJSON(p); err != nil {
				fmt.Println("Error ", err.Error())
			} else {
				fmt.Println(p)
			}
		})
	}
	router.Run(":80")
}

This is an example. The name field is required. If it is not filled in, an error in the first line will appear.

There are also some common verifications as follows (multiple verifications are separated by commas and do not add spaces):

eqfield= // Which attribute should be the same as that of the struct
oneof=a b c // Need to be one of abc
email // Need to conform to mailbox format
gt=3 // Greater than 3
gte=3 // Greater than or equal to 3
eq=3 // Equal to 3
len=5 // The length of slice or string is equal to 5

VIII Permission Middleware

jwt authentication can be realized by using the third-party library jwt go. The three parts of jwt are header, load and signature. The code is as follows:

package jwt

import (
	"github.com/dgrijalva/jwt-go"
	"time"
)

const (
	TokenExpireDuration = time.Hour * 2 // Expiration time of token
)
var jwtSecret = []byte("secret key")

type MyClaims struct {
	UserID int64 `json:"user_id"` // Some customized information belongs to the content of the load
	Username string `json:"username"`
	jwt.StandardClaims // Here are some common load information such as certification authority and expiration time
}

func GenToken(userId int64, username string) (string, error) {
	c := MyClaims{ // Construct a load
		UserID: userId,
		Username: username,
		StandardClaims: jwt.StandardClaims{
			ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // Expiration time
			Issuer: "bluebell",
		},
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, c) // Sign with HS256
	return token.SignedString(jwtSecret) // jwtSecret is the salt value and is encrypted with HS256 salt value
}

func ParseToken(tokenString string) (*MyClaims, error) { // Parsing a token will also return an error if it expires
	var claims = new(MyClaims)
	_, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) {
		return jwtSecret, nil
	})
	if err != nil {
		return nil, err
	}
	return claims, nil
}

Then implement an authenticated middleware:

// JWT authmiddleware authentication middleware based on JWT
func JWTAuthMiddleware() func(c *gin.Context) {
	return func(c *gin.Context) {
		// There are three ways for the client to carry a Token. 1 Put in request header 2 Put in the request body 3 Put in URI
		// Here, it is assumed that the Token is placed in the Authorization of the Header and starts with Bearer
		// The specific implementation method here should be determined according to your actual business situation
		authHeader := c.Request.Header.Get("Authorization")
		// After obtaining the token, do some legal verification
		// valid code ... 

		mc, err := jwt.ParseToken(parts[1])
		if err != nil {
			response.ResponseError(c, response.CodeInvalidToken)
			c.Abort() // If it's illegal, you have to interrupt this request
			return
		}
		c.Next() // Subsequent processing functions can use c.Get("userID") to obtain the currently requested user information
	}
}

IX Https support

First, you need a certificate, and then modify ListenAndServe to ListenAndServeTLS() (or call RunTLS of gin engine):

func main() {
	gin.SetMode(gin.ReleaseMode)
	router := gin.Default()
	group := router.Group("/groups")
	{
		group.POST("/hello", func(context *gin.Context) {
			p := new(People)
			if err := context.ShouldBindJSON(p); err != nil {
				fmt.Println("Error ", err.Error())
			} else {
				fmt.Println(p)
			}
		})
	}
	router.RunTLS(":443", "server.pem", "server.key")
}

X Elegant closure

It can be implemented through third-party endless, but one can be written by hand without introducing a third-party package:

// +build go1.8

package main

import (
  "context"
  "log"
  "net/http"
  "os"
  "os/signal"
  "syscall"
  "time"

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

func main() {
  router := gin.Default()
  router.GET("/", func(c *gin.Context) {
    time.Sleep(5 * time.Second)
    c.String(http.StatusOK, "Welcome Gin Server")
  })

  srv := &http.Server{
    Addr:    ":8080",
    Handler: router,
  }

  // ListenAndServe will block, so it should be started asynchronously
  // HTTP is not used here Listenandserve and router Run starts because we can't get the server object and graceful shutdown
  go func() {
    if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
      log.Fatalf("listen: %s\n", err)
    }
  }()

  // A signal channel must be 1 in size, otherwise it can not be blocked
  quit := make(chan os.Signal, 1)
  // kill (no param) default send syscall.SIGTERM
  // kill -2 is syscall.SIGINT
  // kill -9 is syscall.SIGKILL but can't be catch, so don't need add it
  // Register the two stop modes into the quit channel
  signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
  // block
  <-quit
  log.Println("Shutting down server...")

  // The context is used to inform the server it has 5 seconds to finish
  // the request it is currently handling
  ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  defer cancel()
  // In shutdown, if 5s is not over, a select will receive the above timeout error and return it
  // If the normal graceful end is lost, the cancel function will be called to issue a normal end
  if err := srv.Shutdown(ctx); err != nil {
    log.Fatal("Server forced to shutdown:", err)
  }

  log.Println("Server exiting")
}

Xi A process starts multiple services

Start the two processes and start the server respectively. The approximate wording is as follows:

func main() {
	r1 := gin.New()
	r2 := gin.New()
	var g errgroup.Group // To prevent blocking, a co process should be started to prevent the main process G from blocking
	g.Go(func() error {
		return r1.Run(":8080") // server.ListenAndServe can also be used. The essence is the same
	})
	g.Go(func() error {
		return r2.Run(":8081")
	})
	if err := g.Wait(); err != nil {
		log.Fatal(err)
	}
}

Keywords: Go Front-end Back-end RESTful

Added by bad_gui on Mon, 07 Feb 2022 00:01:26 +0200