CORS原理分析
一、简介
CORS全称为”跨域资源共享”(Cross-origin resource sharing)
它允许浏览器跨服务器发起XMLHttpRequest
请求,从而解决Ajax只能同源使用的限制。CORS需要浏览器和服务器同时支持。目前大部分浏览器都支持该功能,但IE浏览器不能低于IE10。
只要浏览器支持CORS,主要重任就落到了服务端,需要服务端根据需求配置CORS响应头信息
-
整个CORS通信过程,都由浏览器自动完成,不需要用户参与。浏览器一旦发现Ajax请求跨域,就会自动添加一些附加的请求头信息(比如origin:xxx等),但用户不会察觉。
Accept:*/*
Accept-Encoding:gzip, deflate
Accept-Language:zh-CN,zh;q=0.8
Connection:keep-alive
Content-Length:0
Host:172.0.2.218:3000 //Ajax请求端口3000
Origin:http://172.0.2.218:4000 //源前端口4000
Referer:http://172.0.2.218:4000/
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36 -
跨域请求开始后,服务端收到请求后,如果同意跨域访问,必须进行配置。比如必要字段Access-Control-Allow-Origin。服务端配置响应头信息:
//服务端配置
res.setHeader("Access-Control-Allow-Origin", "*"); //`*`表示接受任意域名的请求
//配置后的请求响应头信息
Access-Control-Allow-Origin:*
Connection:keep-alive
Content-Length:274
Content-Type:application/json; charset=utf-8
Date:Tue, 07 Nov 2017 06:15:56 GMT
ETag:W/"112-bBbMBo5MbTrlOpNo1daLKcUeKqs"
X-Powered-By:Express后,浏览器解析到服务端响应头信息Access-Control-Allow-Origin 字段满足要求,就继续正常请求;如果不满足,浏览器则抛出错误
Failed to load http://172.0.2.218:3000/client/api/000003: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://172.0.2.218:4000' is therefore not allowed access.
二、简单请求和非简单请求
对于简单请求和非简单请求浏览器没有等同视之,而有不同的处理规则。
对于简单请求:浏览器直接发出CORS请求。只不过在请求的头信息中增加了一个
origin
字段(表示源服务器)。服务端只需要根据业务需求在响应头信息中配置Access-Control-Allow-Origin字段即可。-
对于非简单请求:浏览器首先发送预检请求 去询问服务器的跨域策略,如果配置符合,浏览器再继续发送CORS请求。如果不符合,浏览器报错误:禁止跨域访问
Request URL:http://172.0.2.218:3000/client/api/000003
Request Method:OPTIONS //预检请求方法
Status Code:200 OK
Remote Address:172.0.2.218:3000
Referrer Policy:no-referrer-when-downgrade 此时服务器就需要在预检请求 中去配置响应头信息
什么是预检请求:预检请求 就是一个请求方式为OPTIONS 的请求。只需要再服务端增加 预检请求路由 即可。
一旦预检请求通过,以后的每次CORS请求,就类似于简单请求,只需要服务器每次响应一个头信息即可。
三、什么是简单请求和非简单请求?
-
只要满足以下两个条件,就属于简单请求:
- 请求方法是以下三种方法之一
- HEAD
- GET
- POST
- HTTP的头信息不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Conent-Type: 仅限于 application/x-www-form-urlencoded、multipart/form-data、text/plain。
- 请求方法是以下三种方法之一
只要不满足以上两则条件都是非简单请求
四、服务器配置字段
必要字段Access-Control-Allow-Origin
该字段的值可以是指定的域名或者指定的一组域名或者是*
。浏览器根据该字段的值进行判断是否可以跨域。如果是*
表示服务端接受任意域名请求;如果是一个域名:表示服务器只接受指定的唯一域名;如果是一组域名:表示服务器接受指定的部分域名。可选字段Access-Control-Allow-Credentials
该值是一个布尔值,表示是否允许发送cookie。可选字段Access-Control-Allow-Headers
该字段表示服务器支持的所有头信息字段,如果跨域请求头中得请求字段不在此服务器支持范围,则浏览器禁止同源访问。可选字段Access-Control-Max-Age
用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。
实战解析
一、服务器环境node、利用同一ip不同端口来模拟跨域请求(源端口为:4000,资源端口为:3000)。非简单请求利用Ajax发送常见的Content-Type=application/json 实现
二、 源服务器文件:start.js 代码如下:
"use strict"
var http = require("http")
var fs = require("fs")
var path = require("path")
var root = path.resolve(process.argv[2] || ".")
var server = http.createServer(function(req, res) {
var filePath = path.join(root, "cors.html")
fs.stat(filePath, function(err, stats) {
if(!err && stats.isFile()) {
res.writeHead(200, {"Content-Type": "text/html"});
fs.createReadStream(filePath).pipe(res)
}else {
console.log(err)
}
})
})
server.listen(4000)
console.log("server is running at http://172.0.2.218:4000/")
三、源服务器html文件cors.html(包含Ajax请求脚本)。代码如下:
<!DOCTYPE>
<html>
<head>
<meta charset="UTF-8">
<title>跨域资源共享</title>
</head>
<body>
<button onclick="simpleRequest()">Ajax简单请求</button>
<button onclick="noSimpleRequest()">Ajax非简单请求</button>
<h5>请求结果为:</h5>
<p></p>
</body>
<script>
function simpleRequest() {//Ajax简单请求
var pDoc = document.getElementsByTagName("p")[0]
pDoc.innerHTML = ""
var xmlHttp
if(window.XMLHttpRequest) {
xmlHttp = new XMLHttpRequest()
}else {
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP")
}
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
pDoc.innerHTML = xmlHttp.responseText
}
}
xmlHttp.open("POST", "http://172.0.2.218:3000/client/api/000003", true)
xmlHttp.send()
}//172.0.2.218外网地址改为localhost
function noSimpleRequest() {//Ajax非简单请求
var pDoc = document.getElementsByTagName("p")[0]
pDoc.innerHTML = ""
var xmlHttp
if(window.XMLHttpRequest) {
xmlHttp = new XMLHttpRequest()
}else {
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP")
}
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
pDoc.innerHTML = xmlHttp.responseText
}
}
xmlHttp.open("POST", "http://172.0.2.218:3000/client/api/000003", true)
xmlHttp.setRequestHeader("Content-Type","application/json")
xmlHttp.send()
}
</script>
</html>
四、目的服务器文件名:app.js 部分代码如下:
/**
* 临时接口
* 处理cors跨域
*/
//配置预检请求方式1
/*
app.all("*", function(req, res, next) {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
res.setHeader("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
next();
})
app.post("/client/api/000003", function(req, res) {
var jsonData = {
code: "0000",
msg: "success",
result: [
{
notice_id: "18",
notice_title: "清算问题",
publish_date: "2017-10-30",
publish_time: "10:34:42",
type: "2"
},
{
notice_id: "12",
notice_title: "A股交易规则",
publish_date: "2017-10-19",
publish_time: "09:33:22",
type: "2"
}
]
}
res.json(jsonData);
})
*/
//配置预检请求方式2:
//预检请求方法OPTIONS
app.options("/client/api/000003", function(req, res, next) {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
res.setHeader("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
next();
})
app.post("/client/api/000003", function(req, res) {
var jsonData = {
code: "0000",
msg: "success",
result: [
{
notice_id: "18",
notice_title: "清算问题",
publish_date: "2017-10-30",
publish_time: "10:34:42",
type: "2"
},
{
notice_id: "12",
notice_title: "A股交易规则",
publish_date: "2017-10-19",
publish_time: "09:33:22",
type: "2"
}
]
}
res.setHeader("Access-Control-Allow-Origin", "*");//单独配置跨域响应头信息
res.json(jsonData);
})
/**
* 方式2和方式1的不同
* 方式1: 每次请求发起的时候都必须经过`all("*")`所以每次请求头都配置了跨域信息
* 方式2; option仅仅是接受预检请求,只是表示浏览器前期询问跨域同意。而其他请求的时候还需要单独配置跨域响应头信息
*/
//监听端口
var port = process.env.PORT || 3000
app.listen(port)
console.log(`app is running at ${port}`)
五、打开命令行,进入到origin_server目录,启动源服务器
$ node start.js
六、新开命令行界面,进入到resource_server目录,启动目的服务器
$ node app.js
七、浏览器中输入4000端口,效果如下:
八、点击简单请求和非简单请求:
demo链接:https://github.com/MayerFan/CORS_Demo
参考链接:http://www.ruanyifeng.com/blog/2016/04/cors.html