关于post请求产生的preflight request小记
关于post请求产生的preflight request小记
首先感谢几篇文章的大佬,学习到很多
- 记一次跨域post请求数据之preflight request
- 跨域请求出现preflight request失败的问题的解决
- 为什么axios先请求options在请求post及解决方法
以上文中,大佬们已经总结了很多也很详细,以下是个人的一些小结
一、问题背景
本地启动的前端vue项目使用axios发送post请求去获取一个展示列表,对于可能会产生的跨域请求后端项目中已经配置了如下
1
2 3 4 |
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"); |
前端请求为
1
2 3 4 5 6 7 |
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属性
1
2 3 4 5 6 7 8 9 10 11 |
axios({
method: 'post', url: myUrl, params: { token: myToken, }, header: { 'Content-Type': 'application/x-www-form-urlencoded', } }) // 我自己本地测试,改成这样后,已经可以正常获取接口数据了 |
4.2 方法二
使用FormData参数格式 + axios的data字段
1
2 3 4 5 6 7 |
let formData = new FormData();
formData.append('token', myToken); axios({ method: 'post', url: myUrl, data: formData, }) |
使用formData发送请求时,axios不会预先发送option请求,直接只有一个post请求
4.3 方法三
使用axios的params字段
1
2 3 4 5 6 7 |
axios({
method: 'post', url: myUrl, params: { token: myToken, }, }) |
使用params字段发送请求时,axios不会预先发送option请求,直接只有一个post请求
补充:如果axios用post请求且直接使用data字段,浏览器会报preflight request错误,而且浏览器会先发送一个option请求
用心写代码,不辜负程序员之名。