首先明白一件事:
跨域是浏览器的限制html
也就是说不是你真的不能访问,而是浏览器出于他本身的种种担忧拦截了你的访问。前端
至于浏览器在担忧什么(什么是跨域,为何会有跨域),参考:百度node
其实cors的原理就是,告诉浏览器:"你别担忧啦,这个请求我ok的,让他访问吧"。
而jsonp的原理是,使用浏览器不担忧的方式去请求:script标签中的src属性(其余带有src属性的标签也能够,好比img)。ajax
先来模拟出跨域的场景,在本地启动一个最简单的node服务,返回查询参数。json
// 新建一个server.js文件,固然前提要安装node const http = require('http'); const querystring = require('querystring'); const server = http.createServer((req, res) => { const query = querystring.parse(req.url.split('?')[1]); const queryStr = JSON.stringify(query); res.writeHead(200, { "Content-type": "text/plain; charset=utf-8" }); res.end(queryStr); }); server.listen(9999); console.log('server run at 9999'); 复制代码
启动服务后端
node server.js
复制代码
先在浏览器里直接访问一下 http://127.0.0.1:9999/?name=无用书生&age=25 看到返回没有问题跨域
而后就在当前页面(你如今正在阅读文章的掘金页面)f12打开控制台,在console里建立一个ajax请求浏览器
var xhr = new XMLHttpRequest(); xhr.open('get', 'http://127.0.0.1:9999/?name=无用书生&age=25'); xhr.send(); 复制代码
执行之后就出现跨域报错了bash
首先来看一下cors的跨域原理,其实报错里就写的很明白,咱们访问的资源没有设置对掘金这个访问源的头。markdown
在cors的规则中,请求分为简单请求和非简单请求,咱们上面发送的就是一个简单请求。
关于简单请求和非简单请求,参考:CORS跨域原理解析
只要在响应头中(response header)指明容许哪些访问源访问就能够了。
在server.js中给响应头添加 Access-Control-Allow-Origin
const http = require('http'); const querystring = require('querystring'); const server = http.createServer((req, res) => { const query = querystring.parse(req.url.split('?')[1]); const queryStr = JSON.stringify(query); res.writeHead(200, { "Content-type": "text/plain; charset=utf-8", "Access-Control-Allow-Origin": "*" // * 表明容许全部的源访问 }); res.end(queryStr); }); server.listen(9999); console.log('server run at 9999'); 复制代码
重启node服务,再试一次
报错没有了,打开network能够看到 Access-Control-Allow-Origin
已经生效,数据也成功获取到
将上面请求的方法从get改为put,再次请求 (put方法就属于非简单请求)
var xhr = new XMLHttpRequest(); xhr.open('put', 'http://127.0.0.1:9999/?name=无用书生&age=25'); xhr.send(); 复制代码
能够看到跨域报错又出现了
刚才设置的响应头依然存在,却不起做用了。
另外这里能够看到咱们原本发送的是put请求,请求方法那里写的倒是options。缘由就是对于非简单请求浏览器会先发送一次预检,预检经过才会发送真正的请求,这个options就是预检请求,由于没有经过,因此也就没有发送真正的请求
其实报错中也写的很明白,咱们访问的资源没有设置容许对PUT这个方法的访问
在server.js中给响应头添加 Access-Control-Allow-Methods
,设置容许put方法的请求
const http = require('http'); const querystring = require('querystring'); const server = http.createServer((req, res) => { const query = querystring.parse(req.url.split('?')[1]); const queryStr = JSON.stringify(query); res.writeHead(200, { "Content-type": "text/plain; charset=utf-8", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, PUT", }); res.end(queryStr); }); server.listen(9999); console.log('server run at 9999'); 复制代码
重启服务之后再次请求,能够看到跨域报错就消失了
还能够看到依然先进行了一次预检请求,此次预检请求经过了,继续发送了put请求
非简单请求还对请求头的信息有所限制,原理仍是同样的,经过Access-Control-Allow-Headers
在返回头中设置容许的访问头就ok了,好比
var xhr = new XMLHttpRequest(); xhr.open('put', 'http://127.0.0.1:9999/?name=无用书生&age=25'); xhr.setRequestHeader("X-Corx-Test", "aabbcc"); xhr.send(); 复制代码
设置容许 X-Corx-Test这个请求头
const http = require('http'); const querystring = require('querystring'); const server = http.createServer((req, res) => { const query = querystring.parse(req.url.split('?')[1]); const queryStr = JSON.stringify(query); res.writeHead(200, { "Content-type": "text/plain; charset=utf-8", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET, POST, PUT", "Access-Control-Allow-Headers": "X-Corx-Test" }); res.end(queryStr); }); server.listen(9999); console.log('server run at 9999'); 复制代码
因为掘金作了csp处理,没法测试jsonp,用百度进行演示。
什么是csp,参考:阮一峰博客
去掉node服务中对cors的配置
const http = require('http'); const querystring = require('querystring'); const server = http.createServer((req, res) => { const query = querystring.parse(req.url.split('?')[1]); const queryStr = JSON.stringify(query); res.writeHead(200, { "Content-type": "text/plain; charset=utf-8" }); res.end(queryStr); }); server.listen(9999); console.log('server run at 9999'); 复制代码
在百度首页f12打开控制台,这时候若是再使用ajax请求咱们的服务又会报跨域的错误
上面说过jsonp的原理就是使用script标签的src不受浏览器跨域限制的原理
在console中建立一个jsonp请求
var script = document.createElement('script'); script.src = 'http://127.0.0.1:9999/?name=无用书生&age=25'; document.head.appendChild(script); 复制代码
执行之后看到这时候就没有报错了
这时候请求虽然成功了,还没拿到返回的数据
获取数据的方法就是在前端定义一个接收数据的函数,而后后端返回的js中执行这个函数,并把要返回的数据做为参数传入
好比在前端定义一个叫作 getData
的函数
var script = document.createElement('script'); script.src = 'http://127.0.0.1:9999/?name=无用书生&age=25'; document.head.appendChild(script); function getData(res) { console.log(res); } 复制代码
在后端返回的内容中调用这个函数,把数据传进去
const http = require('http'); const querystring = require('querystring'); const server = http.createServer((req, res) => { const query = querystring.parse(req.url.split('?')[1]); const queryStr = JSON.stringify(query); res.writeHead(200, { "Content-type": "text/plain; charset=utf-8", }); let jsonpStr = `getData(${queryStr})`; res.end(jsonpStr); }); server.listen(9999); console.log('server run at 9999'); 复制代码
重启服务之后,执行前端代码,数据就能够取到了
这个函数名字要先后端约定一致,另外获取完数据之后最好移除一下script标签
var script = document.createElement('script'); script.src = 'http://127.0.0.1:9999/?name=无用书生&age=25'; document.head.appendChild(script); function getData(res) { console.log(res); document.head.removeChild(script); } 复制代码