允许跨域请求

第一个例子,也是比较常见的功能需求。

我们需要在所有API请求之前增加允许跨域请求的返回 Header 信息,该功能可以通过中间件实现:

  1. package main
  2. import (
  3. "github.com/gogf/gf/v2/frame/g"
  4. "github.com/gogf/gf/v2/net/ghttp"
  5. )
  6. func MiddlewareCORS(r *ghttp.Request) {
  7. r.Response.CORSDefault()
  8. r.Middleware.Next()
  9. }
  10. func main() {
  11. s := g.Server()
  12. s.Group("/api.v2", func(group *ghttp.RouterGroup) {
  13. group.Middleware(MiddlewareCORS)
  14. group.ALL("/user/list", func(r *ghttp.Request) {
  15. r.Response.Writeln("list")
  16. })
  17. })
  18. s.SetPort(8199)
  19. s.Run()
  20. }

执行后,终端打印出路由表信息如下:

  1. SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE
  2. |---------|---------|---------|--------|-------------------|-------------------|---------------------|
  3. default | default | :8199 | ALL | /api.v2/user/list | main.main.func1.1 | main.MiddlewareCORS
  4. |---------|---------|---------|--------|-------------------|-------------------|---------------------|

这里我们使用 group.Middleware(MiddlewareCORS) 将跨域中间件通过分组路由的形式注册绑定到了 /api.v2 路由下所有的服务函数中。随后我们可以通过请求 http://127.0.0.1:8199/api.v2/user/list 来查看允许跨域请求的 Header 信息是否有返回。

中间件/拦截器-使用示例 - 图1

请求鉴权处理

我们在跨域请求中间件的基础之上加上鉴权中间件。

为了简化示例,在该示例中,当请求带有 token 参数,并且参数值为 123456 时可以通过鉴权,并且允许跨域请求,执行请求方法;否则返回 403 Forbidden 状态码。

  1. package main
  2. import (
  3. "net/http"
  4. "github.com/gogf/gf/v2/frame/g"
  5. "github.com/gogf/gf/v2/net/ghttp"
  6. )
  7. func MiddlewareAuth(r *ghttp.Request) {
  8. token := r.Get("token")
  9. if token.String() == "123456" {
  10. r.Response.Writeln("auth")
  11. r.Middleware.Next()
  12. } else {
  13. r.Response.WriteStatus(http.StatusForbidden)
  14. }
  15. }
  16. func MiddlewareCORS(r *ghttp.Request) {
  17. r.Response.Writeln("cors")
  18. r.Response.CORSDefault()
  19. r.Middleware.Next()
  20. }
  21. func main() {
  22. s := g.Server()
  23. s.Group("/api.v2", func(group *ghttp.RouterGroup) {
  24. group.Middleware(MiddlewareCORS, MiddlewareAuth)
  25. group.ALL("/user/list", func(r *ghttp.Request) {
  26. r.Response.Writeln("list")
  27. })
  28. })
  29. s.SetPort(8199)
  30. s.Run()
  31. }

执行后,终端打印出路由表信息如下:

  1. SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE
  2. |---------|---------|---------|--------|-------------------|-------------------|-----------------------------------------|
  3. default | default | :8199 | ALL | /api.v2/user/list | main.main.func1.1 | main.MiddlewareCORS,main.MiddlewareAuth
  4. |---------|---------|---------|--------|-------------------|-------------------|-----------------------------------------|

可以看到,我们的服务方法绑定了两个中间件,跨域中间件和而鉴权中间件。 请求时将会按照中间件注册的先后顺序,先执行 MiddlewareCORS 全局中间件,再执行 MiddlewareAuth 分组中间件。 随后我们可以通过请求 http://127.0.0.1:8199/api.v2/user/listhttp://127.0.0.1:8199/api.v2/user/list?token=123456 对比来查看效果。

中间件/拦截器-使用示例 - 图2

中间件/拦截器-使用示例 - 图3

鉴权例外处理

使用分组路由中间件可以很方便地添加鉴权例外,因为只有当前分组路由下注册的服务方法才会绑定并执行鉴权中间件,否则并不会执行到鉴权中间件。

  1. package main
  2. import (
  3. "net/http"
  4. "github.com/gogf/gf/v2/frame/g"
  5. "github.com/gogf/gf/v2/net/ghttp"
  6. )
  7. func MiddlewareAuth(r *ghttp.Request) {
  8. token := r.Get("token")
  9. if token.String() == "123456" {
  10. r.Middleware.Next()
  11. } else {
  12. r.Response.WriteStatus(http.StatusForbidden)
  13. }
  14. }
  15. func main() {
  16. s := g.Server()
  17. s.Group("/admin", func(group *ghttp.RouterGroup) {
  18. group.ALL("/login", func(r *ghttp.Request) {
  19. r.Response.Writeln("login")
  20. })
  21. group.Group("/", func(group *ghttp.RouterGroup) {
  22. group.Middleware(MiddlewareAuth)
  23. group.ALL("/dashboard", func(r *ghttp.Request) {
  24. r.Response.Writeln("dashboard")
  25. })
  26. })
  27. })
  28. s.SetPort(8199)
  29. s.Run()
  30. }

执行后,终端打印出路由表信息如下:

  1. SERVER | ADDRESS | DOMAIN | METHOD | P | ROUTE | HANDLER | MIDDLEWARE
  2. |---------|---------|---------|--------|---|------------------|---------------------|---------------------|
  3. default | :8199 | default | ALL | 2 | /admin/dashboard | main.main.func1.2.1 | main.MiddlewareAuth
  4. |---------|---------|---------|--------|---|------------------|---------------------|---------------------|
  5. default | :8199 | default | ALL | 2 | /admin/login | main.main.func1.1 |
  6. |---------|---------|---------|--------|---|------------------|---------------------|---------------------|

