前端网络基础(4)

前端开发
2020年03月06日
956

同源策略

同源策略(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、读写cookiesessionajax等操作的权限

不受同源策略限制的项

  • 页面的超链接:<a href="https://www.baidu.com/">百度一下,你就知道</a>;
  • 重定向页面
  • 表单的提交
  • 资源引入:srcipt:srclink:hrefimg:srciframe: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 - 服务器中转请求

由于同源策略只是针对浏览器(客户端)而言的,所以可以通过下面的方式来实现跨域请求:

  1. 客户端向同源的服务器发起请求;
  2. 同源服务器再向不同源的服务器发起相应的请求;
  3. 同源服务器返回响应给客户端。

4-1服务器中转.jpg

跨域之2 - 设置基础域名 + iframe

4-1基础域名iframe.jpg

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

4-2name&iframe.jpg

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事件

4-3postMessage.jpg

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值来传递数据

4-4hash.jpg

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