首先感谢几篇文章的大佬,学习到很多😀
以上文中,大佬们已经总结了很多也很详细,以下是个人的一些小结
一、问题背景
本地启动的前端vue项目使用axios发送post请求去获取一个展示列表,对于可能会产生的跨域请求后端项目中已经配置了如下
response.setHeader("Access-Control-Allow-Origin",request.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Methods", "*");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Headers", "Authorization,Origin, X-Requested-With, Content-Type, Accept,Access-Token");
前端请求为
axios.post(myUrl, {
token: myToken,
}).then( res => {
// do sth success
}).catch( err => {
// do sth error
})
然后浏览器控制台显示为

二、初步分析
首先看到报错中最熟悉的关键字CORS,初步判定是跨域问题,但后端已经配置了处理跨域,所以需要把跨域问题排除掉,接着看报错的文字Redirect is not allowed for a preflight request.,其中有个关键字应该是preflight request,普通的跨域报错好像没有这个,接下来就可以去看文章开头大佬们的解析了,分析前首先也要明确几点:
- axios使用post请求时,会默认先发送一个option请求
- axios使用post请求时,默认的
Content-Type是application/json- 使用
FormData格式的参数作为post请求的参数时,不会出现跨域问题和preflight request报错
2.1 请求类别
- 简单请求
- 非简单请求
2.1.1简单请求
满足以下两个条件
- 请求方法是以下三种方法之一:
- HEAD
- GET
- POST
- Content-Type: (仅当POST方法的Content-Type值等于下列之一才算做简单需求)
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
对于简单请求,浏览器直接发出CORS请求。
- Access-Control-Allow-Origin: 该字段是必须的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。
- Access-Control-Allow-Credentials: 该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送Cookie,删除该字段即可。
- Access-Control-Expose-Headers: 该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。
2.1.2非简单请求
- 请求方法不是GET/HEAD/POST
- POST请求的Content-Type并非application/x-www-form-urlencoded, multipart/form-data, 或text/plain
- 请求设置了自定义的header字段
- 非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json
- 非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)
- "预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源
- 浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错
- 除了Origin字段,"预检"请求的头信息包括两个特殊字段:
- Access-Control-Request-Method:该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法
- Access-Control-Request-Headers:该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段
2.1.3为什么要发预检请求
preflight request是为确保服务器是否允许发起对服务器数据产生副作用的HTTP请求方法,而预先由浏览器发起OPTIONS方法的一个预检请求,如果允许就发送真实的请求,如果不允许则直接拒绝发起真实请求。
三、问题回溯
- 使用axios的post请求,且
Content-Type默认是application/json格式 - 由于是非简单请求,所以浏览器会先发一个option请求,虽然后端已设置解决跨域,但出现
preflight request报错
四、尝试解决
4.1 方法一
在发送axios发送post请求时,配置header属性 + axios的data字段
注意:这个配置
application/x-www-form-urlencoded后,浏览器不会报错preflight request,但后端可能会获取不到数据,因为虽然格式是application/x-www-form-urlencoded,但发送的参数还是JSON字符串如果要解决这个问题居然又想到了之前自己发的一篇文,-_-||,传送门: 【爬坑日记】利用axios进行post提交formdata
🚀进阶版:可以再加上qs转换下即可
// 此时只解决preflight request报错
axios({
method: 'post',
url: myUrl,
data: {
token: myToken,
},
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
}
})
4.2 方法二
使用FormData参数格式 + axios的data字段
let formData = new FormData();
formData.append('token', myToken);
axios({
method: 'post',
url: myUrl,
data: formData,
})
使用formData发送请求时,axios不会预先发送option请求,直接只有一个post请求
4.3 方法三
直接使用axios的params字段
axios({
method: 'post',
url: myUrl,
params: {
token: myToken,
},
})
使用params字段发送请求时,axios不会预先发送option请求,直接只有一个post请求
补充:如果axios用post请求且直接使用data字段,浏览器会报
preflight request错误,而且浏览器会先发送一个option请求
五、个人对axios的post请求总结
最终看几种解决方法,个人认为都是以使用key/value格式的参数为最终目的
- 当使用方法三时,虽然未设置
Content-Type为application/x-www-form-urlencoded,但参数格式为key/value
- 当使用方法二时,是标准的formData的
key/value格式
- 当使用方法一的普通版时,虽然浏览器不报
preflight request错误了,但能看见参数还不是key/value格式
- 当使用方法一的进阶版时,参数已经是
key/value格式,但发现和方法二还是有细微差别
六、由个人总结引申出来的对application/x-www-form-urlencoded和multipart/form-data疑惑
参考于:www.cnblogs.com/dd86/p/1119…
form的不同的编码方式
-
form的enctype属性为编码方式,常用有两种:application/x-www-form-urlencoded和multipart/form-data,默认为application/x-www-form-urlencoded。
-
当数据传递的方式是get的时候,浏览器使用application/x-www-form-urlencode的编码方式,把form数据转换为一个字符串,如(name=zhangsan&age=128),然后把这个字符添加到url后面,用?连接 组成新的url并加载
-
当数据传递的方式是post的时候 浏览器把form数据封装到http body中,然后发送到server。 如果没有type=file的控件,用默认的application/x-www-form-urlencoded就可以了。 但是如果有type=file的话,就要用到multipart/form-data了。浏览器会把整个表单以控件为单位分割,并为每个部分加上Content-Disposition(form-data或者file),Content-Type(默认为text/plain),name(控件name)等信息,并加上分割符(boundary)。