6

使用gin搭建api后台系统之跨域问题

 2 years ago
source link: https://www.yangyanxing.com/article/use-go-gin-cors.html
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.
neoserver,ios ssh client

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

准备前端文件

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

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

python -m SimpleHTTPServer 8888

<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 来获取到请求头信息

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

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请求,这时可以不进行校验,直接返回

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中加入该中间件

func main() {
	r := gin.Default()
	r.Use(midwares.CheckCors())
  ....
  r.Run(":8080")

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

GIN框架解决跨域问题


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK