Go language web framework - Gin

Gin

Gin is a web framework written in go language

1 Web workflow

  • The client establishes a TCP connection to the server through the TCP/IP protocol
  • The client sends the HTTP protocol request Request to the server GET /url, requesting the resource document in the server
  • The server sends the HTTP protocol response Response to the client. If the requested resource contains dynamic language content, the server will call the interpretation engine of the dynamic language to process the "dynamic content" and return the processed data to the client.
  • Client disconnected from server. The HTML document is interpreted by the client, and the graphical result is rendered on the client screen

img

2 Gin frame

The two most popular lightweight web frameworks in the Go language are Gin and Echo. These two frameworks are similar, both are plug-in lightweight frameworks, and behind them are an open source ecology to provide various small plug-ins. The performance of these two frameworks is also very good, and the naked test runs fast. Gin started earlier than Echo, with a higher market share and a richer ecology.

Reference excellent article:

2.1 Hello World

The general process of Gin running is:

  1. Instantiate an Engine structure
  2. Bind the Engine instance to http.Server through the net/http library
  3. net/http start listening service
  4. After receiving the request, forward the request to the Engine's ServeHTTP interface, and call handleHTTPRequest to process the request
  5. handleHTTPRequest processes the path, and obtains request handlers by querying the nodes of trees
  6. Handlers are handed over to the Context to make the actual handler call and return the request result
func main() {
    
    
   // 初始化一个http服务对象
   engine := gin.Default()
   // 设置一个get请求的路由,url为localhost
   engine.GET("/", func(c *gin.Context) {
    
    
      c.String(http.StatusOK, "hello World!")
   })
    //监听并启动服务,默认 http://localhost:8080/
   engine.Run()
}

engine.Run()

// 将 Engine 的路由挂载到 http.Server 上,并开起监听,等待 HTTP 请求
//     addr:监听地址
func (engine *Engine) Run(addr ...string) (err error) {
    
    
    defer func() {
    
     debugPrintError(err) }()

    address := resolveAddress(addr)
    debugPrint("Listening and serving HTTP on %s\n", address)
    err = http.ListenAndServe(address, engine)
    return
}

2.2 gin.Engine

Engine is the most important data structure of the Gin framework, and it is the entry point of the framework. We use the Engine object to define service routing information, assemble plug-ins, and run services . The entire web service is driven by it. The underlying HTTP server uses the built-in http server of the Go language. The essence of Engine is just a wrapper for the built-in HTTP server, making it more convenient to use.

gin.Default()

// 返回一个启用 Recovery 中间件 和 Logger 中间件的 Engine 实例
// Logger 用于输出请求日志,Recovery 确保单个请求发生 panic 时记录异常堆栈日志,输出统一的错误响应。
func Default() *Engine {
    
    
    debugPrintWARNINGDefault()
    engine := New()
    engine.Use(Logger(), Recovery())
    return engine
}

