28

golang web框架——gin使用教程(二)

 4 years ago
source link: https://www.tuicool.com/articles/uUNnUrM
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

gin使用教程(一)

gin使用教程(二)

上篇教程主要讲了gin的路由以及参数获取,这篇主要讲解gin的中间件。

中间件可以在我们接受到一个http请求时,在handle之前或者handle之后做一些处理。通常,在handle之前,我们可以通过中间件很方便地进行校验,如果再handle之后,我们可以对response进行一些调整。

基本用法

使用

// 创建一个不包含中间件的路由器
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中间件可以让我们做打印的一些自定义配置
  • Recovery中间件可以让我们从崩溃中恢复
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中间件是如何实现的即可。

// 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
复制代码

Group中使用中间件

此外,我们的中间件可以不全局使用,而只针对部分的group:

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")
  
    //你可以写一些逻辑代码
}
复制代码

可以看出,中间件的写法和路由的 Handler 几乎是一样的,只是多调用 c.Next() 。正是有个 c.Next() ,我们可以在中间件中控制调用逻辑的变化,看下面的 middleware2 代码。在 middleware2中,执行到  c.Next() 时,Gin 会直接跳到流程的下一个方法中,等到这个方法执行完后,才会回来接着执行 middleware2 剩下的代码。

所以请求上面注册的路由 url /someGet ,请求先到达middleware1,然后到达 middleware2,但此时 middleware2调用了 c.Next() ,所以 middleware2的代码并没有执行,而是跳到了 handler ,等 handler执行完成后,跳回到 middleware2,执行 middleware2剩下的代码。

所以我们可以在控制台上看到以下日志输出:

exec middleware1
arrive at middleware2
exec handler
exec middleware2
复制代码

在中间件中使用goroutines

在中间件或处理程序中启动新的goroutine时,你不应该使用其中的原始上下文,你必须使用只读副本( 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")
}
复制代码

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK