前后端如何通信
- Ajax
- Websoket
- CORS
为什么会发生ajax跨域
因为浏览器同源策略的限制,不允许ajax跨域请求
同源的限制
不是用一个源的文档,无法操作另一个源的文档
- 开发时针对浏览器的限制可以用如下命令打开允许跨域的窗口
open /Applications/Google\ Chrome.app --args --disable-web-security --user-data-dir
同源的目的
- 网站一般会把一些重要信息放在Cookie或者LocalStorage中,如果没有同源策略,这些信息可以共享,如果有人恶意窃取网站数据,这些信息就会被泄露。所以说同源策略是浏览器安全的基础。
跨域带来的问题
- cookie、localstorage无法获取
- DOM无法操作
- Ajax请求无法发送
同源
同源指的是 协议、域名、端口
一个域名地址的组成
- 一般我们看到的网址没有显示端口号,是因为使用的默认端口80,可以省略
http://(协议)www(子域名).abc.com(主域名):8080(端口号)/somescript/jquery.js(请求资源地址)
可以跨域的三个标签
- 图片:
<img src='xxx'>:用于打点统计 - css:
<link href='xxx'>:可以使用CDN资源 - js:
<script src='xxx'>:可以用于JSONP和CDN资源
跨域的解决办法
JSONP
- 原理:利用script标签的异步加载来实现
我们给服务端传递一个回调的名称,用加载script标签的形式发送一个请求,服务端给返回一个代码块,代码块中有我传递的回调名称的代码
- jsonp返回的数据不是json对象而是js脚本,这个脚本是一个函数的调用,函数的名字是jsonp请求的时候写的函数,这个jsonp请求返回的值作为参数。
- jsonp发送的请求type是script
JSONP是ajax吗
不是
为什么JSONP不是ajax
它是利用script标签的异步加载来实现
使用jsonp服务器后台需要改动吗
需要,所有跨域请求都必须经过信息提供方的允许
缺点
- 服务器端需要改动,返回值不再是JSON对象,而是JS脚本
- 发送的不是XHR请求
- 只能实现get请求,容易遭受XSS攻击
优点
- 兼容性比较好,服务器改造非常小
原生JS模拟
function jsonp({url,params,cb}) {
return new Promise((resolve, reject) => {
let script = document.createElement('script');
window[cb] = function (params) {
resolve(params);
}
params = {...params,cb};
let arrs = [];
for(let key in params){
arrs.push(`${key}=${params[key]}`);
}
script.src = `${url}?${arrs.join('&')}`;
document.body.appendChild(script);
});
};
jsonp({
url: 'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su',
params:{wd:'a'},
cb:'show'
}).then(data=>{
console.log('jsonp跨域请求的数据为:',data);
});
jquery模拟
jquery原代码也是通过动态创建script标签的形式实现跨越的
$.ajax({
url: 'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su',
type: 'get',
data: {wd:'a'},
dataType: 'jsonp', // 请求方式为jsonp
jsonpCallback: "show", // 自定义回调函数名
success:function(){
},
error:function(){
}
});
vue:axios
this.$http.jsonp('https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su', {
params: {wd:'a'},
jsonp: 'show'}).then((res) => {
console.log(res);
})
Hash
场景: 当前页面A通过iframe嵌入了跨域页面
- A页面中的操作
var B = document.getElementByTagName('iframe')
B.src = B.src + '#' + 'data'
- B页面中接收数据
window.onhashchange = function(){
let data = window.location.hash
}
postMessage
场景:窗口A像跨域的窗口B发送消息
- A中发送
Bwindon.postMessage('data','http://B.com')
- B中接受
windoq.addEventListener('message',function(){
console.log(event.origin)
console.log(event.source)
console.log(event.data)
},false)
WebSocket
var ws = new WebSocket('wss://echo.websocket.org')
ws.onopen = function(evt){
console.log('Connection open...')
ws.send('Hello WebSockets')
}
ws.onmessage = function(evt){
console.log('Received Message'+ evt.data)
}
ws.onclose = function(evt){
console.log('Connection closed')
}
CORS 参考资料
服务器端设置http header
response.setHeader('Access-Control-Allow-Origin','http://a.com,http://b.com')
response.setHeader('Access-Control-Allow-Origin','X-Requested-With')
response.setHeader('Access-Control-Allow-Origin','PUT,POST,GET,DELETE,OPTIONS')
// 接收跨域的cookie
response.setHeader('Access-Control-Allow-Credentials','true')
不需要使用预请求去验证的
允许的method,其他的通过设置Access-Control-Allow-Methods:'PUT,DELETE'来设置
- GET
- HEAD
- POST
响应头允许的Content-Type
- text/plain、text/html
- multipart/form-data
- application/x-www-form-urlencoded
允许的请求头,其他都通过设置Access-Control-Allow-Headers来处理
- Accept
- Accept-Language
- Content-Language
设置响应头:任何域名/指定域名可以访问这个服务
'Access-Control-Allow-Origin': '*'
'Access-Control-Allow-Origin': 'http://baidu.com'
CORS预请求
- 跨域请求,设置了自定义请求头
fetch('http://localhost:8887', {
method: 'POST',
headers: {
'X-Test-Cors': '123'
}
})
- 设置响应头
response.writeHead(200, {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'X-Test-Cors',
'Access-Control-Allow-Methods': 'PUT,DELELTE',
'Access-Control-Max-Age':'1000'
})
3.浏览器会先发送一个options类型的预请求
4.然后在发送真正的请求
- 通过设置响应头Access-Control-Max-Age,可以设置预请求在多长时间内不需要重复发送
jsonp和cors的对比
1.cors与jsonp的使用目的相同,但是比jsonp更强大。 2.jsonp只支持get请求,cors支持所有类型的http请求。 3.jsonp的优势在于支持老式浏览器,以及可以向不支持cors的网站请求数据。
手动编写一个ajax,不依赖第三方库
- 不使用promise
var xhr =XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject()
//为保证跨域浏览器兼容,onreadystatechange应该在调用open之前被赋值
xhr.onreadystatechange = function (argument) {
if(xhr.readyState === 4){
if((xhr.status >200 && xhr.status <300)|| xhr.status === 304){
alert(xhr.responseTest)
}else{
alert(xhr.status)
}
}
xhr.open('GET','/api',true)//是否异步请求,true代表异步,false代表同步
xhr.send(null) //请求接口需要的参数,如果没有必须用null代替
- 使用promise
function ajax(url) {
const p = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('GET', url, true)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.responseText))
} else {
reject(new Error('404 Not Found'))
}
}
}
xhr.send(null)
})
return p
}
ajax('/data/test.json')
.then(res=>console.log(res))
.catch(res=>console.error(err))