Request response process:

  • client request server
  • The HTTP service engine http.Server receives the request, and initially processes it into http.ResponseWriter and *http.Request and passes it to the registered upper layer request processing Handler (implements the ServerHTTP interface and registers to the Handler of http.ListenAndServe) (that is, Gin's Engine)
  • Engine puts the request data into the Context pool and passes it to Engine's handleHTTPRequest for processing
  • handleHTTPRequest finds the corresponding node from the trees, and calls back the registered request processing Handler

2.3 Routing and Controller

Routing is a process, referring to an http request , how to find the corresponding processor function (also called a controller function), the controller function is mainly responsible for executing the http request-response task.

r := gin.Default()

// 路由定义post请求, url路径为:/user/login, 绑定doLogin控制器函数
r.POST("/user/login", doLogin)

// 控制器函数
func doLogin(c *gin.Context) {
    
    
        // 获取post请求参数
	username := c.PostForm("username")
	password := c.PostForm("password")

	// 通过请求上下文对象Context, 直接往客户端返回一个字符串
	c.String(200, "username=%s,password=%s", username,password)
}

2.3.1 Routing rules

A routing rule consists of three parts:

  • http request method
    • GET
    • POST
    • PUT
    • DELETE
  • URL path
  • controller function

2.3.2 URL path

There are three ways to write the url path in the echo frame:

  • static url path
  • url path with path parameters
  • url paths with asterisk (*) fuzzy matching parameters
// 例子1, 静态Url路径, 即不带任何参数的url路径
/users/center
/user/111
/food/12

// 例子2,带路径参数的url路径,url路径上面带有参数,参数由冒号(:)跟着一个字符串定义。
// 路径参数值可以是数值,也可以是字符串

//定义参数:id, 可以匹配/user/1, /user/899 /user/xiaoli 这类Url路径
/user/:id

//定义参数:id, 可以匹配/food/2, /food/100 /food/apple 这类Url路径
/food/:id

//定义参数:type和:page, 可以匹配/foods/2/1, /food/100/25 /food/apple/30 这类Url路径
/foods/:type/:page

// 例子3. 带星号(*)模糊匹配参数的url路径
// 星号代表匹配任意路径的意思, 必须在*号后面指定一个参数名,后面可以通过这个参数获取*号匹配的内容。

//以/foods/ 开头的所有路径都匹配
//匹配:/foods/1, /foods/200, /foods/1/20, /foods/apple/1 
/foods/*path

//可以通过path参数获取*号匹配的内容。

2.3.3 Controller functions

Controller function definition:

// 控制器函数接受一个上下文参数。可以通过上下文参数,获取http请求参数,响应http请求。
func HandlerFunc(c *gin.Context)
//实例化gin实例对象。
r := gin.Default()
	
//定义post请求, url路径为:/users, 绑定saveUser控制器函数
r.POST("/users", saveUser)

//定义get请求,url路径为:/users/:id  (:id是参数,例如: /users/10, 会匹配这个url模式),绑定getUser控制器函数
r.GET("/users/:id", getUser)

//定义put请求
r.PUT("/users/:id", updateUser)

//定义delete请求
r.DELETE("/users/:id", deleteUser)


//控制器函数实现
func saveUser(c *gin.Context) {
    
    
    ...忽略实现...
}

func getUser(c *gin.Context) {
    
    
    ...忽略实现...
}

func updateUser(c *gin.Context) {
    
    
    ...忽略实现...
}

func deleteUser(c *gin.Context) {
    
    
    ...忽略实现...
}

Tip: In actual project development, do not write both routing definitions and controller functions in one go file, as it is inconvenient to maintain. You can refer to the project structure in Chapter 1 to plan your own business modules.

2.3.4 Packet Routing

Route grouping, in fact, is to set the url prefix of the same type of route. When doing api development, if you want to support multiple api versions, you can implement api version processing through group routing.

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

	// 创建v1组
	v1 := router.Group("/v1")
	{
    
    
         // 在v1这个分组下,注册路由
		v1.POST("/login", loginEndpoint)
		v1.POST("/submit", submitEndpoint)
		v1.POST("/read", readEndpoint)
	}

	// 创建v2组
	v2 := router.Group("/v2")
	{
    
    
         // 在v2这个分组下,注册路由
		v2.POST("/login", loginEndpoint)
		v2.POST("/submit", submitEndpoint)
		v2.POST("/read", readEndpoint)
	}

	router.Run(":8080")
}

The above example will register the following routing information:

  • /v1/login
  • /v1/submit
  • /v1/read
  • /v2/login
  • /v2/submit
  • /v2/read

2.4 RouterGroup

RouterGroup is the packaging of the routing tree, and all routing rules are ultimately managed by it. The Engine structure inherits RouterGroup, so Engine directly has all the routing management functions of RouterGroup. At the same time, the RouteGroup object will also contain a pointer to Engine.

type Engine struct {
    
    
  RouterGroup
  ...
}

type RouterGroup struct {
    
    
  ...
  engine *Engine
  ...
}

2.4.1 Relationship between Engine and RouterGroup

  • Manage Engine's trees (routing tree)
  • Manage middleware
  • Manage routing groups

From the implementation code of RouterGroup, it can be seen that it is meaningless to exist alone. It is an abstraction layer dedicated to Engine, mainly used to manage the routing of Engine.

  • Since the entities of trees are in the Engine, the RouterGroup needs to use the Engine to operate the trees, so there will always be a pointer variable pointing to the Engine instance in the RouterGroup
  • The design of RouterGroup's Group interface and root variable endows it with the ability to group routes

2.4.2 RouterGroup method

RouterGroup implements the IRouter interface and exposes a series of routing methods. These methods finally hook the request processor into the routing tree by calling the Engine.addRoute method.

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
// 匹配所有 HTTP Method
Any(string, ...HandlerFunc) IRoutes

2.4.3 Routing registration process

  • Engine instance (Engine known to include RouterGroup) calls GET, passing request path, response function method
  • GET calls group.handle,
  • And call AddRouter of Engine through the Engine pointer of itself (RouterGroup in Engine)
  • Add the request path and response method to the corresponding root of the trees

2.5 gin.Context

Save the context information of the request, which is the entry parameter of all request handlers.

type HandlerFunc func(*Context)

type Context struct {
    
    
  ...
  Request *http.Request // 请求对象
  Writer ResponseWriter // 响应对象
  Params Params // URL匹配参数
  ...
  Keys map[string]interface{
    
    } // 自定义上下文信息
  ...
}

The Context object provides very rich methods for obtaining the context information of the current request. If you need to obtain URL parameters, Cookies, and Headers in the request, you can obtain them through the Context object. This series of methods is essentially a wrapper for the http.Request object.

// 获取 URL 匹配参数  /book/:id
func (c *Context) Param(key string) string
// 获取 URL 查询参数 /book?id=123&page=10
func (c *Context) Query(key string) string
// 获取 POST 表单参数
func (c *Context) PostForm(key string) string
// 获取上传的文件对象
func (c *Context) FormFile(name string) (*multipart.FileHeader, error)
// 获取请求Cookie
func (c *Context) Cookie(name string) (string, error) 
...

2.6 Gin framework operation mode

To facilitate debugging, the Gin framework defaults to the debug mode when it is running, and a lot of debug logs will be printed out by default on the console. When going online, we need to turn off the debug mode and change to the release mode.

Set the Gin framework operating mode:

2.6.1 Setting through environment variables

export GIN_MODE=release

GIN_MODE environment variable, can be set to debug or release

2.6.2 Set by code

In the main function, execute the following code when initializing the gin framework

// 设置 release模式
gin.SetMode(gin.ReleaseMode)
// 或者 设置debug模式
gin.SetMode(gin.DebugMode)

3 Gin processing request parameters

3.1 Get Get request parameters

Get request url example:*/path?id=1234&name=Manu&value=*111

Common functions for obtaining Get request parameters:

  • func (c *Context) Query(key string) string
  • func (c *Context) DefaultQuery(key, defaultValue string) string
  • func (c *Context) GetQuery(key string) (string, bool)

example:

func Handler(c *gin.Context) {
    
    
	//获取name参数, 通过Query获取的参数值是String类型。
	name := c.Query("name")

        //获取name参数, 跟Query函数的区别是,可以通过第二个参数设置默认值。
        name := c.DefaultQuery("name", "tizi365")

	//获取id参数, 通过GetQuery获取的参数值也是String类型, 
	// 区别是GetQuery返回两个参数,第一个是参数值,第二个参数是参数是否存在的bool值,可以用来判断参数是否存在。
	id, ok := c.GetQuery("id")
        if !ok {
    
    
	   // 参数不存在
	}
}

Tip: The logic for judging the existence of a parameter in the GetQuery function is that if the value of the parameter is empty, the parameter is considered to exist. Only when the parameter is not submitted, the parameter does not exist.

3.2 Get Post request parameters

  • The form transmission is a post request, and there are four common transmission formats for http:
    • application/json
    • application/x-www-form-urlencoded
    • application/xml
    • multipart/form-data
  • Form parameters can be obtained through the PostForm() method, which parses parameters in x-www-form-urlencoded or from-data format by default

Common functions to obtain Post request parameters:

  • func (c *Context) PostForm(key string) string
  • func (c *Context) DefaultPostForm(key, defaultValue string) string
  • func (c *Context) GetPostForm(key string) (string, bool)

example:

func Handler(c *gin.Context) {
    
    
	//获取name参数, 通过PostForm获取的参数值是String类型。
	name := c.PostForm("name")

	// 跟PostForm的区别是可以通过第二个参数设置参数默认值
	name := c.DefaultPostForm("name", "tizi365")

	//获取id参数, 通过GetPostForm获取的参数值也是String类型,
	// 区别是GetPostForm返回两个参数,第一个是参数值,第二个参数是参数是否存在的bool值,可以用来判断参数是否存在。
	id, ok := c.GetPostForm("id")
	if !ok {
    
    
	    // 参数不存在
	}
}

Test Post

1. Edit html page

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<form action="http://localhost:8080/form" method="post" action="application/x-www-form-urlencoded">
    用户名:<input type="text" name="username" placeholder="请输入你的用户名">  <br>&nbsp;&nbsp;&nbsp;码:<input type="password" name="userpassword" placeholder="请输入你的密码">  <br>
    <input type="submit" value="提交">
</form>
</body>
</html>

2. Edit post request

func main() {
    
    
   engine := gin.Default()
   engine.POST("/form", func(c *gin.Context) {
    
    
      types := c.DefaultPostForm("type", "post")
      username := c.PostForm("username")
      password := c.PostForm("userpassword")
      c.String(http.StatusOK, fmt.Sprintf("username:%s ,password: %s,type: %s", username, password, types))
   })
   engine.Run()
}

3. Start the service, the service will always listen to '/form' on port 8080 by default

4. Copy the local absolute path of html, open it in the browser, and submit the form

5. After clicking submit, it will automatically jump to http://localhost:8080/form, the page displays the form items obtained by post, and the print display
insert image description here

3.3 Get URL path parameters

Obtaining URL path parameters refers to obtaining /user/: idthe parameters bound to this type of route. In this example, a parameter id is bound.

Common functions for getting url path parameters:

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

example:

r := gin.Default()
	
r.GET("/user/:id", func(c *gin.Context) {
    
    
	// 获取url参数id
	id := c.Param("id")
})

3.4 Bind request parameters to struct object

The previous method of obtaining parameters is to read parameters one by one, which is cumbersome. The Gin framework supports automatic binding of request parameters to a struct object. This method supports Get/Post requests, and also supports HTTP requests with json/xml as the body content. format parameter.

example:

The following example is to bind the request parameters to the User struct object.

// User 结构体定义
type User struct {
    
    
  Name  string `json:"name" form:"name"`
  Email string `json:"email" form:"email"`
}

By defining the label of the struct field, define the relationship between the request parameter and the struct field.
The label of the Name field of User is described below.

struct tag description:

Label illustrate
json:“name” The data format is in json format, and the json field name is name
form:“name” The form parameter is named name
xml:“name”

Tip: You can choose the supported data type according to your own needs. For example, if you need to support the json data format, you can define the field label like this: json: "name"

Let's look at the controller code:

r.POST("/user/:id", func(c *gin.Context) {
    
    
   // 初始化user struct
   u := User{
    
    }
   // 通过ShouldBind函数,将请求参数绑定到struct对象, 处理json请求代码是一样的。
   // 如果是post请求则根据Content-Type判断,接收的是json数据,还是普通的http请求参数
   if c.ShouldBind(&u) == nil {
    
    
     // 绑定成功, 打印请求参数
     log.Println(u.Name)
     log.Println(u.Email)

    }
    // http 请求返回一个字符串 
    c.String(200, "Success")
})

Tip: If you pass request parameters in json format through http request body, and submit the parameters through post request, you need to set Content-Type to application/json, if it is data in xml format, set it to application/xml

3.5 How Gin obtains client ip

r := gin.Default()
	
r.GET("/ip", func(c *gin.Context) {
    
    
	// 获取用户IP
	ip := c.ClientIP()
})

4 Gin processes context response results

The gin.Context context object supports multiple return processing results. The following introduces different response methods.

4.1 c.String

Respond to the request as a string, and return a string through the String function.

Function definition:

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

Parameter Description:

parameter illustrate
code http status code
format The returned result supports string format definitions similar to the Sprintf function, for example, %d represents inserting integers, and %s represents inserting strings
values A string format parameter defined by any number of format parameters

example:

func Handler(c *gin.Context)  {
    
    
	// 例子1:
	c.String(200, "欢迎访问tizi360.com!")
	
	// 例子2: 这里定义了两个字符串参数(两个%s),后面传入的两个字符串参数将会替换对应的%s
	c.String(200,"欢迎访问%s, 你是%s", "tizi360.com!","最靓的仔!")
}

Tip: The net/http package defines a variety of common status code constants, for example: http.StatusOK == 200, http.StatusMovedPermanently == 301, http.StatusNotFound == 404, etc. For details, please refer to the net/http package

4.2 c.JSON

Respond to the request in json format

// User 定义
type User struct {
    
    
  Name  string `json:"name"` // 通过json标签定义struct字段转换成json字段的名字。
  Email string `json:"email"`
}

// Handler 控制器
func(c *gin.Context) {
    
    
  //初始化user对象
  u := &User{
    
    
    Name:  "tizi365",
    Email: "[email protected]",
  }
  //返回json数据
  //返回结果:{"name":"tizi365", "email":"[email protected]"}
  c.JSON(200, u)
}

4.3 c.XML

Respond to request in xml format

// User 定义, 默认struct的名字就是xml的根节点名字,这里转换成xml后根节点的名字为User.
type User struct {
    
    
  Name  string `xml:"name"` // 通过xml标签定义struct字段转换成xml字段的名字。
  Email string `xml:"email"`
}

// Handler 控制器
func(c *gin.Context) {
    
    
  //初始化user对象
  u := &User{
    
    
    Name:  "tizi365",
    Email: "[email protected]",
  }
  //返回xml数据
  //返回结果:
  //  <?xml version="1.0" encoding="UTF-8"?>
  //  <User><name>tizi365</name><email>[email protected]</email></User>
  c.XML(200, u)
}

4.4 c.File

Respond to requests in file format

The following describes how the gin framework returns a file directly, which can be used for file download.

例子1func(c *gin.Context) {
    
    
  //通过File函数,直接返回本地文件,参数为本地文件地址。
  //函数说明:c.File("文件路径")
  c.File("/var/www/1.jpg")
}

例子2func(c *gin.Context) {
    
    
  //通过FileAttachment函数,返回本地文件,类似File函数,区别是可以指定下载的文件名。
  //函数说明: c.FileAttachment("文件路径", "下载的文件名")
  c.FileAttachment("/var/www/1.jpg", "1.jpg")
}

4.5 c.Header

Set http response header (set Header)

func(c *gin.Context) {
    
    
  //设置http响应 header, key/value方式,支持设置多个header
  c.Header("site","tizi365")
}

5 Gin middleware

In the Gin framework, middleware (Middleware) refers to a special function that can intercept the http request-response life cycle. Multiple middleware can be registered in the request-response life cycle. Each middleware performs a different function. A middleware After execution, it is the turn of the next middleware to execute.

Common application scenarios for middleware are as follows:

  • request speed limit
  • API interface signature processing
  • permission check
  • Unified Error Handling

Tip: If you want to intercept all requests and do something, you can develop a middleware function to achieve it.

Gin supports setting global middleware and setting middleware for routing groups. Setting global middleware means that all requests will be intercepted, and setting middleware for group routing means that it only works on the routes under this group.

5.1 Using middleware

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

	// 通过use设置全局中间件

	// 设置日志中间件,主要用于打印请求日志
	r.Use(gin.Logger())

	// 设置Recovery中间件,主要用于拦截paic错误,不至于导致进程崩掉
	r.Use(gin.Recovery())

	// 忽略后面代码
}

