веб-фреймворк golang — руководство по использованию gin (2)

Go

руководство по использованию джина (1)
руководство по использованию джина (2)

В последнем уроке в основном говорилось о маршрутизации и получении параметров gin, а в этой статье в основном рассказывается о промежуточном программном обеспечении gin.
Промежуточное ПО может выполнять некоторую обработку до или после обработки, когда мы получаем http-запрос. Обычно до хендла мы легко можем проверить через мидлвар, если после хендла, то можем внести какие-то коррективы в ответ.

Основное использование

использовать

// 创建一个不包含中间件的路由器
gin.New()
// 使用自定义中间件或者gin提供的中间件
gin.use(gin.Logger())

заменять

gin.Default()

На самом деле, gin по умолчанию использует ПО промежуточного слоя Logger и Recovery, а затем внутри себя вызывает New:

// gin.go

func Default() *Engine {
	debugPrintWARNINGDefault()
	engine := New()
	engine.Use(Logger(), Recovery()) // 使用了Logger和Recovery两个中间件
	return engine
}

Давайте кратко разберемся с этими двумя промежуточными программами:

  • Промежуточное программное обеспечение Logger позволяет нам выполнять некоторые пользовательские настройки для печати.
  • Промежуточное ПО для восстановления позволяет нам восстанавливаться после сбоев
func main() {

	logfile, _ := os.Create("./logs/gin.log")
    
    // 这里将log输出到指定文件
    // 注意这个配置一定要在gin.Default()之前
	gin.DefaultWriter = io.MultiWriter(logfile, os.Stdout)

	router := gin.Default()

    // 这里分别使用两个中间件
    router.Use(gin.Logger())
	router.Use(gin.Recovery())

	router.POST("/test", func(context *gin.Context) {
		var person Person
		if err := context.ShouldBind(&person); err != nil {
			context.JSON(http.StatusBadRequest, gin.H{
				"error": err.Error(),
			})
			return
		}
		context.JSON(http.StatusOK, gin.H{
			"success": true,
		})
	})

	router.Run(":3000")
}

пользовательское промежуточное ПО

Чтобы реализовать промежуточное программное обеспечение самостоятельно, вы также можете взглянуть на то, как реализовано официально определенное промежуточное программное обеспечение восстановления.

// recovery.go

这里只要返回一个HandlerFunc类型即可
func Recovery() HandlerFunc {
	return RecoveryWithWriter(DefaultErrorWriter)
}


// gin.go

HandlerFunc就是一个参数为*context的函数
type HandlerFunc func(*Context)

Поняв общую идею промежуточного программного обеспечения, мы можем вручную реализовать его самостоятельно.
Давайте напишем промежуточное программное обеспечение для проверки подлинности IP Предположим, что наше требование состоит в том, что только IP-адреса из белого списка могут получить доступ к серверу, тогда мы можем добиться этого:

// ipauth.go

func Auth() gin.HandlerFunc {
	return func(context *gin.Context) {
        // 定义ip白名单
		whiteList := []string{
			"127.0.0.1",
		}

		ip := context.ClientIP()

		flag := false

		for _, host := range whiteList {
			if ip == host {
				flag = true
				break
			}
		}

		if !flag {
			context.String(http.StatusNetworkAuthenticationRequired, "your ip is not trusted: %s", ip)
			context.Abort()
		}

	}
}
// main.go

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

	router.Use(ipauth.Auth())

	router.GET("/test", func(context *gin.Context) {
		context.JSON(http.StatusOK, gin.H{
			"success": true,
		})
	})

	router.Run(":3000")
}

Пример теста:

// 如果你用localhost访问ip会显示为::1。
// 导致your ip is not trusted。这是因为你的电脑开启了ipv6支持,这是ipv6下的本地回环地址的表示。
$ curl http://127.0.0.1:3000/test
{"success":true}
// 把whiteList中的127.0.0.1改成127.0.0.2之后,我们再试一下
$ curl http://127.0.0.1:3000/test
your ip is not trusted: 127.0.0.1

Использовать промежуточное ПО в группе

Кроме того, наш middleware можно использовать не глобально, а только для части группы:

func main() {

	router := gin.Default()

  // 定义了group
	authorized := router.Group("/auth", ipauth.Auth())

  // 对上面这个group进行路由绑定
	authorized.GET("/write", handle)

	router.GET("/read", handle)

	router.Run(":3000")
}

func handle(context *gin.Context) {
	context.JSON(http.StatusOK, gin.H{
		"success": true,
	})
}

прецедент

$ curl http://127.0.0.1:3000/auth/write
your ip is not trusted: 127.0.0.1
$ curl http://127.0.0.1:3000/read
{"success":true}

Используйте промежуточное ПО для одного маршрута

Или только для одного маршрута:

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

    // 注册一个路由,使用了 middleware1,middleware2 两个中间件
    router.GET("/someGet", middleware1, middleware2, handler)
  
    // 默认绑定 :8080
    router.Run()
}

func handler(c *gin.Context) {
    log.Println("exec handler")
}

func middleware1(c *gin.Context) {
    log.Println("exec middleware1")
  
    //你可以写一些逻辑代码
  
    // 执行该中间件之后的逻辑
    c.Next()
}

func middleware2(c *gin.Context) {
    log.Println("arrive at middleware2")
    // 执行该中间件之前,先跳到流程的下一个方法
    c.Next()
    // 流程中的其他逻辑已经执行完了
    log.Println("exec middleware2")
  
    //你可以写一些逻辑代码
}

Видно, что middleware пишется почти так же, как и обработчик маршрутизации, но вызывается чаще.c.Next(). Eстьc.Next(), мы можем контролировать изменение логики вызова в промежуточном программном обеспечении, см. код промежуточного программного обеспечения2 ниже. В промежуточном программном обеспечении2 выполните дляc.Next(), Gin сразу перейдет к следующему методу процесса, и после выполнения этого метода вернется и выполнит оставшийся код промежуточного ПО2.

Поэтому запросите URL-адрес маршрута /someGet, зарегистрированный выше, запрос сначала достигает промежуточного ПО1, а затем достигает промежуточного ПО2, но в это время вызывается промежуточное ПО2.c.Next(), поэтому код промежуточного ПО2 не выполняется, а переходит к обработчику.После выполнения обработчика он возвращается к промежуточному ПО2 и выполняет оставшийся код промежуточного ПО2.

Таким образом, мы можем увидеть следующий вывод журнала в консоли:

exec middleware1
arrive at middleware2
exec handler
exec middleware2

Использование горутин в промежуточном программном обеспечении

При запуске новой горутины в промежуточном программном обеспечении или обработчике не следует использовать в ней исходный контекст, необходимо использовать копию только для чтения (c.Copy())

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

	r.GET("/long_async", func(c *gin.Context) {
		// 创建要在goroutine中使用的副本
		cCp := c.Copy()
		go func() {
			// simulate a long task with time.Sleep(). 5 seconds
			time.Sleep(5 * time.Second)

			// 这里使用你创建的副本
			log.Println("Done! in path " + cCp.Request.URL.Path)
		}()
	})

	r.Run(":3000")
}