问题
今天在联调接口的时候,发现了一个奇怪的错误Request header field Authorization is not allowed by Access-Control-Allow-Headers in preflight response
OPTIONS
请求,获得浏览器支持的请求类型,那么何时会进行预检请求呢?
只有在浏览器发送非简单请求的情况下,才会发送预检请求
那么什么样叫做非简单请求,什么又是简单请求呢?
只要同时满足以下两大条件,就属于简单请求。
- 请求方法是以下三种方法之一:
- HEAD
- GET
- POST
- HTTP的头信息不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
凡是不同时满足上面两个条件,就属于非简单请求。
这是该问题所引出的相关名词,本着好奇的原则,继续往下走...
跨域
跨域是针对浏览器端而产生的,服务器端的通信并不会产生这样的问题,这就引出了第一个解决跨域的思路----代理服务器(浏览器请求本地起的服务器—同源服务器,再由后者请求外部服务)并且跨域并非浏览器限制了发起跨站请求,而是跨站请求可以正常发起,但是返回结果被浏览器拦截了。
代理服务器
我们利用node可以实现代理服务器,笔者是利用了http-proxy-middleware
var proxy = require('http-proxy-middleware');
var apiProxy = proxy('/api', {target: 'http://www.example.org', changeOrigin: true});
复制代码
在这里changeOrigin: true
引起了我的兴趣,思考为什么会存在该项配置?这里提到了虚拟主机与独立主机的概念
在我们ping baidu.com的时候会得到IP地址,浏览器通过这个IP地址是可以访问网站的(独立主机),在我们ping apple.com的时候得到的IP地址是不能通过浏览器的方式访问的(共享主机或者虚拟主机) 虚拟主机是一种在单一主机或主机群上,实现多网域服务的方法,可以运行多个网站或服务的技术,因此它可以共享服务器的内存、CPU等资源
虚拟主机主要有以下几种类型
- 网址名称对应(Name-based)
- IP地址对应(IP-based)
- Port端口号对应
在http-proxy-middleware
中使用的changeOrigin
配置项正式由于使用了网址名称对应的虚拟主机。
跨域资源共享(CORS)
在本篇博客中,该种方式会进行详细介绍,利用AJAX进行跨域资源共享的时候,我们就会引出文章刚开始所遇到的问题,简单请求和非简单请求。
- 简单请求: 在这里为了模仿跨域的请求利用到express框架,先建立两个文件,一个后端接口文件backend.js。
'use strict';
var express = require('express'),
app = express();
app.get('/auth/:id/', function (req, res) {
res.send({ id:req.params.id });
});
app.listen(3000);
复制代码
然后利用backend.js
指向一个html
文件,在html
里面发送ajax
请求,模仿跨域(端口号不同)
'use strict';
var express = require('express'),
app = express();
app.use(express.static("./"))
app.get('/', function (req, res) {
res.render(__dirname + '/index.html');
});
app.listen(4000);
复制代码
最后是html
文件,引入了axios
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
</body>
<script type="text/javascript">
axios.get('http://localhost:3000/auth/1')
.then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
</script>
</html>
复制代码
当发起请求时,我们发现
即使在返回结果是200的情况下,仍然时跨域提醒,这就与前面的观点是一致的 跨域并非浏览器限制了发起跨站请求,而是跨站请求可以正常发起,但是返回结果被浏览器拦截了。 服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin
字段(详见下文),就知道出错了,从而抛出一个错误。 那么解决方法也很简单,在后端加上允许跨域请求。
app.all('*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Content-Type", "application/json;charset=utf-8");
next();
});
复制代码
这样就可以正常访问
- 非简单请求 依旧利用上面的代码,在发送请求POST时,当头部
Content-Type
为application/json
时,代码如下:
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
</body>
<script type="text/javascript">
axios.post('http://localhost:3000/auth', {
name: 'gao'
}, {
headers: {
'Content-Type': 'application/json'
}
})
.then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
</script>
</html>
复制代码
会发送如图所示的预检请求:
当头部Content-Type
为
application/x-www-form-urlencoded
时,代码如下:
<!DOCTYPE html>
<html>
<head>
<title></title>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
</body>
<script type="text/javascript">
axios.post('http://localhost:3000/auth', {
name: 'gao'
}, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
.then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
</script>
</html>
复制代码
会发现,浏览器不会发送预检请求并且返回成功
那么如何保证在发送预检请求时,浏览器不会报错呢?后端代码如下:
'use strict';
var express = require('express'),
app = express();
app.all('*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "content-type");
res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
res.header("Content-Type", "application/json;charset=utf-8");
next();
});
app.get('/auth/:id/', function (req, res) {
res.send({ id:req.params.id });
});
app.post('/auth', function (req, res) {
res.send({ id:req.params.id });
});
app.listen(3000);
复制代码
会发现加入了这两行代码即可。
res.header("Access-Control-Allow-Headers", "content-type"); // 这里的content-type是默认的,根据客户端请求来设定
res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
复制代码
此时POST会发送两次请求:
当预检请求通过后,再次发送POST
请求。
其他
像其他的技术,包括JSONP、LocalStorage、cookie、postMessage在此不多做介绍,可以参考阮大的博客进行参考。
奇淫技巧
在有些时候,后端的接口由于各种不能满足我们所需要的跨域支持,那么就需要前端自己下功夫,我们思考一下,既然浏览器由于安全策略阻止了返回的结果,那么就可以在浏览器的安全策略上进行修改。
设置Chrome浏览器的 disable-web-security
, 实现跨域访问后端的接口。
windows
"C:\Users\UserName\AppData\Local\Google\Chrome\Application\chrome.exe" --disable-web-security --user-data-dir
mac
open -a "Google Chrome" --args --disable-web-security --user-data-dir
linux
chromium-browser --disable-web-security