1. 同源策略
浏览器出于安全方面的考虑,只允许与同源下的接口交互
同源:同协议 同域名 同端口
案例**
- 场景1
- 当前页面的URL是 https://baidu.com/,页面中script标签对应的js是 https://cdn.baidu.com/index.js,js中Ajax请求的接口是https://user.baidu.com 是否跨域?
- 场景2
- 当前页面的URL是 https://baidu.com/,页面中script标签对应的js是 https://baidu.com/index.js,js中Ajax请求的接口是http://user.baidu.com 是否跨域?
- 场景3
- 当前页面的URL是 https://www.baidu.com/about,页面中script标签对应的js是 https://cnd.baidu.com/index.js,js中Ajax请求的接口是https://baidu.com/user 是否跨域?
总结:**比较当前页面的url与接口url的协议、域名、端口
动手测试**
打开 https://www.baidu.com,检查元素开启控制台,在控制台中输入如下测试代码,看看控制台有什么报错提示,为什么? 把接口url换成 https://www.baidu.com 试试
fetch('https://baidu.com')
.then(res => res.text())
.then(data => console.log(data))
思考:
1. 同源策略的初衷是什么? 保护知识产权
2. 是服务器拒绝还是浏览器拒绝? 浏览器
3. 如果想跨域请求第三方接口,或者把自己的接口提供给第三方,要如何做?
JSON CORS 服务器中转
2. JSONP
JSON with Padding
原理:
同源策略只限制Ajax请求,不限制script标签加载js。
可以通过script标签请求资源,并提前写好接收函数
<script>
function handleData(data) {
console.log(data)
}
</script>
<script src="http://api.xxxx.com/getWeather.php?callback=handleData">
服务器收到请求后,从callback参数得到fn,把原始数据(假设是 {a:1})处理后变成handleData({a:1}) script里的资源加载后会当成js执行,相当于执行 handleData({a:1}), 即可在预定义的handleData函数里处理数据
测试
// 服务器端
const http = require('http')
const url = require('url')
http.createServer((req, res) => {
let urlObj = url.parse(req.url, true)
if(urlObj.pathname === '/getWeather') {
let data = { city: 'hangzhou', weather: 'sunny' }
res.end(`${urlObj.query.callback}(${JSON.stringify(data)})`)
} else {
res.writeHead(404, 'Not Found')
res.end('not found')
}
}).listen(8888)
// 浏览器端
<script>
function showData(data) {
console.log(data)
}
</script>
<script src="http://127.0.0.1:8888/getWeather?callback=showData">
封装JSONP
function jsonp(url, data={}) {
return new Promise((resolve, reject) => {
window.__fn__ = data => resolve(data)
let script = document.createElement('script')
let query = Object.entries(data).map(a=>`${a[0]}=${a[1]}`).join('&')
script.src = url + '?callback=__fn__&' + query
script.onerror = () => reject('加载失败')
document.head.appendChild(script)
document.head.removeChild(script)
})
}
jsonp('http://api.xxx.com/getWeather.php', {city: '北京'}).then(data=>{
console.log(data)
}).catch(error => console.log(error))
3. CORS
跨域资源共享Cross-origin resource sharing
原理**
发送Ajax请求时,浏览器发现该请求不符合同源策略,会给该请求加一个请求头:Origin
后台收到请求后,如果确定接受请求则在返回结果中加入一个响应头:Access-Control-Allow-Origin浏览器判断该响应头中是否包含当前Origin 的值,如果有则浏览器会处理响应,我们就可以拿到响应数据,如果没有浏览器直接驳回
- 前端需要做什么?
- 什么都不需要做
- 服务端需要做什么?
- 加一个 Access-Control-Allow-Origin 响应头
测试**
// 服务器端
const http = require('http')
const url = require('url')
http.createServer((req, res) => {
let urlObj = url.parse(req.url, true)
if(urlObj.pathname === '/getWeather') {
res.setHeader('Access-Control-Allow-Origin', 'http://js.xxx.com')
// res.setHeader('Access-Control-Allow-Origin', '*')
// 服务器说:"浏览器兄弟,我允许任何域都能获取资源,你不用替我拦截非同源的请求了了"
res.end(JSON.stringify( { city: 'hangzhou', weather: 'sunny' } ))
} else {
res.writeHead(404, 'Not Found')
res.end('not found')
}
}).listen(8888)
// 浏览器端
//当前代码在 http://js.xxx.com 下运行
fetch('http://127.0.0.1:8888/getWeather')
.then(res => res.json())
.then(data => console.log(data))
JSONP和CORS都需要服务器配合,如果不配合怎么办?
**
4. 服务器中转
// 中转服务器
const http = require('http')
const url = require('url')
http.createServer((req, res) => {
let urlObj = url.parse(req.url, true)
if(urlObj.pathname === '/bridge') {
http.get(urlObj.query.url, req => {
let text = '' req.on('data', data => text += data)
req.on('end',() => {
res.setHeader('Access-Control-Allow-Origin','*')
res.end(text)
})
})
} else {
res.writeHead(404, 'Not Found')
res.end('not found')
}
}).listen(8888)
// 浏览器端
//当前代码在 http://js.xxx.com 下运行
fetch('http://localhost:8888/bridge?url='+encodeURIComponent('http://baidu.com'))
.then(res=>res.text())
.then(data => console.log(data))