允许接口跨域往往是需要结合 路由管理-中间件/拦截器 一起使用,来统一设置某些路由规则下的接口可以跨域访问。同时,针对允许 WebSocket 的跨域请求访问,也是通过该方式来实现。

相关方法: https://pkg.go.dev/github.com/gogf/gf/v2/net/ghttp#Response

  1. func (r *Response) CORS(options CORSOptions)
  2. func (r *Response) CORSAllowedOrigin(options CORSOptions) bool
  3. func (r *Response) CORSDefault()
  4. func (r *Response) DefaultCORSOptions() CORSOptions

CORS 对象

CORSW3 互联网标准组织对HTTP跨域请求的标准,在 ghttp 模块中,我们可以通过 CORSOptions 对象来管理对应的跨域请求选项。定义如下:

  1. // See https://www.w3.org/TR/cors/ .
  2. // 服务端允许跨域请求选项
  3. type CORSOptions struct {
  4. AllowDomain []string // Used for allowing requests from custom domains
  5. AllowOrigin string // Access-Control-Allow-Origin
  6. AllowCredentials string // Access-Control-Allow-Credentials
  7. ExposeHeaders string // Access-Control-Expose-Headers
  8. MaxAge int // Access-Control-Max-Age
  9. AllowMethods string // Access-Control-Allow-Methods
  10. AllowHeaders string // Access-Control-Allow-Headers
  11. }

具体参数的介绍请查看W3组织 官网手册

CORS 配置

默认 CORSOptions

当然,为方便跨域设置,在 ghttp 模块中也提供了默认的跨域请求选项,通过 DefaultCORSOptions 方法获取。大多数情况下,我们在需要允许跨域请求的接口中(一般情况下使用中间件)可以直接使用 CORSDefault() 允许该接口跨域访问。

限制 Origin 来源

大多数时候,我们需要限制请求来源为我们受信任的域名列表,我们可以使用 AllowDomain 配置,使用方式:

  1. // 允许跨域请求中间件
  2. func Middleware(r *ghttp.Request) {
  3. corsOptions := r.Response.DefaultCORSOptions()
  4. corsOptions.AllowDomain = []string{"goframe.org", "johng.cn"}
  5. r.Response.CORS(corsOptions)
  6. r.Middleware.Next()
  7. }

OPTIONS 请求

有的客户端,部分浏览器在发送 AJAX 请求之前会首先发送 OPTIONS 预请求检测后续请求是否允许发送。 GoFrame 框架的 Server 完全遵守 W3C 关于 OPTIONS 请求方法的规范约定,因此只要服务端设置好 CORS 中间件, OPTIONS 请求也将会自动支持。

示例1,基本使用

我们来看一个简单的接口示例:

  1. package main
  2. import (
  3. "github.com/gogf/gf/v2/frame/g"
  4. "github.com/gogf/gf/v2/net/ghttp"
  5. )
  6. func Order(r *ghttp.Request) {
  7. r.Response.Write("GET")
  8. }
  9. func main() {
  10. s := g.Server()
  11. s.Group("/api.v1", func(group *ghttp.RouterGroup) {
  12. group.GET("/order", Order)
  13. })
  14. s.SetPort(8199)
  15. s.Run()
  16. }

接口地址是 http://localhost/api.v1/order ,当然这个接口是不允许跨域的。我们打开一个不同的域名,例如:百度首页(正好用了 jQuery,方便调试),然后按 F12 打开开发者面板,在 console 下执行以下 AJAX 请求:

  1. $.get("http://localhost:8199/api.v1/order", function(result){
  2. console.log(result)
  3. });

结果如下:

CORS跨域处理 - 图1

返回了不允许跨域请求的提示错误,接着我们修改一下服务端接口的测试代码,如下:

  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 Order(r *ghttp.Request) {
  11. r.Response.Write("GET")
  12. }
  13. func main() {
  14. s := g.Server()
  15. s.Group("/api.v1", func(group *ghttp.RouterGroup) {
  16. group.Middleware(MiddlewareCORS)
  17. group.GET("/order", Order)
  18. })
  19. s.SetPort(8199)
  20. s.Run()
  21. }

我们增加了针对于路由 /api.v1 的前置中间件 MiddlewareCORS,该事件将会在所有服务执行之前调用。我们通过调用 CORSDefault 方法使用默认的跨域设置允许跨域请求。该绑定的事件路由规则使用了模糊匹配规则,表示所有 /api.v1 开头的接口地址都允许跨域请求。

返回刚才的百度首页,再次执行请求 AJAX 请求,这次便成功了:

CORS跨域处理 - 图2

当然我们也可以通过 CORSOptions 对象以及 CORS 方法对跨域请求做更多的设置。

示例2,授权跨域 Origin

在大多数场景中,我们是需要自定义授权跨域的 Origin,那么我们可以将以上的例子改进如下,在该示例中,我们仅允许 goframe.orgbaidu.com 跨域请求访问。

  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. corsOptions := r.Response.DefaultCORSOptions()
  8. corsOptions.AllowDomain = []string{"goframe.org", "baidu.com"}
  9. r.Response.CORS(corsOptions)
  10. r.Middleware.Next()
  11. }
  12. func Order(r *ghttp.Request) {
  13. r.Response.Write("GET")
  14. }
  15. func main() {
  16. s := g.Server()
  17. s.Group("/api.v1", func(group *ghttp.RouterGroup) {
  18. group.Middleware(MiddlewareCORS)
  19. group.GET("/order", Order)
  20. })
  21. s.SetPort(8199)
  22. s.Run()
  23. }

示例3,自定义检测授权

不知大家是否有注意,在以上的示例中有个细节,即使当前接口不允许跨域访问,但是只要接口被调用,接口完整逻辑仍会被执行,在服务端其实也已经走完了一次请求流程。针对于这个问题,我们可以通过自定义授权 Origin 并在中间件中通过 CORSAllowedOrigin 方法来做判断,如果当前请求的 Origin 在服务端是被允许执行的,那么才会执行后续流程,否则便会终止执行。

在以下示例中,仅允许来自 goframe.org 域名的跨域请求,而来自其他域名的请求将会失败并返回 403

  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. corsOptions := r.Response.DefaultCORSOptions()
  8. corsOptions.AllowDomain = []string{"goframe.org"}
  9. if !r.Response.CORSAllowedOrigin(corsOptions) {
  10. r.Response.WriteStatus(http.StatusForbidden)
  11. return
  12. }
  13. r.Response.CORS(corsOptions)
  14. r.Middleware.Next()
  15. }
  16. func Order(r *ghttp.Request) {
  17. r.Response.Write("GET")
  18. }
  19. func main() {
  20. s := g.Server()
  21. s.Group("/api.v1", func(group *ghttp.RouterGroup) {
  22. group.Middleware(MiddlewareCORS)
  23. group.GET("/order", Order)
  24. })
  25. s.SetPort(8199)
  26. s.Run()
  27. }