前端网络基础(4)
同源策略
同源策略(SOP):Same-Origin-Policy
Web浏览器只允许在两个页面有相同的源时,第一个页面的脚本才能访问第二个页面里面的数据。
跨域报错信息解读
报错信息
plain-text
Access to XMLHttpRequest at 'http://api.localhost.com/server.php' from origin 'http://test.localhost.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Access to XMLHttpRequest
:表示请求已跨域at 'http://api.localhost.com/server.php'
:表示跨的是哪个域from origin 'http://test.localhost.com'
:表示请求的源has been blocked by CORS policy
:被跨域资源共享策略(Cross-origin resource sharing)阻止;No 'Access-Control-Allow-Origin' header is present on the requested resource
:表示在请求的资源中没有发现”允许跨域“的头信息。
源
源: 协议 + 域名 + 端口
- 同源:相同的协议 && 相同的域名 && 相同的端口
- 不同源:不同的协议 || 不同的域名 || 不同的端口
同源策略SOP是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。
只有同一个源的脚本才会被赋予dom
、读写cookie
、session
、ajax
等操作的权限。
不受同源策略限制的项
- 页面的超链接:
<a href="https://www.baidu.com/">百度一下,你就知道</a>
; - 重定向页面
- 表单的提交
- 资源引入:
srcipt:src
、link:href
、img:src
、iframe:src
注意:只要有JS引擎的浏览器都使用同源策略。
AJAX
浏览器与服务器之间的通信的基础是HTTP协议,用户通过网址或表单向服务器提交请求,服务器向浏览器发送相应的响应。
那么如何在不刷新整个页面的基础上,却能获取到新的网页所需的数据或更新部分的网页内容呢?
AJAX就是为此而生的。
历史
- 1999 IE5.0允许JS脚本向服务器单独发起HTTP请求的新功能
- 2004 Gmail退出异步邮件更新服务
- 2005 Google Map 异步更新地图服务
- 2005 AJAX被大厂公认命名
- 2006 W3C发布AJAX国际标准
AJAX是什么
AJAX:Asynchronous JavaScript and XML,异步的JavaScript和XML。
这是JavaScript脚本发起的HTTP通信。
XMLHttpRequest和ActiveXObject
- JS脚本发起HTTP请求必须通过
XMLHttpRequest对象
- 也是通过AJAX进行浏览器与服务器通信的接口
- 不局限XML,可以发送任何格式的数据
XMLHttpRequest
本身是一个JS引擎内置的构造函数,所有XMLHttpRequest对象
都需要被实例化:
js
var xhr = new XMLHttpRequest();
兼容性:IE5/IE6使用ActiveXObject
js
var xhr = new ActiveXObject('Microsoft.XMLHTTP');
发起HTTP请求
open()
open(method, url, async)
:发送设置
- method:请求方式
- url:请求发送的地址
- async:是否为异步请求
send()
send()
:发送请求
参数:发送POST请求体数据用,GET不需要填写(或传入null
)
onreadystatechange事件
onreadystatechange事件
:挂载到XMLHttpRequest对象
上的事件;
readyState
状态:通过XMLHttpRequest对象
发送HTTP请求的各阶段状态码(0-4)
- 0:请求未初始化
- 1:服务器连接已建立
- 2:请求已接收
- 3:请求处理中
- 4:请求已完成,且响应已就绪
**status
状态:**服务器响应的状态码(200、404…)
当readyState
发生变化时,将触发 onreadystatechange事件
,并执行其事件处理函数。
注意:readyState
仅是针对请求的状态码,获取资源的成功与否取决于status
的状态。
示例
js
var xhr = window.XMLHttpReqeust
? new XMLHttpRequest()
: new ActiveXObject('Microsoft.XMLHTTP');
if (!xhr) {
throw new Error('您的浏览器不支持发送异步请求');
}
xhr.onreadystatechange = function () {
console.log(xhr.readyState); // 2 3 4
if (xhr.readyState === 4) {
console.log(xhr.status); // 服务器响应的状态码
}
}
console.log(xhr.readyState); // 0
xhr.open('GET', 'https://www.baidu.com/', true);
console.log(xhr.readyState); // 1
xhr.send(null);
服务器响应
responseText
:获取字符串数据responseXML
:获取XML数据
注意事项
在POST请求下:send()
参数中的格式:a=1&b=2&c=3
;并且,在open()
与send()
之间需要设置请求头(Content-type
)
js
xhr.open('POST', url, true);
// POST请求必须设置请求头信息
// 目的是请求体中的数据转换为键值对
// 这样后端接收到a=1&b=2&c=3这样的数据才知道这是一个POST请求过来的数据
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send('a=1&b=2');
XMLHttpRequest版本
XMLHttpRequest标准又分类Level1和Level2:
Level 1
缺点:
- 无法发送跨域请求
- 只能获取纯文本数据
- 无法获取传输进度
Level 2
Level2进行了改进:
- 可以发送跨域请求;
- 支持获取二进制数据;
- 支持上传文件;
- Form Data对象;
- 可以获取传输进度;
- 可以设置超时时间
兼容性问题
- IE8/9/Opera Mini不支持xhr对象
- IE10/11不支持响应类型为JSON
- 部分浏览器不支持超时设置
- 部分浏览器不支持blob(文件对象的二进制数据)
状态码与状态提示
xhr.status/xhr.statusText
:服务器响应的HTTP状态码/服务器发送的状态提示。
- 200,OK,访问正常
- 301,Moved Permanently,永久移动
- 302,Move Temporarily,暂时移动
- 304,Not Modified,未修改
- 307,Temporary Redirect,暂时重定向
- 401,Unauthorized,未授权
- 403,Forbidden,禁止访问
- 404,Not Found,未发现指定网址
- 500,Internal Server Error,服务器发生错误
事件
xhr.onloadstart
:绑定 HTTP请求发出的监听函数xhr.onerror
:绑定 请求失败的监听函数xhr.onload
:绑定 请求成功完成的监听函数xhr.onabour
:绑定 请求中止(调用abour()
)的监听函数xgr.onloadend
:绑定 请求完成(不管成功或失败)的监听函数
事件触发顺序
plain-text
loadstart -> readyState === 4 -> load/error/abort -> loadend
请求超时
xhr.timeout
:指定请求超时时间,如果该属性等于0,就表示没有时间限制;xhr.ontimeout
:绑定 请求超时的事件监听函数,如果发生了超时事件,就会触发。
注意:自带的超时有兼容性问题,一般不用。
异步与同步
async
参数的值:
- 异步(默认)
async设置为true
:AJAX异步发送请求时,不影响页面加载、用户操作以及AJAX程序后面的程序执行。 - 同步
async设置为false
:AJAX同步发送请求时,会阻塞后面的程序执行。
返回类型
dataType
:返回的数据类型
- JSON:
JSON.parse(xhr.responseText)
- TEXT:
xhr.responseText
- XML:
xhr.responseXML
跨域的八种方式
跨域之1 - 服务器中转请求
由于同源策略只是针对浏览器(客户端)而言的,所以可以通过下面的方式来实现跨域请求:
- 客户端向同源的服务器发起请求;
- 同源服务器再向不同源的服务器发起相应的请求;
- 同源服务器返回响应给客户端。
跨域之2 - 设置基础域名 + iframe
js
// 通过basic domain 方式进行跨域请求
// 需要设置两边的document.domain
var basicDomain = (function (doc) {
return function (opt) {
var iframe = doc.createElement('iframe');
iframe.id = opt.iframeId;
iframe.src = opt.iframeUrl;
iframe.style.display = 'none';
iframe.onload = function () {
var myIframe = doc.getElementById(opt.iframeId),
$$ = myIframe.contentWindow.xhr;
$$.ajax({
url: opt.url,
type: opt.type,
dataType: opt.dataType,
success: opt.success,
error: opt.error,
complete: opt.complete
});
}
doc.body.appendChild(iframe);
}
})(document);
跨域之3 - window.name + iframe
window.name
的特点:
- 每个浏览器窗口都有一个全局变量
window
(包含iframe
框架的contentWindow
) - 每个
window对象
都有一个name属性
(注意:一个窗口只有一个name属性
) - 该窗口被关闭前,所有页面都共享一个
name属性
,并有读写权限 - 无论窗口在关闭前载入什么页面,都不会改变
name
的值(除非手动修改) - 存储约为2M的字符串
如果父级窗口地址源和iframe
的地址源不同,父级无法通过iframe.contentWindow.name
获取值,但iframe
内部不受该规则限制。
解决方案:
先让iframe
中的页面程序保存window.name
,然后跳转到和父级窗口同源的页面中,父级页面就可以从当前的iframe
中拿到该页面的window.name
。
api.localhost.com/study/test.html
js
xhr.ajax({
url: 'http://api.localhost.com/study/data',
type: 'GET',
success: function (data) {
window.name = JSON.stringify(data)
// 设置完window.name之后,让当前页面跳转到与请求页面同源的页面去
location.href = 'http://test.localhost.com/test2.html'
}
});
test.localhost.com/test.html
html
<iframe src="http://api.localhost.com/study/test.html" id="myIframe"></iframe>
<script src="ajax_2.js"></script>
<script type="text/javascript">
var iframe = document.getElementById('myIframe');
setTimeout(function () {
console.log(JSON.parse(iframe.contentWindow.name));
}, 500)
</script>
跨域之4 - postMessage + iframe
注意:这种方式不常用:
- 伪造数据端漏洞
- xss攻击
- 兼容性问题
otherWindow.postMessage(message, targetOrigin)
- otherWindow:接收方的引用
- message:要发送到接受方的数据(只能是文本类型)
- targetOrigin:接收方的源
接收方需要监听message事件
api.localhost.com/study/test.html
js
xhr.ajax({
url: 'http://api.localhost.com/study/data',
type: 'GET',
success: function (data) {
window.parent.postMessage(JSON.stringify(data), 'http://test.localhost.com');
}
});
test.localhost.com/test.html
html
<iframe src="http://api.localhost.com/study/test.html" id="myIframe"></iframe>
<script src="ajax_2.js"></script>
<script type="text/javascript">
window.onmessage = function (e) {
console.log(JSON.parse(e.data))
}
</script>
跨域之5 - hash + iframe
原理:利用url的hash值来传递数据
test.localhost.com/test.html
html
<button id="btn">获取hash</button>
<iframe src="http://api.localhost.com/study/test.html" id="myIframe"></iframe>
<script src="ajax_2.js"></script>
<script type="text/javascript">
var oBtn = document.getElementById('btn');
oBtn.onclick = function () {
var data = JSON.parse(decodeURI(location.hash.substring(1)));
console.log(data);
}
</script>
api.localhost.com/study/test.html
html
<iframe src="http://test.localhost.com/test2.html" id="test2Frame"></iframe>
<script src="http://api.localhost.com/static/ajax_2.js"></script>
<script>
xhr.ajax({
url: 'http://api.localhost.com/study/data',
type: 'GET',
success: function (data) {
var test2Frame = document.getElementById('test2Frame');
test2Frame.src = 'http://test.localhost.com/test2.html#' + JSON.stringify(data);
}
});
</script>
test.localhost.com/test2.html
js
setTimeout(function () {
var str = self.location.hash.substring(1);
parent.parent.location.hash = str;
}, 1000);
跨域之6 - CORS
需要在服务端配置
跨域资源共享:Cross-origin resource sharing
api.php
php
// 任意域名
header('Access-Control-Allow-Origin: *');
// 单域名
header('Access-Control-Allow-origin: http://www.baidu.com');
// 多域名
$allowed_origins = [
'http://www.baidu.com',
'http://www.google.com'
];
$origin = isset($_SERVER['HTTP_ORIGIN'])? $_SERVER['HTTP_ORIGIN'] : '';
if (in_array($origin, $allowOrigins)) {
header('Access-Control-Allow-Origin: ' . $origin);
}
// 通知服务器在真正的请求中会采用哪种HTTP方法
header('Access-Control-Request-Methods: GET,POST');
跨域之7 - JSONP跨域
JSONP (JSON with Padding):跨域获取JSON数据的一种非官方的使用模式
JSON和JSONP不是一个类型:
- JSON是数据交换格式
- JSONP是一种跨域获取JSON数据的交互技术、非正式协议
- JSONP抓取的资源并不直接是JSON数据,而是带有JSON参数的函数的执行
客户端期望返回:{"name": "Jacky", "age": "18"}
JSONP实际返回:callback({"name": "Jacky", "age": "18"})
注意:JSONP只支持GET请求。
js
var oScript = document.createElement('script');
oScript.src = 'https://www.baidu.com/sugrec?prod=pc&wd=京东&cb=callback';
function callback (data) {
console.log(data);
}
document.body.appendChild(oScript);
document.body.removeChild(oScript);
跨域之8 - Flash
略