在前后端分离的开发模式下,跨域问题一直是个老生常谈的问题,一般的解决方案分为前端与后端,前端主要利用jsonp来解决,但是后端解决会更更加方便,本文记录一下使用gin框架时在后端解决跨域问题。

准备前端文件

准备一下用于前端发送http请求的文件,该文件使用jquery来发送ajax,当然也可以使用vue+axios

我这里为了演示,简单的使用python 启了个http服务

python -m SimpleHTTPServer 8888

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<html>
    <head>
        <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
    </head>
    <body>
      <script>
        $(document).ready(function(){
          const url = "http://127.0.0.1:8080/api/v1/test";
          $.ajax({
            url: url,
            type: "GET",
            success:function(result){
              console.log(result)
            },
            error:function(error){
              console.log(error)
            }
          })
        })
      </script>
    </body>
</html>

这时如果在浏览器中打开这个文件,或者使用nginx作为静态代理,只要该文件不是通过http://127.0.0.1:8080 这个域发起的请求,则由于浏览器的同源策略都会被拦截。

Access to XMLHttpRequest at ‘http://127.0.0.1:8080/api/v1/test’ from origin ‘http://127.0.0.1:8888’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

gin后端解决跨域

浏览器在发送http请求的时候,会在header中记录Origin信息

image-20220118234738737

我们先来看下gin是如何获取请求 header 信息的,gin 通过 *gin.Context.Request.Header 来获取到请求头信息

1
2
3
4
func (ApiV1) Get(c *gin.Context)  {
	fmt.Println(c.Request.Header)
	c.JSON(200, gin.H{"msg": "handlers get request!"})
}

想要获取到Origin 信息,Header 本身是个 map[string][]string 类型数据,可能通过Get方法来获取Origin信息

在响应头中将该域添加到 Access-Control-Allow-Origin中,并同时设置 Access-Control-Allow-Methods

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
func (ApiV1) Get(c *gin.Context)  {
	origin := c.Request.Header.Get("Origin") //请求头部
	if origin!=""{
    // 将该域添加到allow-origin中
		c.Header("Access-Control-Allow-Origin", origin)  // 
		c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
		c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization")
		c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type")
    //允许客户端传递校验信息比如 cookie
		c.Header("Access-Control-Allow-Credentials", "true")
	}
	c.JSON(200, gin.H{"msg": "handlers get request!"})
}

这时再访问刚才的html文件就可以正常的请求到了数据。

image-20220118235824264

写成中间件

上面只是针对某一个接口,如果想要所有的接口都可以被跨域请求,则可以将其写成一个中间件,上面是将所有的域都允许跨域请求,其实还是有点问题的,这里我将允许跨域请求的地址写到一个map中,本来想写到数组或者slice中,但是发现在判断是否存在的时候,golang中并没有in 运算符,需要遍历数组或者slice,复杂度为O(n) ,还是使用map吧。

另外如果请求方法是OPTIONS 的话,如websocket请求会先发一个OPTIONS请求,这时可以不进行校验,直接返回

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package midwares

import (
	"github.com/gin-gonic/gin"
)

func CheckCors() gin.HandlerFunc {
	//这里可以处理一些别的逻辑
	return func(c *gin.Context) {
		// 定义一个origin的map,只有在字典中的key才允许跨域请求
		var allowOrigins = map[string]struct{}{
			"http://127.0.0.1:8888": struct {}{},
			"https://www.yangyanxing.com": struct {}{},
		}
		origin := c.Request.Header.Get("Origin") //请求头部
    method := c.Request.Method
		if method == "OPTIONS"{
			c.AbortWithStatus(http.StatusNoContent)
		}
		if origin!=""{
			if _, ok:=allowOrigins[origin];ok{
				c.Header("Access-Control-Allow-Origin", origin)
				c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
				c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization")
				c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type")
				c.Header("Access-Control-Allow-Credentials", "true")
			}
		}
		c.Next()
	}
}

在main.go中加入该中间件

1
2
3
4
5
func main() {
	r := gin.Default()
	r.Use(midwares.CheckCors())
  ....
  r.Run(":8080")

之后就可以完成跨域请求了。

参考文章

GIN框架解决跨域问题