解决跨域的方法和简单实现

前面要说的

为了在本地实现跨域访问的效果
- 首先我们使用node搭建静态服务器,监听端口3000,通过127.0.0.1:3000访问页面
- 再下载http-server用来挂载静态页面。使用npm install -g http-server下载该模块。在该文件位置的终端输入http-server运行,即可在127.0.0.1:8080访问页面

其实,我作为小白每次面试官问到跨域,我只知道说JSONP,其余的了解的真的很少。所以下定决心,要好好搞懂,最好通过几个栗子来加深理解。然后就遇到了它=>传送文

JSONP

因为浏览器的同源策略出现了“跨域”问题,但所有的src属性并没有受到相关的限制,JSONP的原理就从scipt说起,script标签可以执行其他域的js函数。比如:

//a.html
....
<script>
function callback(data){
    console.log(data.msg)
}
</script>
<script src='b.js'></script>
//b.js

callback({
    msg:'hello world'
})

这样,会打印出 hello world出来。
那么,我们利用这个特性,如果b.js里内容不固定,而是根据一些东西自动生成。这个就是JSONP的原理了。使用回调函数+数据,就组成了JSONP。回调函数用来响应应该在页面中调用的函数;数据则用来传入要执行的回调函数,数据的产生则会通过url的参数部分来实现,下面的实例中可以看到。

栗子

//server.js
const url = require('url');
const http = require('http')
server=http.createServer((req,res)=>{
    const data = {
        x:10
    };
    const callback = url.parse(req.url,true).query.callback;//获取url中参数内的callback值
    res.writeHead(200);
    //拼接回调函数和作为函数参数的数据data
    res.end(`${callback}(${JSON.stringify(data)})`)
});
server.listen(3000,'127.0.0.1');
console.log('启动服务,监听 127.0.0.1:3000')
//index.html
...
<script type="text/javascript">
        function jsonpCallback(data) {
            alert('跨域成功,获得 x 数据:'+data.x)
        }
</script>
<script src='http://127.0.0.1:3000?callback=jsonpCallback'></script>

CORS

CORS全称为“跨域资源共享”,他的原理就是使用自定义的HTTP 头部,让服务器与浏览器进行沟通,主要是通过设置响应头的 Access-Control-Allow-Origin 来达到目的的。这样,XMLHttpRequest 就能跨域了。

栗子

//server.js
const http = require('http');
http.createServer((req,res)=>{
    res.writeHead(200,{
        'Access-Control-Allow-Origin':'http://localhost:8080'
    });
    res.end('this is your data')
}).listen(3000,'127.0.0.1')
console.log("启动服务器")

最为关键的就是设置响应头中的 Access-Control-Allow-Origin,该值要与请求头中 Origin 一致才能生效,否则将跨域失败。

//index.html
<script type="text/javascript">
        const xhr = new XMLHttpRequest();
        xhr.open('GET','http://127.0.0.1:3000',true);
        xhr.onreadystatechange = function () {
            if(xhr.readyState ===4 && xhr.status===200){
                console.log(xhr.responseText);//'this is your data'
            }
        }
        xhr.send(null)
    </script>

这样访问localhost:8080就可以看到控制台打印的’this is your data’了。

CORS不但支持 GET、POST 请求方式,还能实现PUT等非简单请求跨域。

Server Proxy

当你需要有跨域的请求操作时发送请求给后端,让后端帮你代为请求,然后最后将获取的结果发送给你。

栗子

这里我们假设有这样得场景,你的页面需要获取 CNode:Node.js专业中文社区 论坛上一些数据,如通过 https://cnodejs.org/api/v1/topics,但是因为不同域,所以你可以将请求后端,让其对该请求代为转发:

//server.js
const url = require('url');
const http = require('http');
const https = require('https');

const server = http.createServer((req, res) => {
    const path = url.parse(req.url).path.slice(1);
    if(path === 'topics') {
        https.get('https://cnodejs.org/api/v1/topics', (resp) => {
            let data = "";
            resp.on('data', chunk => {
                data += chunk;
            });
            resp.on('end', () => {
                res.writeHead(200, {
                    'Content-Type': 'application/json; charset=utf-8'
                });
                res.end(data);
            });
        })      
    }
}).listen(3000, '127.0.0.1');

console.log('启动服务,监听 127.0.0.1:3000');

postMessage

postMessageHTML5 新增加的一项功能,跨文档消息传输(Cross Document Messaging),目前:Chrome 2.0+Internet Explorer 8.0+, Firefox 3.0+, Opera 9.6+, 和 Safari 4.0+ 都支持这项功能,使用起来也特别简单。

语法:otherWindow.postMessage(message, targetOrigin, [transfer]);

创建一个 iframe,使用 iframe 的一个方法 postMessage 可以向 http://localhost:8081/postMessage-b.html 发送消息,然后监听 message,可以获得其他文档发来的消息。

栗子

//postMessage-a.html
<body>
    <iframe src="http://localhost:8081/postMessage-b.html" style='display: none'></iframe>
    <script type="text/javascript">
        window.onload=function () {
            let targetOrigin = 'http://localhost:8081';//指定窗口地址
            window.frames[0].postMessage('我要给你发消息了:',targetOrigin)//当前窗口的第一个进行跨源通信
        }
        window.addEventListener('message',function(e){//监听派遣的message,可以获得其他文档发来的消息。
            console.log('a.html接收到的消息:',e.data)
        })
    </script>
</body>
//postMessage-b.html
<script type="text/javascript">
    window.addEventListener('message',function(e) {
        if(e.source != window.parent){
            let data = e.data;
            console.log('b.html接收到的消息:',data)
            parent.postMessage('我已经接收到信息了',e.origin)
        }
    })
</script>

document.domain

在一级域名一致,二级域名不同的情况下,可以设置 document.domain;比如:abc.baidu.com/index.htmlcde.baidu.com/main.html是可以通过document.domain来实现的。而abc.baidu.com/index.htmlabc.csdn.com/main.html是不可以实现的。

栗子

具体做法需要iframe来实现,比如我们要在当前页面:abc.baidu.com/index.html上传图片给cde.baidu.com/main.html

//abc.baidu.com/index.html
if(docuement.domain!='baidu.com'){
    document.domain='baidu.com'
}
//cde.baidu.com/main.html的iframe文件内
if(docuement.domain!='baidu.com'){
    document.domain='baidu.com'
}

这样上传就是在相同的域下了。

WebSocket

WebSocket是一种在单个TCP连接上进行全双工通讯的协议。当客户端与服务端创建WebSocket连接后,本身就可以天然的实现跨域资源共享,WebSocket协议本身就不受浏览器“同源策略”的限制,所以问题本身就不成立。

location.hash

url 中,http://www.baidu.com#helloworld 的 “#helloworld” 就是 location.hash,改变 hash 值不会导致页面刷新,所以可以利用 hash 值来进行数据的传递,不过这样数据会暴露在url上,而且数据量是有限的。

window.name

window.name的值不是一个普通的全局变量,而是当前窗口的名字,这里要注意的是每个 iframe 都有包裹它的 window,而这个 windowtop window 的子窗口,而它自然也有 window.name 的属性,window.name 属性的神奇之处在于 name 值在不同的页面(甚至不同域名)加载后依旧存在(如果没修改则值不会变化),并且可以支持非常长的 name 值(2MB)。
主要利用的就是iframe标签的跨域能力和window.name属性值在文档刷新后依旧存在的能力。详细跨域的方案见传送门:window.name详细跨域方法

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页