常言道,"读万卷书,不如行万里路"。技术的学习也是如此,惟有实践才能更清楚的明白原理和加深印象,所以本文会利用node.js对前端的各类跨域方式进行实践,强烈建议一步一步跟着作,相信你确定会对跨域有更深层次的理解。而因为篇幅限制,本文只会贴出关键性的代码,本系列总共分为上下篇。具体的代码请移步个人Github。若是对你有帮助的话,欢迎 star ヾ(´・ω・`)ノcss
首先咱们在本地起一个服务器,用于接收客户端的请求并做出回应。html
//目录:cors/server.js前端
const http = require('http'); http.createServer(function (req, res) { //设置响应头部 res.writeHead(200, {'Content-Type': 'text/plain'}); res.write('This is a server page'); res.end(); }).listen(3333); console.log('server start!')
而后,开启另外一个服务,服务里加载一个html页面,页面对发出xhr请求,模拟浏览器对服务器的请求。node
//目录:cors/clientServer.jsjquery
const express = require('express'); const app = express(); app.use(express.static('./public')); app.listen(3000) console.log('client server start');
//目录:cors/public/client.htmlgit
const content = document.getElementById('content'); const xhr = new XMLHttpRequest(); xhr.withCredentials = true; xhr.onload = function(){ if(xhr.readyState == 4) { if(xhr.status >= 200 && xhr.status <300 || xhr.status == 304) { content.innerHTML = 'Reuqest was success:' + xhr.responseText; console.log('Request was success:', xhr.responseText); }else { content.innerHTML = 'Reuqest was failed:' + xhr.status; console.log("Request was failed:", xhr.status); } } } // xhr.open('get', 'http://localhost:3000/client.html', true); //不跨域 xhr.open('get', 'http://localhost:3333', true); //跨域 xhr.send();
分别运行两个服务,测试3000和3333接口,发现只有跨域的时候,请求的头部才会带着origin
字段。此时咱们修改cors/server.js, 加上这行代码:github
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
这行代码表明服务器容许接收来自3000接口的请求,此时客户端再次请求服务器,就能在用户毫无感知的状况下完成跨域。web
而此时若是想让客户端带cookie请求呢?那么须要作如下工做:ajax
1.cors/server.js 加上这行express
res.setHeader('Access-Control-Allow-Credentials', true);
2.cors/public/client.html 加上这行
xhr.withCredentials = true;
而后,你就会发现,客户端会把当前域下的cookie一块儿发给服务器啦╮( ̄▽ ̄")╭
ps:注意cookie只能细分到域名下,不能细分到端口。即没办法设置一个cookie仅在localhost:xxxx下。尽管端口不一样会被浏览器认为不一样源。
一般为了减轻web服务器的负载,咱们把js、css,img等静态资源分离到另外一台独立域名的服务器上,在html页面中再经过相应的标签从不一样域名下加载静态资源,而被浏览器容许,基于此原理,咱们能够经过动态建立script,再请求一个带参网址实现跨域通讯。
下面这个例子采用jQuery中的ajax方法,与服务器端约定将数据回传到回调函数中,好比本例中的callback=person
,而后咱们就能够从回调函数person里获取服务器传给浏览器的数据了。另外,jsonp的缺点就是只能采用get请求。
1.目录:jsonp/server.js
const http = require('http'); const urllib = require('url'); const httpdispatcher = require('httpdispatcher'); const dispatcher = new httpdispatcher(); const PORT = 1112; function handleRequest(req, res) { try { console.log(req.url); dispatcher.dispatch(req, res); }catch(err) { console.log(err); } } const server = http.createServer(handleRequest); dispatcher.onGet('/getPerson', function (req, res, next) { const data = {'name': 'Jchermy', 'company': 'dog company'}; const params = urllib.parse(req.url, true); if(params.query && params.query.callback) { let str = `${params.query.callback}(${JSON.stringify(data)})`; res.write(str); res.end(); }else { res.write(JSON.stringify(data)); res.end(); } }) server.listen(PORT, function () { console.log("server listening on http://localhost: %s", PORT); })
2.目录:jsonp/client.js
const express = require('express'); const app = express(); app.use(express.static('./public')); app.listen(1111); console.log('client start');
3.目录:jsonp/public/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>index</title> <script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.js"></script> </head> <body> <button id='getData'>跨域获取数据</button> <div id='content'> 姓名:<span class="name"></span><br/> 公司:<span class="company"></span> </div> <script> const btn = document.getElementById('getData'); const container = document.getElementById('content'); btn.addEventListener('click', function(){ $.ajax({ url: 'http://localhost:1112/getPerson?callback=?', dataType: 'jsonp', jsonpCallback: 'person', success: function(data){ $('.name').html(data.name); $('.company').html(data.company); } }) }, false); </script> </body> </html>
分别运行客户端和服务端,点击“获取跨域数据的按钮”,当前页面(1111端口)就能够拿到1112端口的数据啦~~(●′ω`●)
此方案仅限主域相同,子域不一样的跨域应用场景。
实现原理:两个页面都经过js强制设置document.domain为基础主域,就实现了同域。
下面只是举个例子帮助你们理解一下。
如今有两个网址。百度知道和百度百科
https://zhidao.baidu.com/ https://baike.baidu.com/
在百度知道的网页,写下如下命令:
document.domain = "baidu.com"; const child= window.open("https://baike.baidu.com/");
在打开的百度百科的网页,写下如下命令:
document.domain = "baidu.com";
而后回到百度知道的网页,就能够获取到百度百科(子页面)的元素啦:
const button = other.document.getElementById("search"); //<button id="search" nslog="normal" nslog-type="10080008" type="button">进入词条</button>
window.name属性的独特之处:name值在不一样的页面(甚至不一样域名)加载后依旧存在,而且能够支持很是长的 name 值(2MB)。
在本地起两个node服务,分别占用3333和4444。父页面是:
1.window-name/public/index.html
const proxy = function(url ,callback) { let status = 0; const iframe = document.createElement('iframe'); iframe.src = url; iframe.onload = function(){ if(status === 1) { callback(iframe.contentWindow.name); destoryFrame(); } else if (status === 0) { iframe.contentWindow.location = 'http://localhost:4444/proxy.html'; status = 1; } } document.body.appendChild(iframe); }; function destoryFrame() { iframe.contentWindow.document.write(''); iframe.contentWindow.close(); document.body.removeChild(iframe); } proxy('http://localhost:3333/iframe.html', function(data) { alert(data); })
2.iframe 页面是
window-name/public/iframe.html
<script> window.name = 'this is window.name from iframe'; </script>
3.还有一个代理页面,跟父页面同源。内容为空就好。目录:/window-name/public/proxy.html
总结:经过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操做。
原理:A域想和B域通讯,经过中间页面c。不一样域之间经过location.hash来通讯,而相同域之间直接经过js来通讯。
实现:A域:a.html ----> B域:b.html ----> A域:c.html,a与b不一样域只能经过hash值单向通讯,b与c也不一样域也只能单向通讯,但c与a同域,因此c可经过parent.parent访问a页面全部对象。
目录:location-hash/public/a.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>a</title> <iframe id='iframe' src="http://localhost:4444/b.html" style="display:none;"></iframe> </head> <body> <script> const iframe = document.getElementById('iframe'); //向b.html传递hash值 setTimeout(function(){ iframe.src = iframe.src + '#user=admin'; },1000); function onCallback(res) { alert('data from c.html ---->' + res); } </script> </body> </html>
目录:location-hash/public/b.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>b</title> <iframe id="iframe" src="http://localhost:3333/c.html" style="display:none;"></iframe> </head> <body> <script> const iframe = document.getElementById('iframe'); // 监听a.html传来的hash值,再传给c.html window.onhashchange = function(){ iframe.src = iframe.src + location.hash; } </script> </body> </html>
目录:location-hash/public/c.html
<script> // 监听b.html传来的hash值 window.onhashchange = function(){ // 再经过操做同域a.html的js回调,将结果传回 window.parent.parent.onCallback('hello '+ location.hash.replace('#user=', '')); }; </script>
而后,咱们经过node服务将a.html和c.html部署在同一个端口下,将b.html部署在另外一个端口。
//location-hash/server1.js app.use('/a.html', express.static(__dirname+'/public/a.html')); app.use('/c.html', express.static(__dirname+'/public/c.html')); app.listen(3333); //location-hash/server2.js app.use('/b.html', express.static(__dirname+'/public/b.html')); app.listen(4444);
最后,咱们分别将两个服务跑起来。访问localhost:3333能够看到弹窗。
即b.html借助c.html接收到了a.html发来的消息,并给予回应"hello admin"
,跨域成功~
接下文--->「跨域」利用node.js实践前端各类跨域方式(下)