什么是跨域?
什么是同源策略
只有协议、域名、端口三者完全相同的页面才能请求数据,否者会被浏览器阻止
浏览器规定:
如果 JS 运行在 源A里,那么就只能获取源A的数据,不能获取源B的数据,既不允许跨域,这样能保护用户隐私
-
为什么 a.qq.com 访问 qq.com也算跨域?
答:因为历史上出现过不同公司共用域名 -
为什么不同端口也算跨域?
原因同上,一个端口一个公司 -
为什么两个网站的IP是一样的,也算跨域?
原因同上,IP可以共用 -
为什么可以跨域使用CSS、JS和图片等?
同源策略限制的是数据访问,我们引用 CSS、JS和图片的时候,其实并不知道内容,我们只是在引用。
同源策略限制:
- Cookie、LocalStorage 和 IndexDB 无法读取
- AJAX 请求不能发送
什么是跨域
一个域的页面去请求另一个域的数据,域名和端口都相同,但是请求路径不同,不属于跨域
CORS 跨域资源共享
对于前端开发者来说,CORS 通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
实现CORS通信的关键是服务器(后端)。只要服务器实现了CORS接口,就可以跨源通信。
前端 AJAX 代码
var request = new XMLHttpRequest(); // 新建XMLHttpRequest对象
request.onreadystatechange = function () { // 状态发生变化时,函数被回调
if (request.readyState === 4) { // 成功完成
// 判断响应结果:
if (request.status === 200) {
// 成功,通过responseText拿到响应的文本:
return success(request.responseText);
} else {
// 失败,根据响应码判断失败原因:
return fail(request.status);
}
} else {
// HTTP请求还在继续...
}
}
// 发送请求:
request.open('GET', '/api/categories');
request.send();
后端服务器代码
添加响应头
Access-Control-Allow-Origin给信任的网址
var http = require('http')
var server = http.creareSever()
server.on('request',(request,response)=>{
response.setHeader('Access-Control-Allow-Origin', url)
})
JSONP 跨域
实现原理:动态加载js,任何网站都能请求js文件,
script标签的src属性不受跨域影响,可以访问外部脚本文件
IE 不支持 CORS,但是必须要有一种方式跨域。当前网站创造一个script去请求另一个网站的js,js中夹带数据;这个js文件会在当前网站执行一个回调全局函数,回调里面就有我们的数据。回调的名字是可以随机生成的一个随机数,把这个名字以callback的形式传给后台,后台会把这个函数返回过来并执行。
优点:JSONP的优点是兼容IE,可以跨域,即使请求另外的域名,也能跨域
缺点:因为是script标签,它只能知道成功和失败,拿不到状态码,header;因为是script标签,只能发get请求,不支持post
实现过程
请求方——浏览器
响应方——服务器
- 请求方创建
script,src指向响应方,同时传递查询参数?callback=random - 响应方根据查询参数
callback构造random.call(null,'data')这样的响应 - 浏览器接收响应,就会执行
random.call(null,'data') - 请求方得到数据
标准写法
虽然写的是ajax,但是一点关系没有,是动态的script
$.ajax({
url: "https://ryan.com",
// The name of the callback parameter, as specified by the YQL service
jsonp: "callback",
// Tell jQuery we're expecting JSONP
dataType: "jsonp",
// Tell YQL what we want and that we want JSON
data: {
q: "select title,abstract,url from search.news where query=\"cat\"",
format: "json"
},
// Work with the response
success: function( response ) {
console.log( response ); // server response
}
});
手写JSONP
function jsonp(url){
return new Promise((resolve,reject)=>{
const random = 'ryanJSONPCallbackName' + Math.random()
//拿到数据后调用resolve
window[random] = (data)=>{resolve(data)}
const script = document.createElement('script')
script.src=`${url}?callback=${random}`
script.onload=()=>{
script.remove()
}
//出错调用reject
script.onerror =()=>{
reject()
}
document.body.appendChild(script)
})
}
Websocket
WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,能更好的节省服务器资源和带宽并达到实时通讯的目的,是server push技术的一种很好的实现。
socket本质上是对 TCP/IP 的运用进行了一层封装,应用程序直接调用 socket API 即可进行通信。
socket是如何工作的呢?
分为 2 个部分,服务端需要建立 socket 来监听指定的地址,然后等待客户端来连接。而客户端则需要建立 socket 并与服务端的 socket 地址进行连接。
WebSocket 和 HTTP最大不同是
- WebSocket是一种双向通信协议。在建立连接后,WebSocket服务器端和客户端都能主动向对方发送或接收数据,
- WebSocket需要像TCP一样,先建立连接,连接成功后才能相互通信。
- 一旦WebSocket连接建立后,后续数据都以帧序列的形式传输。
- 在客户端断开WebSocket连接或Server端中断连接前,不需要客户端和服务端重新发起连接请求。
- 在海量并发及客户端与服务器交互负载流量大的情况下,极大的节省了网络带宽资源的消耗,有明显的性能优势,且客户端发送和接受消息是在同一个持久连接上发起,实时性优势明显。
websocket的基本用法
// WebSocket 协议的 URL 使用 ws://开头,另外安全的 WebSocket 协议使用 wss://开头
// 只需要实例化 WebSocket,创建连接,然后服务端和客户端就可以相互发送和响应消息
var socket = new websocket('ws://echo.websocket.org');
//当 Browser 和 WebSocketServer 连接成功后,会触发 onopen
ws.onopen = function(){ws.send(“Test!”); };
// 如果连接失败,发送、接收数据失败或者处理数据出现错误,会触发 onerror
ws.onerror = function(evt){console.log(“WebSocketError!”);};
// 当 Browser 接收到 WebSocketServer 发送过来的数据时,就会触发 onmessage 消息,参数 evt 中包含 Server 传输过来的数据
ws.onmessage = function(evt){console.log(evt.data);ws.close();};
// 如果你想往后端推送数据,可以使用
// 因为Web Socket只能接受和发送纯为本数据,所以对数稍微复杂的数据,可以把他转化为JSON字符串
ws.send(data);
// 当 Browser 接收到 WebSocketServer 端发送的关闭连接请求时,就会触发 onclose 消息。
ws.onclose = function(evt){console.log(“WebSocketClosed!”);};
postMessage
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下三个场景的跨域数据传递:
- 页面和其打开的新窗口的数据传递
- 多窗口之间消息传递
- 页面与嵌套的iframe消息传递
postMessage的API
otherWindow.postMessage(message, targetOrigin, [transfer]);
- otherWindow:其他窗口的一个引用,比如iframe的contentWindow属性,执行window.open返回的窗口对象,或者是命名过的或数值索引的window.frames。
- message: 要发送到其他窗口的数据, html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。
- targetOrigin: 协议+主机+端口号,通过窗口的origin属性来指定哪些窗口能接收到消息事件,指定后只有对应origin下的窗口才可以接收到消息,设置为通配符"*"表示可以发送到任何窗口,但通常处于安全性考虑不建议这么做.如果想要发送到与当前窗口同源的窗口,可设置为"/"
- transfer:可选属性,是一串和message同时传递的Transferable对象,这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。
其他窗口可以监听message事件
window.addEventListener("message", receiveMessage, false);
function receiveMessage(event)
{
// For Chrome, the origin property is in the event.originalEvent
// object.
// 这里不准确,chrome没有这个属性
// var origin = event.origin || event.originalEvent.origin;
var origin = event.origin
if (origin !== "http://example.org:8080")
return;
// ...
}
postMessage的具体使用示例
例如:A页面要给B页面发送消息
// A页面的域名是http://www.examplea.com:8080,在A页面的script标签下加入以下代码
const winpopup = window.open('http://www.exampleb.com:8888/b.html');
// 将第二个参数targetOrigin设置为要打开的B的域名,如果设置的和要打开的页面域名不一致,会导致消息发送不出去。
winpopup.postMessage('Hello B, I am A', 'http://www.examplea.com:8080');
function receiveMessage(event) {
const origin = event.origin;
// 判断信息来源是否为B
if(origin !== "http://www.examplea.com:8080") {
return;
}
// data: 从其他 window 中传递过来的对象。
// do something
consoel.log(event.data);
}
window.addEventListener('message', receiveMessage, false);
// B页面的域名是http://www.exampleb.com:8888,在B页面的script标签中加入以下代码
//当A页面postMessage被调用后,这个function被addEventListener调用
function receiveMessage(event)
{
// event.origin是调用 postMessage 时消息发送方窗口的 origin, 即A页面
// 判断信息来源是否为A
if (event.origin !== "http://www.examplea.com:8080") {
return;
}
// event.source 就当前弹出页的来源页面
// event.data 是 "Hello B, I am A"
// 假设你已经验证了所收到信息的origin, 可以把event.source 作为回信的对象,并且把event.origin作为targetOrigin
event.source.postMessage("hi A! the secret response is: rheeeeet!", event.origin);
}
window.addEventListener("message", receiveMessage, false);
postMessage的使用注意事项
- 如果您不希望从其他网站接收message,请不要为message事件添加任何事件侦听器。 这是一个完全万无一失的方式来避免安全问题。
- 如果您确实希望从其他网站接收message,请始终使用origin和source属性验证发件人的身份。这不能低估:无法检查origin和source属性会导致跨站点脚本攻击。
- 当您使用postMessage将数据发送到其他窗口时,始终指定精确的目标origin,而不是*。