可以看到,只有 /admin/dashboard 路由的服务方法绑定了鉴权中间件 main.MiddlewareAuth,而 /admin/login 路由的服务方法并没有添加鉴权处理。 随后我们访问以下URL查看效果:

  1. http://127.0.0.1:8199/admin/login
  2. http://127.0.0.1:8199/admin/dashboard
  3. http://127.0.0.1:8199/admin/dashboard?token=123456

中间件/拦截器-使用示例 - 图4

中间件/拦截器-使用示例 - 图5

中间件/拦截器-使用示例 - 图6

统一的错误处理

基于中间件,我们可以在服务函数执行完成后做一些后置判断的工作,特别是统一数据格式返回、结果处理、错误判断等等。这种需求我们可以使用后置的中间件类型来实现。我们使用一个简单的例子,用来演示如何使用中间件对所有的接口请求做后置判断处理,作为一个抛砖引玉作用。

  1. package main
  2. import (
  3. "net/http"
  4. "github.com/gogf/gf/v2/frame/g"
  5. "github.com/gogf/gf/v2/net/ghttp"
  6. )
  7. func MiddlewareAuth(r *ghttp.Request) {
  8. token := r.Get("token")
  9. if token.String() == "123456" {
  10. r.Middleware.Next()
  11. } else {
  12. r.Response.WriteStatus(http.StatusForbidden)
  13. }
  14. }
  15. func MiddlewareCORS(r *ghttp.Request) {
  16. r.Response.CORSDefault()
  17. r.Middleware.Next()
  18. }
  19. func MiddlewareErrorHandler(r *ghttp.Request) {
  20. r.Middleware.Next()
  21. if r.Response.Status >= http.StatusInternalServerError {
  22. r.Response.ClearBuffer()
  23. r.Response.Writeln("哎哟我去,服务器居然开小差了,请稍后再试吧!")
  24. }
  25. }
  26. func main() {
  27. s := g.Server()
  28. s.Use(MiddlewareCORS)
  29. s.Group("/api.v2", func(group *ghttp.RouterGroup) {
  30. group.Middleware(MiddlewareAuth, MiddlewareErrorHandler)
  31. group.ALL("/user/list", func(r *ghttp.Request) {
  32. panic("db error: sql is xxxxxxx")
  33. })
  34. })
  35. s.SetPort(8199)
  36. s.Run()
  37. }

执行后,终端打印出路由表信息如下:

  1. SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE
  2. |---------|---------|---------|--------|-------------------|---------------------|-------------------------------------------------|
  3. default | default | :8199 | ALL | /* | main.MiddlewareCORS | GLOBAL MIDDLEWARE
  4. |---------|---------|---------|--------|-------------------|---------------------|-------------------------------------------------|
  5. default | default | :8199 | ALL | /api.v2/user/list | main.main.func1.1 | main.MiddlewareAuth,main.MiddlewareErrorHandler
  6. |---------|---------|---------|--------|-------------------|---------------------|-------------------------------------------------|

在该示例中,我们在后置中间件中判断有无系统错误,如果有则返回固定的提示信息,而不是把敏感的报错信息展示给用户。当然,在真实的项目场景中,往往还有是需要解析返回缓冲区的数据,例如 JSON 数据,根据当前的执行结果进行封装返回固定的数据格式等等。

执行该示例后,访问 http://127.0.0.1:8199/api.v2/user/list?token=123456 查看效果。

中间件/拦截器-使用示例 - 图7

自定义日志处理

我们来更进一步完善一下以上示例,我们将请求日志包括状态码输出到终端。这里我们必须得使用”全局中间件”了,这样可以拦截处理到所有的服务请求,甚至 404 请求。

  1. package main
  2. import (
  3. "net/http"
  4. "github.com/gogf/gf/v2/frame/g"
  5. "github.com/gogf/gf/v2/net/ghttp"
  6. )
  7. func MiddlewareAuth(r *ghttp.Request) {
  8. token := r.Get("token")
  9. if token.String() == "123456" {
  10. r.Middleware.Next()
  11. } else {
  12. r.Response.WriteStatus(http.StatusForbidden)
  13. }
  14. }
  15. func MiddlewareCORS(r *ghttp.Request) {
  16. r.Response.CORSDefault()
  17. r.Middleware.Next()
  18. }
  19. func MiddlewareLog(r *ghttp.Request) {
  20. r.Middleware.Next()
  21. errStr := ""
  22. if err := r.GetError(); err != nil {
  23. errStr = err.Error()
  24. }
  25. g.Log().Println(r.Response.Status, r.URL.Path, errStr)
  26. }
  27. func main() {
  28. s := g.Server()
  29. s.SetConfigWithMap(g.Map{
  30. "AccessLogEnabled": false,
  31. "ErrorLogEnabled": false,
  32. })
  33. s.Use(MiddlewareLog, MiddlewareCORS)
  34. s.Group("/api.v2", func(group *ghttp.RouterGroup) {
  35. group.Middleware(MiddlewareAuth)
  36. group.ALL("/user/list", func(r *ghttp.Request) {
  37. panic("啊!我出错了!")
  38. })
  39. })
  40. s.SetPort(8199)
  41. s.Run()
  42. }

中间件/拦截器-使用示例 - 图8

中间件/拦截器-使用示例 - 图9

可以看到,我们注册了一个全局的日志处理中间件以及跨域中间件,而鉴权中间件是注册到 /api.v2 路由下。

执行后,我们可以通过请求 http://127.0.0.1:8199/api.v2/user/listhttp://127.0.0.1:8199/api.v2/user/list?token=123456 对比来查看效果,并查看终端的日志输出情况。