背景
前端框架:vue.js,是网上别人已经封装好的模板
后台web框架:gin
后台限制请求方式只能是post,所有接口已经测试通过。前端调试始终提示404.日志打印前端的请求方式是options,而不是post。
此时将后台的请求方式由post修改为options,返回成功。
说明问题点就在请求method这里。但是前端明显有设置method为post,到了发送请求的时候自动变成了options
关键
前端在header里面有自定义字段,那么浏览器会首先发送一个options请求,如果请求通过,才会继续发送正式的post请求。
解决
后台使用gin自带的解决跨域问题的cors中间件,但是前端继续报错,如下:
Request header field system is not allowed by Access-Control-Allow-Headers in preflight response.
后来前端修改了别人模板里的header信息(这里所谓的修改是前端没有在header里面自定义参数,补充于2018-10-10 15:01),再次访问ok。
========================================分界线=====================================================
抱歉,如果你之前看了这个文章,那我可能有点误导你了。这个跨域问题我是今天上午才彻底解决。
必须严肃对待这个问题。
首先看下为什么会有跨域的问题,需要翻墙,如果你无法翻墙,那我等以后哪天有空了再翻译吧。参考链接:跨域
关键点如下:
浏览器分两种请求。简单请求的话就是按照vue框架给你封装的,直接Post简单body不修改它的header。如果修改了header那就是复杂请求。得需要考虑跨域问题。解决跨域的是先发一次options请求,获取allowheader,允许跨域之后才会再发真正的Post请求。(这段话是一位大佬发给我的)
这次这个问题是我需要前端在header里面自定义Token给我,就属于复杂请求了,所以就一定会有跨域问题的发生。
网上说的办法不是不行,都可以解决。举个例子,比如这个大佬的文章:
一般中间件都是在所有请求之前绑定的,大佬这篇文章在中间件里面放行了options请求,我也照做了,但是此时前端调用接口或者自己postman模拟的时候只会返回一个200, options request! 的结果,这显然不是页面需要的数据。以大佬的文章来说,我应该在options request!替换为每次响应的结果而不仅仅是options request这么一句话。因为前端不仅仅需要这个状态码,还需要他每次请求以后服务器响应的结果。那么问题来了,这个中间件是在所有请求之前绑定,这时候都还没有调用接口,哪里有数据返回给前端?
解决关键:AbortWithStatus()
在遇到options请求以后,不需要像大佬文章里的那样返回结果给前端,而应该是调用
c.AbortWithStatus(http.StatusNoContent)
AbortWithStatus()会调用Abort()方法,源码如下,我怕自己翻译会不太准确。
// Abort prevents pending handlers from being called. Note that this will not stop the current handler.
// Let's say you have an authorization middleware that validates that the current request is authorized.
// If the authorization fails (ex: the password does not match), call Abort to ensure the remaining handlers
// for this request are not called.
func (c *Context) Abort() {
c.index = abortIndex
}
// AbortWithStatus calls `Abort()` and writes the headers with the specified status code.
// For example, a failed attempt to authenticate a request could use: context.AbortWithStatus(401).
func (c *Context) AbortWithStatus(code int) {
c.Status(code)
c.Writer.WriteHeaderNow()
c.Abort()
}
回到刚刚的话题,我在调用c.AbortWithStatus()的时候传入的是204,其实个人感觉只要是2开头的状态码,但不要是200都行。等我有空再研究研究
以后postman模拟请求为options的时候,这个请求会暂停,等待用户的下一次post请求来触发接口。前端无需再发送第二次请求即可。
希望对你有帮助。
// 处理跨域请求,支持options访问
func Cors() gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token")
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
c.Header("Access-Control-Allow-Credentials", "true")
//放行所有OPTIONS方法
if method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
}
// 处理请求
c.Next()
}
}