关于跨域以及跨域的完成体式格局

关于跨域

why?

为何会有跨域?

我们得先了解下 ==同源战略(SOP, Same Origin Policy)==。

浏览器出于平安方面的斟酌,只能接见与包括它的页面位于统一个域中的资本,该战略为通讯设置了“雷同的协定、雷同的域、雷同的端口”这一限定。试图接见上述限定以外的资本,都邑激发平安毛病。这类平安战略能够防备某些歹意行动。

简而言之,

  1. 同协定 Same Protocol
  2. 同域名 Same Hostname
  3. 同端口号 Same Port

Same Protocol && Same Hostname && Same Port

What?

什么是跨域?

==跨域就是采纳手艺计划打破同源战略的限定,完成差别域之间交互(请求相应)。==

How?

那末怎样完成跨域呢?
有以下几种要领。

==要领一==

CORS (Cross-Origin Resource Sharing,跨域源资本共享),是一种ajax跨域请求资本的体式格局,支撑当代浏览器,IE支撑10以上,经由历程XMLHttpRequest完成Ajax通讯的一个重要限定就是同源战略。
CORS是W3C的一个事情草案,定义了在必需接见跨境资本时,浏览器和服务器该怎样沟通。CORS的基础思想,就时运用自定义的HTTP头部让浏览器和服务器举行沟通,从而决议请求或许相应应当胜利照样失利。
完成思绪:运用XMLHttpRequest发送请求时,浏览器会给该请求自动增加一个请求头:Origin。服务器经由一系列处置惩罚,假如肯定请求泉源页面属于白名单,则在相应头部到场首部字段:Access-Control-Allow-Origin。浏览器比较请求头部的Origin 和相应头部的 Access-Control-Allow-Origin是不是一致,一致的话,浏览器获得相应数据。假如服务器没有设置Access-Control-Allow-Origin 或许这个头部源信息不婚配,浏览器就会驳回请求。

模仿CORS的完成

步骤1.

怎样假装一个网站(在当地)?

1.编辑hosts文件

苹果mac: 直接在git bash上输入命令行操纵即可 “sudo vi /etc.hosts” ,或许下载一些图形界面运用软件直接修正。

