前端使用axios,后端使用go-gin框架开发
请看下列代码
// 在http.js中引入axios
import Vue from "vue";
import axios from "axios"; // 引入axios
axios.defaults.timeout = 15000;
axios.defaults.headers.post["Content-Type"] = "application/json;charset=UTF-8";
axios.defaults.headers.post["Access-Control-Allow-Origin"] = "*";
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method
origin := c.Request.Header.Get("Origin")
if origin != "" {
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization")
c.Header("Access-Control-Allow-Credentials", "true")
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar")
}
if method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
}
c.Next()
}
}
好的,前后端均做跨域处理,但是却依旧请求失败
跨域回顾
浏览器的请求分为,简单请求,非简单请求。简单请求浏览器不会预检,而非简单请求会预检。
简单请求
同时满足下列三大条件,就属于简单请求,否则属于非简单请求
请求方式只能是:GET、POST、HEAD HTTP请求头限制这几种字段:Accept、Accept-Language、Content-Language、Content-Type、Last-Event-ID
Content-type只能取:application/x-www-form-urlencoded、multipart/form-data、text/plain
对于简单请求,浏览器直接请求,会在请求头信息中,增加一个origin字段,来说明本次请求来自哪个源(协议+域名+端口)。服务器根据这个值,来决定是否同意该请求。服务器返回的响应会多几个和跨域相关的头信息字段
- Access-Control-Allow-Origin:该字段是必须的,* 表示接受任意域名的请求,还可以指定域名
- Access-Control-Allow-Credentials:该字段可选,是个布尔值,表示是否可以携带cookie,(注意:如果Access-Control-Allow-Origin字段设置*,此字段设为true无效)
- Access-Control-Allow-Headers:表明服务器允许请求中携带字段 ,如Cache-Control、Content-Type、Expires等
- Access-Control-Max-Age:有效时间,在有效时间内,浏览器无须为同一请求再次发起预检请求
非简单请求
非简单请求是对那种对服务器有特殊要求的请求,比如请求方式是PUT或者DELETE,或者Content-Type字段类型是application/json。都会在正式通信之前,增加一次HTTP请求,称之为预检。
浏览器会先询问服务器,当前网页所在域名是否在服务器的许可名单之中,服务器允许之后,浏览器会发出正式的XMLHttpRequest请求,否则会报错。(备注:之前碰到预检请求后端没有通过,就不会发正式请求)
axios 的处理
经过不断尝试,发现在axios代码中如果去掉
axios.defaults.headers.post["Access-Control-Allow-Origin"] = "*";
这行代码的话,请求可以了。但这样并不满足我们的需要,倘若前端不是我们自己开发的,后端如何处理才能解决这种问题呢?
问题解决
仔细思考后端代码逻辑,做如下修改:
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method
origin := c.Request.Header.Get("Origin")
if origin != "" {
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization")
c.Header("Access-Control-Allow-Credentials", "true")
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers,Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma,FooBar")
}
if method == "OPTIONS" {
/* 添加 */
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Methods", "OPTIONS")
c.Header("Access-Control-Allow-Headers", "*")
/* 添加 */
c.AbortWithStatus(http.StatusNoContent)
}
c.Next()
}
}
在请求是OPTIONS的时候,需要对请求头进行重新赋值,经过测试
c.Header("Access-Control-Allow-Headers", "*") 问题出现在这里,必须填* 否则都会出现上述问题
猜测可能axios或者浏览器内部的bug,前端不设置"Access-Control-Allow-Origin"就没有问题,或者前端设置,但是后端不对"Access-Control-Allow-Origin"做处理,具体不知道他们的交互逻辑是什么,也有人提出类似的问题,但是都没有说明原因,笔者这里也仅仅是猜测。