为了解决上一章节遗留的疑问:如何捕获返回的错误对象并作自定义的错误处理。 在本章节中,我们将会先简单介绍Web Server的中间件特性,再回答这个疑问。

中间件介绍

中间件是一种拦截器设计,在Web Server中可以拦截请求和返回结果,并在其前后进行自定义处理逻辑。

中间件的定义和普通的路由回调函数一样,但是可以在 Request 参数中使用 Middleware 属性对象来控制请求流程。

中间件的类型分为两种:前置中间件后置中间件。前置即在路由服务函数调用之前调用,后置即在其后调用。

  1. func Middleware(r *ghttp.Request) {
  2. // 前置中间件处理逻辑
  3. r.Middleware.Next()
  4. // 后置中间件处理逻辑
  5. }

在中间件中执行完成处理逻辑后,使用 r.Middleware.Next() 方法进一步执行下一个流程; 如果这个时候直接退出不调用 r.Middleware.Next() 方法的话,将会退出后续的执行流程(例如可以用于请求的鉴权处理)。

使用中间件

我们使用中间件对上一章节的程序进行简单的改造,如下:

main.go

  1. package main
  2. import (
  3. "context"
  4. "github.com/gogf/gf/v2/frame/g"
  5. "github.com/gogf/gf/v2/net/ghttp"
  6. )
  7. type HelloReq struct {
  8. g.Meta `path:"/" method:"get"`
  9. Name string `v:"required" dc:"姓名"`
  10. Age int `v:"required" dc:"年龄"`
  11. }
  12. type HelloRes struct{}
  13. type Hello struct{}
  14. func (Hello) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error) {
  15. r := g.RequestFromCtx(ctx)
  16. r.Response.Writef(
  17. "Hello %s! Your Age is %d",
  18. req.Name,
  19. req.Age,
  20. )
  21. return
  22. }
  23. func ErrorHandler(r *ghttp.Request) {
  24. // 执行路由回调函数
  25. r.Middleware.Next()
  26. // 判断是否产生错误
  27. if err := r.GetError(); err != nil {
  28. r.Response.Write("error occurs: ", err.Error())
  29. return
  30. }
  31. }
  32. func main() {
  33. s := g.Server()
  34. s.Group("/", func(group *ghttp.RouterGroup) {
  35. group.Middleware(ErrorHandler)
  36. group.Bind(
  37. new(Hello),
  38. )
  39. })
  40. s.SetPort(8000)
  41. s.Run()
  42. }
  • 我们定义了一个错误处理的中间件ErrorHandler,在该中间件中我们先通过r.Middleware.Next()执行路由函数流程, 随后通过r.GetError()获取路由回调函数是否有错误产生。如果产生错误,那么直接输出错误信息。
  • 在路由注册中,我们通过group.Middleware(ErrorHandler)给该分组路由下的所有注册的路由,都绑定了错误处理的中间件。

执行结果

运行后,终端输出:

  1. 2024-11-06 22:30:06.927 [INFO] pid[35434]: http server started listening on [:8000]
  2. 2024-11-06 22:30:06.927 [INFO] {905637567a67051830833b2189796dda} openapi specification is disabled
  3. ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE
  4. ----------|--------|-------|-------------------|--------------------
  5. :8000 | GET | / | main.(*Hello).Say | main.ErrorHandler
  6. ----------|--------|-------|-------------------|--------------------

这里的MIDDLEWARE部分多了一个main.ErrorHandler方法,表示该路由绑定的中间件名称。

我们访问 http://127.0.0.1:8000/?name=john&age=18 可以看到,页面输出结果符合预期。

img.png

我们尝试一下错误的参数请求 http://127.0.0.1:8000/ 可以看到,页面输出结果也符合预期。

img_4.png

学习小结

我们使用中间件对请求错误进行自定义的处理,并捕获校验错误返回自定义的错误信息。 可以看到,中间件的功能非常灵活且强大,当然不仅仅局限于处理校验错误这种小场景。

试想一下,假如我们的项目中有很多接口,通常这些接口的输出格式都是固定的,例如都是json格式。 那么能否直接通过中间件的方式,统一返回的数据结构呢?答案当然是肯定的,我们将在下一章节介绍。