brief introduction
Gin framework is a Web framework written in Go language and encapsulated based on net/http package.
The routing function of Gin core is through the customized version HttpRouter It has high routing performance.
Basic functions of Web Services
1. HTTP / HTTPS support
Because Gin framework is a Web framework encapsulated based on net/http package, it naturally supports HTTP / HTTPS.
Start an HTTP service by:
insecureServer := &http.Server { Addr : ":8080", Handle: router(), ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, } ... err := insecureServer.ListenAndServe()
Start an HTTPS service by:
secureServer := &http.Server { Addr: ":8443", Handler: router(), ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, } ... err := secureServer.ListenAndServeTLS("server.pem", "server.key")
2. Route matching
Gin framework supports two routing matching rules: exact matching and fuzzy matching.
2.1 exact matching
For example, the route is / products/:name
The matching is as follows:
route | Matching situation |
---|---|
/products/iphone12 | √ |
/products/xiaomi8 | √ |
/products/xiaomi8/music | × |
/products/ | × |
2.2 fuzzy matching
For example, the route is / products/*name
The matching is as follows:
3. Routing packet
Gin realizes the function of routing packets through the Group function.
(1) Routes of the same version can be grouped.
(2) Routes of the same RESTful resources can be grouped.
(3) Through routing packets, the routes of the same packets can be processed uniformly.
// For unified processing, add gin to all routes belonging to v1 packets Basicauth middleware to realize authentication function. // v1 grouping v1 := router.Group("/v1", gin.BasicAuth(gin.Accounts{"foo": "var", "admin": "pass"})) { // Route matching productv1 := v1.Group("/products") { productv1.POST("", productHandler.Create) productv1.GET(":name", productHandler.Get) } // Route matching orderv1 := v1.Group("/orders") { orderv1.POST("", orderHandler.Create) orderv2.GET(":name", orderHandler.Get) } } // v2 grouping v2 := router.Group("/v2", gin.BasicAuth(gin.Accounts{"foo": "var", "admin": "pass"})) { productv2.POST("", productHandler.Create) productv2.GET(":name", productHandler.Get) }
4. One process and multiple services
The following code implements two same services, listening on different ports respectively.
Note that in order to start the second service without blocking, you need to execute the ListenAndServe function in goroutine and call eg.Wait() to block the program process, so that the two HTTP services can continuously listen to the port in goroutine and provide services.
var eg errgroup.Group insecureServe := &http.Server{...} secureServe := &http.Server{...} eg.Go(func() error { err := insecureServer.ListenAndServe() if err := nil && err != http.ErrServerClosed { log.Fatal(err) } return err }) eg.Go(func() error { err := secureServer.ListenAndServeTLS("server.pem", "server.key") if err != nil && err != http.ErrServerClosed { log.Fatal(err) } return err }) if err := eg.Wait(); err != nil { log.Fatal(err) }
5. Parameter analysis, parameter verification, logic processing and return results
A Web service should have four functions: parameter parsing, parameter verification, logical processing and return results.
HTTP request parameters can exist in different locations. How does Gin parse them?
5.1 HTTP has the following five parameter types
- Path parameter (path)
For example: / user/:name
Where name is the path parameter.
- Query string parameter (query)
For example: / welcome? firstname=tian&lastname=dh
Among them, firstname and lastname are query string parameters.
- Form parameters (form)
For example: curl -X POST -F 'username=tian' -F 'password=123456' http://domain.com/login
Where username and password are form parameters.
- HTTP header parameter (header)
For example: curl - x post - H 'content type: application / JSON' - D '{username ":" admin "," password ":" 123456 "}' http://domain.com/login
Where content type is the HTTP header parameter.
- Message body parameter (body)
For example: curl - x post - H 'content type: application / JSON' - D '{username ":" admin "," password ":" 123456 "}' http://domain.com/login
Where username and password are message body parameters.
5.2 parameter analysis
Method 1: directly read the value of a parameter
Use the c.Param() function
Method 2: bind similar HTTP parameters to a Go structure
When binding parameters, Gin determines which type of parameters to bind to the structure through the tag of the structure.
gin.Default().GET("/:name/:id", nil) // Mode 1 name := c.Param("name") // Mode II type Person struct { ID string `uri: "id" binding: "required,uuid"` Name string `uri: "name" binding: "required"` } if err := c.ShouldBindUri(&person); err != nil { // normal code return }
Different HTTP parameters have different structures tag:
- Path parameter: uri
- Query string parameter: form
- Form parameters: form
- HTTP header parameter: header
- Message body parameter: select json or xml automatically according to the content type; You can also call ShouldBindJSON or ShouldBindXML to specify which tag to use.
For each parameter type, Gin has corresponding functions to obtain and bind these parameters.
These functions are encapsulated based on ShouldBindWith and MustBindWith:
(1)ShouldBindWith(obj interface{}, b binding.Binding) error
The bottom layer of many ShouldBindXXX functions calls ShouldBindWith function to complete parameter binding.
This function will bind the parameters to the passed structure pointer according to the passed binding engine.
If the binding fails, only the error content is returned, but the HTTP request is not terminated.
ShouldBindWith supports multiple binding engines, such as:
binding.JSON,binding.Query,binding.Uri,binging.Header, etc.
For more details, please refer to binding.go
(2)MustBindWith(obj interface{}, b binding.Binding) error
The bottom layer of many BindXXX functions calls MustBindWith function to complete parameter binding.
This function will bind the parameters to the passed structure pointer according to the passed binding engine.
If the binding fails, an error is returned and the request is terminated, and an HTTP 400 error is returned.
The binding engine supported is the same as the ShouldBindWith function.
Gin derives many new Bind functions based on ShouldBindWith and MustBindWith.
These functions can meet the requirements of obtaining HTTP parameters in different scenarios.
The function provided by Gin can obtain five categories of HTTP parameters.
(1) Path parameters
ShouldBindUri
BindUri
(2) Query string parameters
ShouldBindQuery
BindQuery
(3) Form parameters
ShouldBind
(4) HTTP header parameters
ShouldBindHeader
BindHeader
(5) Message body parameters
ShouldBindJSON
BindJSON
Note: Gin does not provide functions such as ShouldBindForm and BindForm to bind form parameters.
When the HTTP method is GET, ShouldBind only binds Query type parameters;
When the HTTP method is POST, first check whether the content type is json or xml. If not, bind the parameters of Form type.
Therefore, ShouldBind can bind Form type parameters, but only if the HTTP method is POST and the content type is not application/json or application/xml.
It is recommended to use ShouldBindXXX to ensure that the set HTTP Chain (which can be understood as a series of processing plug-ins for HTTP requests) can continue to be executed.
5.3 examples
func (u *productHandler) Create (c *gin.Context) { u.Lock() defer u.Unlock() // 1. Parameter analysis var product Product if err := c.ShouldBindJSON(&product); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } // 2. Parameter verification if _, ok := u.products[product.Name]; ok { c.JSON(http.StatusBadRequest, gin.H{ "error": fmt.Sprintf("product %s already exist", product.Nme)}) return } product.CreatedAt = time.Now() // 3. Logic processing u.products[product.Name] = product log.Printf("Register product %s success", product.Name) // 4. Return results c.JSON(http.StatusOK, product) }
Web services advanced features
1. Middleware
1.1 INTRODUCTION
Gin supports middleware. HTTP requests will be processed by a series of loaded middleware before being forwarded to the actual processing function.
1.2 use
In the middleware, you can parse HTTP requests and do some logical processing. For example: cross domain processing, or generate X-Request-ID and save it in the context to track a request.
After processing, (1) you can choose to interrupt and return the request. (2) You can also continue to forward the request to the next middleware for processing.
After all middleware processes are completed, the request will be transferred to the routing function for processing.
1.3 advantages and disadvantages
Through middleware, we can handle all requests uniformly, improve development efficiency and make our code more concise.
However, because all requests need to be processed by the middleware, the request delay may be increased.
For middleware features, the following suggestions are made:
- The middleware is made loadable, and the middleware loaded when the program is started is specified through the configuration file.
- Only some general and necessary functions are made into middleware.
- When writing middleware, we must ensure the code quality and performance of middleware.
1.4 Gin uses Middleware
In Gin, you can use Gin Use method of engine to load middleware. It can be loaded in different locations, and the scope of action is different in different locations.
// Returns a gin Engine router := gin.New() // Use method to load Middleware // Middleware works on all HTTP requests router.Use(gin.Logger(), gin.Recovery()) // Middleware works on v1 group v1 := router.Group("/v1").Use(gin.BasicAuth(gin.Accounts{"foo": "bar"})) //Middleware only works on the / v1/login API interface v1.POST("/login", Login).Use(gin.BasicAuth(gin.Accounts{"foo": "bar"}))
1.5 middleware supported by gin
- gin.Logger()
The Logger middleware will write the log to gin DefaultWriter.
gin.DefaultWriter defaults to OS Stdout .
- gin.BasicAuth()
HTTP request basic authentication (authentication with user name and password)
- gin.Recovery()
The Recovery middleware can recover from any panic and write a 500 status code.
- gin.CustomRecovery(handle gin.ReciveryFunc)
It is similar to the Recover middleware, but the incoming handle method will be called for processing during recovery.
- Custom Middleware
Middleware is actually a function. The function type is gin HandleFunc.
The underlying type of HandleFunc is func (*Context).
// Custom Logger() middleware func Logger() gin.HandlerFunc { return func (c *gin.Context) { t := time.Now() // Set variable example c.Set("example", "12345") // Before request c.Next() // After request latency := time.Since(t) log.Print(latency) // Access the status we sent status := c.Writer.Status() log.Println(status) } } func main() { r := gin.New() // Customized Logger Middleware r.Use(Logger()) r.GET("/test", func (c *gin.Context) { example := c.MustGet("example").(string) // print : "12345" log.Println(example) }) r.Run(":8080") }
1.6 open source middleware
middleware | function |
---|---|
gin-jwt | JWT middleware to realize JWT authentication |
gin-swagger | Automatically generate RESTful API documents in Swagger 2.0 format |
cors | Cross domain implementation of HTTP requests |
sessions | Session management middleware |
authz | Authorization middleware based on caspin |
pprof | gin pprof Middleware |
go-gin-prometheus | Prometheus metrics exporter |
gzip | Support gzip compression of HTTP requests and responses |
gin-limit | HTTP request Concurrency Control Middleware |
requestid | Generate uuid for each Request and add it to the returned X-Request-ID Header. |
2. Authentication, RequestID, cross domain
These three advanced functions can be realized through Gin middleware.
router := gin.New() // authentication router.Use(gin.BasicAuth(gin.Accounts{"foo": "bar"}))
router := gin.New() // RequestID router.Use(requestid.New(requestid.Config{ Generator: func() string { return "test" }, }))
router := gin.New() // Cross domain // CORS for https://foo.com and https://github.com // allowing: // - PUT and PATCH methods // - Origin header // - Credentials share // - Prefight requests cached for 12 hours router.Use(cors.New(cors.Config{ AllowOrigins: []string{"https://foo.com"}, AllowMethods: []string{"PUT", "PATCH"}, AllowHeaders: []string{"Origin"}, ExposeHeaders: []string{"Content-Length"}, AllowCredentials: true, AllowOriginFunc: func (origin string) bool { return origin == "https://github.com" }, MaxAge: 12 * time.Hour, }))
3. Graceful shutdown
After the project goes online, we need to constantly iterate to enrich the project functions and repair bugs, which means that we need to constantly restart the service.
For HTTP services, if the traffic is large, many connections may not be disconnected and requests may not be completed when the service is restarted. If you close the service directly at this time, these connections will be directly disconnected and the request will terminate abnormally, which will affect the user experience.
We expect the HTTP service to close these connections normally after processing all requests, that is, to close the service gracefully.
There are two ways to gracefully turn off HTTP services:
3.1 with the help of a third party Go package
The most commonly used packages are fvbock/endless
You can use fvlock / endless to replace the ListenAndServe method of net/http.
router := gin.Default() router.GET("/", handler) ... endless.ListenAndServe(":4242", router)
3.2 self coding implementation
The advantage of using third-party packages is that it can slightly reduce the coding workload, but the disadvantage is that a new dependent package is introduced.
Therefore, they prefer to implement their own coding.
Go version 1.8 or later, http The server's built-in Shutdown method has realized graceful Shutdown.
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, } // Put SRV Listenandserve is executed in goroutine, // This will not block the SRV Shutdown function. go func() { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("listen: %s\n", err) } }() // Because we put SRV Listenandserve is placed in goroutine, // So we need a mechanism that can make the whole process resident. // Use unbuffer channel and call signal The notify function binds the channel to SIGINT and SIGTERM // On the signal. In this way, after receiving SIGINT and SIGTERM signals, the quit channel will be written to the value, so as to end the blocking state and the program // Continue running and execute SRV Shutdown (CTX), graceful shutdown of HTTP service. quit := make(chan os.Signal) // kill (no param) default send syscall syscall.SIGTERM // kill -2 is syscall.SIGINT // kill -9 is syscall.SIGKILL but can't be cache, so don't need add it signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Println("Shutting down server...") ctx, cancel := context.WithTimeout(context.Background(), 5 * time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Fatal("Server forced to shutdown: ", err) } log.Println("Server exiting") }