关于浏览器跨域的原理,一个请求在浏览器端发送出去后,是会收到返回值响应的,只不过浏览器在解析这个请求的响应之后,发现不属于浏览器的同源策略(地址里面的协议、域名和端口号均相同),会进行拦截。如果是在curl里面发送一个请求,都是没有跨域这样一个概念的,下面是例子进行分析:
server.html
在这个html里面采用ajax请求3011这个服务
<html>
<head>
<meta charset="utf-8" />
<title>cors</title>
</head>
<body>
<script>
const xhr = new XMLHttpRequest();
xhr.open('GET', 'http://127.0.0.1:3011/');
xhr.send();
</script>
</body>
</html>
server.3011.js
const http = require('http');
const port = 3011;
http.createServer((request, response) => {
console.log('request url: ', request.url);
response.end('3011 port serve for you');
}).listen(port);
console.log('server listening on port ', port);
server.3010.js
对上面定义的server.html模版进行渲染,从而在该模版里调用3011这个端口服务
const http = require('http');
const fs = require('fs');
const port = 3010;
http.createServer((request, response) => {
console.log('request url: ', request.url);
const html = fs.readFileSync('server.html', 'utf-8');
response.writeHead(200, {
'Content-Type': 'text/html',
});
response.end(html);
}).listen(port);
console.log('server listening on port ', port);
打开浏览器,地址栏输入http://127.0.0.1:3010/,会看到以下提示not allowed access,但是server.3011.js,会打印出 request url: /
,说明浏览器在发送这个请求的时候并不知道服务是不是跨域的,还是会发送请求并接收服务端返回的内容,但是当浏览器接收到响应后在headers里面没有看到 Access-Control-Allow-Origin:
被设置为允许,会把这个请求返回的内容给忽略掉,并且在命令行报下面的错误。
Failed to load http://127.0.0.1:3011/: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://127.0.0.1:3010' is therefore not allowed access.
解决的几种方法
-
设置Access-Control-Allow-Origin
- 设置为*表示,可以接收任意域名的访问
http.createServer((request, response) => { response.writeHead(200, { 'Access-Control-Allow-Origin': '*' }) }).listen(port);
- 也可以设置为特定域名访问
http.createServer((request, response) => { response.writeHead(200, { 'Access-Control-Allow-Origin': 'http://127.0.0.1:3010/' }) }).listen(port);
- 如果有多个域名访问可以在服务端动态设置
http.createServer((request, response) => { const origin = request.headers.origin; if ([ 'http://127.0.0.1:3010' ].indexOf(origin) !== -1) { response.writeHead(200, { 'Access-Control-Allow-Origin': origin, }) } }).listen(port);
- jsonp
浏览器是允许像link、img、script标签在路径上加载一些内容进行请求,是允许跨域的,那么jsonp的实现原理就是在script标签里面加载了一个链接,去访问服务器的某个请求,返回内容。
<html>
<head>
<meta charset="utf-8" />
<title>cors</title>
</head>
<body>
<!-- <script>
const xhr = new XMLHttpRequest();
xhr.open('GET', 'http://127.0.0.1:3011/');
xhr.send();
</script> -->
<script src="http://127.0.0.1:3011/"></script>
</body>
</html>
CORS预请求
预请求也是浏览器的一种安全机制,会先发送一个OPTIONS请求给目的站点,与跨域服务器协商可以设置的头部信息,允许的方法和headers信息等。
-
Access-Control-Allow-Origin: 跨域服务器允许的来源地址(跟请求的Origin进行匹配),可以是*或者某个确切的地址,不允许多个地址
-
Access-Control-Allow-Methods: 允许的方法GET、HEAD、POST
-
Access-Control-Allow-Headers: 允许的Content-Type
-
text/plain
-
multipart/form-data
- application/x-www-form-urlencoded
-
-
Access-Control-Max-Age: 预请求的返回结果(Access-Control-Allow-Methods和Access-Control-Allow-Headers)可以被缓存的时间,单位秒
-
请求头限制
-
XMLHttpRequestUpload对象均没有注册任何事件监听器
- 请求中没有使用ReadableStream对象
修改server.html
<html>
<head>
<meta charset="utf-8" />
<title>cors</title>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.4/fetch.min.js"></script>
<script>
fetch('http://127.0.0.1:3011/', {
method: 'PUT',
headers: {
'X-Test-Cors': 'test'
}
})
</script>
</body>
</html>
在次运行后浏览器提示如下信息,上面自定义的头X-Test-Cors在跨域请求的时候是不被允许的.
Failed to load http://127.0.0.1:3011/: Request header field X-Test-Cors is not allowed by Access-Control-Allow-Headers in preflight response.
Failed to load http://127.0.0.1:3011/: Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.
修改后端服务server.3011.js处理OPTIONS请求,设置相应的跨域响应头
const http = require('http');
const port = 3011;
http.createServer((request, response) => {
response.writeHead(200, {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'X-Test-Cors',
'Access-Control-Allow-Methods': 'PUT, DELETE',
'Access-Control-Max-Age': '1000',
})
response.end('3011');
}).listen(port);
console.log('server listening on port ', port);
运行结果
第一次先发送了一个OPTIONS请求询问
第二次发送一个PUT数据请求