Windows操纵体系:

  1. win键(四个方块的键)+ R = 弹开运转窗口
  2. 复制该文件途径 c:windowssystem32driversetc
  3. 选中hosts文件,右键-属性-平安-挑选组或用户名(增加修正保留的权限的对象)- 编辑 – 再次挑选组或用户名(增加修正保留的权限的对象 – 勾选权限(选项在此不表)
  4. 翻开hosts文件,写入 127.0.0.1 localhost;127.0.0.1 bai.com;127.0.0.1 google.com;能够写入你任何你想模仿的网站,根据这类对应关联花样即可, ip地点+域名。

步骤2.
所需东西
node.js && git bash(模仿服务器),一个简朴的html页面内里有个跨域请求的Ajax通讯。

<!-- 前端 -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Google</title>
    <style type="text/css">
        body>h1 {
            text-align: center;
        }
        h1 {
            margin: 0 auto;
        }

    </style>
</head>
<body>
    <h1>hello world</h1>
    <script type="text/javascript">
    //CORS的完成
    var xhr = new XMLHttpRequest();
    xhr.onload = function(){
        if( (xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
            var responseData = xhr.responseText;
            console.log(responseData)
            //['NBA Break News','CBA Break News']
        }

    }
    xhr.open('GET', 'http://baidu.com:8080/getNews', true);
    xhr.send()

    </script>
</body>
</html>
//nodeJS模仿后端相应CORS的完成
var http = require('http');
var fs = require('fs');
var url = require('url');
var path = require('path');

http.createServer(function(req, res){

     var urlObj = url.parse(req.url, true)

    switch (urlObj.pathname){

    case '/getNews':
    var news = ['NBA Break News','CBA Break News']
    //CORS的完成
    res.setHeader('Access-Control-Allow-Origin','http://google.com:8080')
    /*res.setHeader('Access-Control-Allow-Origin','*')
    服务器设置公用接口
    */
    res.end(JSON.stringify(news));
    break;

    case '/' :
    if(urlObj.pathname == '/') {
        urlObj.pathname += 'index.html'
    }

    default: 
    var filePath = path.join(__dirname, urlObj.pathname);
    fs.readFile(filePath,'binary', function(error, fileContent){
        if(error){
            console.log('404')
            res.writeHeader(404, 'not found')
            res.end('<h1>404,not found</h1>')
        }else {
            res.write(fileContent, 'binary')
        }
    })
    }

}).listen(8080);

上面代码就是CORS完成的历程。

  1. 在当地修正hosts文件,127.0.0.1 google.com, 页面的url为 http://google.com:8080。
  2. 在title为google的页面上增加一个ajax请求,该请求以get要领会向baiduServer的端口(’http://baidu.com:8080/getNews’)发送一个请求。
  3. 浏览器会给请求头加上Origin: http://google.com:8080, Request URL: http://baidu.com:8080/getNews。
  4. baiduServer后端,相应头增加首部字段。Access-Control-Allow-Origin: http://google.com:8080。 表明该服务器(baiduServer)接收请求并赋予相应。
  5. 浏览器比较请求头部的Origin 和相应头部的 Access-Control-Allow-Origin是不是一致,一致的话,浏览器获得相应数据。假如服务器没有设置Access-Control-Allow-Origin: http://google.com:8080 或许这个头部源信息不婚配,浏览器就会驳回请求。

固然服务器也能够设置公用接口, res.setHeader(‘Access-Control-Allow-Origin’,’*’)

服务器设置公用接口, 任何人都能够运用该服务器这个端口的数据。


==要领二==

JSONP,是JSON with padding的简写(添补式JSON或参数式JSON)。

JSONP的道理,经由历程动态<script>元素,运用时能够为该元素的src属性增加一个跨域URL, <script>元素的src有才不受限定地从其他域中,加载资本。
通常具有src这个属性的元素都能够跨域,比方<script><img><iframe>。

JSONP和JSON看起来差不多,只不过是被包括在函数挪用中的JSON。

JSONP由两个部份构成:回调函数和数据。回调函数是当相应到来时应当在页面中挪用的函数。回调函数的名字平常是在请求中指定的,所以须要对应接口的后端合营才完成。而数据就是传入回调函数中的JSON数据。

模仿JSONP的完成

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Google</title>
    <style type="text/css">
        body {
            text-align: center;
        }
        h1 {
            margin: 0 auto;
        }
        ul, li {
            list-style: none;
        }
    
    </style>

</head>
<body>
    
    <h1>hello world</h1>
    <ul class="news">

    </ul>
    
    <!--JSONP的代码完成要领1-->

    <!-- <script type="text/javascript">
    
    function getResponseData(jsonData){
        document.write(jsonData[0] + ', ');
        document.write(jsonData[1]);
        //NBA Break News, CBA Break News
    
    }
    </script>
    <script src="http://baidu.com:8080/getNews?newsData=getResponseData">
    //getResponseData(["NBA Break News","CBA Break News"])
    </script> -->
    
    <!--JSONP的代码完成要领2-->
    <script>
        
        var script = document.createElement('script');
        script.setAttribute('src', 'http://baidu.com:8080/getNews?newsData=getResponseData');
        $('body').appendChild(script);
        $('body').removeChild(script);

        

        function getResponseData(jsonData){
        var nodeStock = document.createDocumentFragment();
        for(var i = 0; i < jsonData.length; i++) {
            var newsNode = document.createElement('li');
            newsNode.innerText = jsonData[i];
            nodeStock.appendChild(newsNode)
        };

        $('.news').appendChild(nodeStock)
        // <li>NBA Break News</li>   <li>CBA Break News</li>
        };

        function $(selector) {
            return document.querySelector(selector);
        };

    </script>



</body>
</html>

nodeJS

var http = require('http');
var fs = require('fs');
var path = require('path');
var url = require('url');

http.createServer(function(req,res){
    var urlObj = url.parse(req.url, true);
    switch(urlObj.pathname) {
        case '/getNews' :
        var news = ['NBA Break News','CBA Break News'];
        res.setHeader('Content-Type','text/javascript; charset=utf-8');
        if(urlObj.query.newsData){
            var data = urlObj.query.newsData + '(' + JSON.stringify(news) + ')' ;
            res.end(data);
        } else {
            res.end(JSON.stringify(news))
        }
        break;

        case '/' :
        if(urlObj.pathname == '/') {
            urlObj.pathname +=  'index.html'
        }

        default:
            fs.readFile(path.join(__dirname, urlObj.pathname), function(error, data) {
                if(error) {
                    res.writeHeader(404, 'not found');
                    res.end('<h1>404, Not Found</h1>');
                } else {
                    res.end(data)
                }
            });

    };
}).listen(8080);

==要领三==

降域,重要运用场景是统一页面下差别源的框架iframe请求

基于iframe完成的跨域,请求两个域都必需属于统一个基础域, 比方 a.xx.com, b.xx.com,都有一个基础域xx.com, 运用统一协定和端口,如许在两个页面中同时增加documet.domain,就能够完成父页面操控子页面(框架)。

关于document.domain, 用来获得当前网页的域名。在浏览器输入URL,wwww.baidu.com。 http://wwww.baidu.com, document.domain 为 “www.baidu.com”。 也能够为document.domain赋值, 不过有限定,就是前面提到的,只能赋值为当前的域名或许基础域名。
范例:

document.domain = “www.baidu.com” //successed 赋值胜利, 当前域名。

document.domain = “baidu.com” // successed 赋值胜利, 基础域名。

然则下面的赋值会报错(参数无效)。

“VM50:1 Uncaught DOMException: Failed to set the ‘domain’ property on ‘Document’: ‘a.baidu.com’ is not a suffix of ‘www.baidu.com’.

at <anonymous>:1:17"。

范例
document.domain = “google.com” // fail, 参数无效

document.domain = “a.baidu.com” // fail, 参数无效

由于google.com 和 a.baidu.com不是当前的域名,也不是当前域名的基础域名。
缘由: 浏览器为了防备歹意修正document.domain来完成跨域盗取数据。

— —
==模仿降域的完成==

毛病范例:

hosts 文件设置 win10体系途径为 c:windowssystem32driversetchosts
127.0.0.1 a.com
127.0.0.1 b.com

a.com的一个网页(a.html)内里 应用iframe引入一个b.com里的一个网页(b.html )。在a.html内里能够看到b.html的内容,但不能用Javascript来操纵它。
缘由: 这两个页面属于差别的域,在操纵之前,浏览器会检测两个页面的域是不是相称,相称则许可操纵,不相称则报错。
这个例子里,不可能把a.html与b.html,应用JS改成统一个域。缘由:两个域的基础域名不相称。

http://a.com:8080/a.html的控制台(console), 输入代码window.frames[0].document.body //VM150:1 Uncaught DOMException: Blocked a frame with origin “http://a.com:8080” from accessing a cross-origin frame.

at <anonymous>:1:18
<!--a.com的一个网页(a.html)内里 应用iframe引入一个b.com里的一个网页(b.html ) -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>a.com:8080/a.html</title>
</head>
<body>
    <iframe src="http://b.com:8080/b.html" frameborder="1"></iframe>
</body>
</html>
<!-- b.html-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>b.com:8080/b.html</title>
</head>
<body>
    <h1>this is b.html </h1>
    <input type="text" placeholder="How are you? this is http://b.com:8080/b.html">
</body>
</html>
//nodeJS 
var http = require('http');
var fs = require('fs');
var path = require('path');
var url = require('url');

http.createServer(function(req,res){
    var urlObj = url.parse(req.url, true);
    switch(urlObj.pathname) {
        case '/getNews' :
        var news = ['NBA Break News','CBA Break News'];
        res.setHeader('Content-Type','text/javascript; charset=utf-8');
        if(urlObj.query.newsData){
            var data = urlObj.query.newsData + '(' + JSON.stringify(news) + ')' ;
            res.end(data);
        } else {
            res.end(JSON.stringify(news))
        }
        break;

        case '/' :
        if(urlObj.pathname == '/') {
            urlObj.pathname +=  'index.html'
        }

        default:
            var filePath = path.join(__dirname, 'static' ,urlObj.pathname);
            console.log(filePath)
            fs.readFile(filePath, function(error, data) {
                if(error) {
                    res.writeHeader(404, 'not found');
                    res.end('<h1>404, Not Found</h1>');
                } else {
                    res.end(data)
                }
            });

    };
}).listen(8080);

能够把iframe的src改变成”http://a.com:8080/b.html”,如许就能够了,是不会有这个题目的,由于域相称。
控制台不会报错,然则如许没完成跨域。能够运用html5中的postMessage来完成,针对基础域差别的框架,这里暂且不表, 在要领四,会用到这类要领。

window.frames[0].document.body

<body>​<h1>​this is b.html ​</h1>​<input type=​”text” placeholder=​”How are you? this is http:​/​/​b.com:​8080/​b.html”>​</body>​

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>a.com:8080/a.html</title>
</head>
<body>
    <!-- <iframe src="http://b.com:8080/b.html" frameborder="1"></iframe> -->
    <iframe src="http://a.com:8080/b.html" frameborder="1"></iframe>
</body>
</html>

==准确范例:
降域的完成==

hosts文件设置

基础域名雷同

127.0.0.1 a.shawroc.com

127.0.0.1 b.shawroc.com

a.shawroc.com的内里一个网页(a.html)引入b.shawroc.com里的一个网页(b.html),a.shawroc.com照样不能操纵b.shawroc.com内里的内容。
缘由:document.domain不一样,a.shawroc.com vs b.shawroc.com。
然则两个页面的基础域名是一样的,经由历程JS,将两个页面的domain改成一样。
在a.html 和 b.html 里都到场
<script type=”text/javascript”>
document.domian = shawroc.com
</script>

如许在两个页面中同时增加document.domain, 就能够完成父页面操控子页面(框架)。

控制台
window.frames[0].document.body
//console输出

<body>
    <h1>this is http://b.shawroc.com:8080/b.html </h1>
    <input type="text" placeholder="How are you? this is http://b.shawroc.com:8080/b.html">
    <script>
    document.domain = 'shawroc.com';
    </script>

</body>

代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>a.shawroc.com:8080</title>
</head>
<body>
    <!-- <iframe src="http://b.com:8080/b.html" frameborder="1"></iframe> -->
    <iframe src="http://b.shawroc.com:8080/b.html" frameborder="1" height="400px" width="600px"></iframe>
    <script>
    document.domain = 'shawroc.com';
    </script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>b.shawroc.com:8080/b.html</title>
</head>
<body>
    <h1>this is http://b.shawroc.com:8080/b.html </h1>
    <input type="text" placeholder="How are you? this is http://b.shawroc.com:8080/b.html">
    <script>
    document.domain = 'shawroc.com';
    </script>
</body>
</html>

==要领四==

html5的postMessage API

html5引入的postMessage()要领,许可来自差别源的剧本采纳异步体式格局举行有限的通讯,能够完成跨文本档、多窗口、跨域音讯通报。

postMessage(data, origin) 要领,接收两个参数。

1.data:要通报的数据,html5范例中提到该参数能够是JavaScript的恣意基础范例或可复制的对象,但是并非一切浏览器都做到了这点儿,部份浏览器只能处置惩罚字符串参数,所以我们在通报参数的时刻须要运用JSON.stringify()要领对对象参数序列化,在低版本IE中援用json2.js能够完成相似结果。

2.origin:字符串参数,指明目的窗口的源,协定+主机+端口号[+URL],URL会被疏忽,所以能够不写,这个参数是为了平安斟酌,postMessage()要领只会将message通报给指定窗口,固然假如情愿也能够建参数设置为”*”,如许能够通报给恣意窗口,假如要指定和当前窗口同源的话设置为”/”。

范例

模仿postMessage的事情机制

改写hosts文件

127.0.0.1 a.com

127.0.0.1 b.com

<!--a.html-->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>a.com:8080</title>
</head>
<body>
    <div class="textMessageInACom">
        <input type="text" placeholder="http://a.com:8080/a.html">
    </div>
    <iframe src="http://b.com:8080/b.html" frameborder="1" height="400px" width="600px"></iframe>
    <script>

    $('.textMessageInACom input').addEventListener('input', function(){
        window.frames[0].postMessage(this.value, 'http://b.com:8080');
    })
    //步骤1 a.com:8080/a.html页面下的input发作输入事宜时, 向目的窗口发一个MessageEvent事宜<iframe src="http://b.com:8080/b.html">, MessageEvent.data能够获得this.value的值。接下来切换到b.html页面

    window.addEventListener('message', function(messageEvent){
        $('.textMessageInACom input').value = messageEvent.data
    })
    // 步骤4 监听嵌套页面b.com:8080的message事宜,把b.com:8080的input.value(message.data)赋值给a.com:8080的input.value, 就能够完成输入内容的同步啦。
    
    function $(selector){
        return document.querySelector(selector);
    }

    </script>
</body>
</html>
<!--b.html-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>b.shawroc.com:8080/b.html</title>
</head>
<body>
    <h1>this is http://b.com:8080/b.html </h1>
    <input id="input" type="text" placeholder="How are you? this is http://b.com:8080/b.html">

</body>
<script>
 window.addEventListener('message', function(e){
     $('#input').value = e.data;
 })
 //步骤2, 在b.com:8080/b.html监听message事宜
 
 $('#input').addEventListener('input', function() {
     window.parent.postMessage(this.value, 'http://a.com:8080');
 })
 //步骤3,b.com的input发作输入事宜时,向嵌套页面的父页面window.parent, a.com:8080 postMessage,然后切回到a.html,


function $(selector){
        return document.querySelector(selector);
    }

</script>
    
</html>
//nodeJS  模仿后端
var http = require('http');
var fs = require('fs');
var path = require('path');
var url = require('url');

http.createServer(function(req,res){
    var urlObj = url.parse(req.url, true);
    switch(urlObj.pathname) {
        case '/getNews' :
        var news = ['NBA Break News','CBA Break News'];
        res.setHeader('Content-Type','text/javascript; charset=utf-8');
        if(urlObj.query.newsData){
            var data = urlObj.query.newsData + '(' + JSON.stringify(news) + ')' ;
            res.end(data);
        } else {
            res.end(JSON.stringify(news))
        }
        break;

        case '/' :
        if(urlObj.pathname == '/') {
            urlObj.pathname +=  'index.html'
        }

        default:
            var filePath = path.join(__dirname, 'postMessage' ,urlObj.pathname);
            fs.readFile(filePath, function(error, data) {
                if(error) {
                    res.writeHeader(404, 'not found');
                    res.end('<h1>404, Not Found</h1>');
                } else {
                    res.end(data)
                }
            });

    };
}).listen(8080);

剖析代码
步骤1, a.com:8080/a.html页面下的input发作输入事宜时, 向目的窗口发一个MessageEvent事宜<iframe src=”http://b.com:8080/b.html”>, MessageEvent.data能够获得this.value的值。接下来切换到b.html页面。

步骤2, 在b.com:8080/b.html监听message事宜,在这,就能够完成 a.com:8080/a. html的input标签输入什么,嵌入在 a.com:8080/a. html的iframe框架(b.com:8080/b.html)同步父页面(a.com:8080/a. html)的输入内容了。

步骤3,b.com:8080/b.html的input发作输入事宜时,向嵌套页面的父页面window.parent(a.com:8080 )postMessage,然后切回到a.html。

步骤4, 监听嵌套页面b.com:8080/b.html的messageEvent事宜,把b.com:8080/b.html的input.value(message.data)赋值给a.com:8080的input.value, 完成输入内容的双向同步。

    原文作者:来自卡冈图雅
    原文地址: https://segmentfault.com/a/1190000016058181
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