5.2 Custom middleware

Let's use an example to understand how to customize a middleware

package main
// 导入gin包
import (
"github.com/gin-gonic/gin"
	"log"
	"time"
)

// 自定义个日志中间件
func Logger() gin.HandlerFunc {
    
    
	return func(c *gin.Context) {
    
    
		t := time.Now()

		// 可以通过上下文对象,设置一些依附在上下文对象里面的键/值数据
		c.Set("example", "12345")

		// 在这里处理请求到达控制器函数之前的逻辑
     
		// 调用下一个中间件,或者控制器处理函数,具体得看注册了多少个中间件。
		c.Next()

		// 在这里可以处理请求返回给用户之前的逻辑
		latency := time.Since(t)
		log.Print(latency)

		// 例如,查询请求状态吗
		status := c.Writer.Status()
		log.Println(status)
	}
}

func main() {
    
    
	r := gin.New()
	// 注册上面自定义的日志中间件
	r.Use(Logger())

	r.GET("/test", func(c *gin.Context) {
    
    
		// 查询我们之前在日志中间件,注入的键值数据
		example := c.MustGet("example").(string)

		// it would print: "12345"
		log.Println(example)
	})

	// Listen and serve on 0.0.0.0:8080
	r.Run(":8080")
}

6 Execution flowchart

img

Guess you like

Origin blog.csdn.net/qq_42647903/article/details/126121651