跨域访问问题
由于浏览器同源策略的限制,一个域下的 JavaScript 无法访问另一个域,包括提交、获取等操作。这里的同源指的是协议、域名、端口都相同,其中即便是域名和域名对应的 IP 之间浏览器也不允许访问。对于端口和协议不同的跨域访问,只能在后端处理。
JSONP
我们知道 JSON 是一种数据交换格式,而 JSONP(JSON with Padding)是一个非正式的跨域数据交互协议。受浏览器同源策略的影响,使用 AJAX 不能直接访问非同源的资源,但是 JavaScript 的 <script>
不受同源策略限制。于是我们可以使用 <script>
的 src 指向另一个域的地址并附带一个参数指定回调函数名,并提供这个回调函数来处理接收的数据,这个地址的服务器端后台处理这个请求并将数据返回,由于 JSON 的天然优势(纯文本、跨平台、JS 原生支持等),遂将数据用回调函数名包裹封装成 JSON 返回。浏览器请求这个 <script>
后会得到服务器端返回的数据,执行回调函数。这样说起来有点费劲,还是代码清楚。
使用 Node.js
模拟跨域环境,运行该脚本将启动一个 http 服务器,通过 http://127.0.0.1:8888
访问。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const http = require('http'); const url = require("url");
http.createServer(function (request, response) { const data = { status: 'success' } const callback = url.parse(request.url, true).query.callback; response.end(`${callback}(${JSON.stringify(data)})`);
}).listen(8888, '127.0.0.1');
console.log('--------http server start on 8888 port--------')
|
然后是前端的页面。这里要使用到 Node.js 的 http-server
。使用 npm install -g http-server
(加-g 参数全局安装)安装 http-server
,然后在前端页面所在目录运行 http-server
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>firstpage</title> </head> <body></body> <script> function callback(data) { console.log(data) } </script> <script src="http://127.0.0.1:8888?callback=callback"></script> </html>
|
可以看到,http://192.168.1.102:8080/firstpage.html
中的脚本访问 http://127.0.0.1:8888
,并成功获取到了数据。如果选择使用 jQuery 还可以免去定义 <script>
和回调函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>firstpage</title> </head> <body></body> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script> <script> $.ajax({ url: 'http://127.0.0.1:8888', type: 'get', dataType: 'jsonp', jsonp: 'callback', jsonpCallback: 'callback', success: function (data) { console.log(data) }, error: function (msg) { console.log(msg) } }) </script> </html>
|
乍一看 AJAX 与 JSONP 很相似,但是它俩最本质的区别就是 AJAX 是通过 XMLHttpRequest 对象获取非本页数据(同源),而 JSONP 是通过 js 的 <script>
标签获取非本页数据(同源、不同源都可以)。同时,JSONP 只能使用 GET 请求并且可能会有安全隐患。
CORS
CORS 是 W3C 的标准,全称是”跨域资源共享”(cross-origin resource sharing)。它允许浏览器向跨源服务器发出 XMLHttpRequest
请求,从而克服了 ajax 只能同源使用的限制。
CORS 需要浏览器和服务器同时支持才可以生效,所以可能会存在兼容性的问题。服务器实现了 CORS 接口,选择兼容的浏览器就可以实现跨域通信,并且开发者完全可以用之前的方式使用 XMLHttpRequest
而感觉不到跨域限制的存在。
简单来说,CORS 通过在跨域地址的响应头中设置 Access-Control-Allow-Origin 的值为允许跨域访问的地址,一个域在发起跨域访问时,浏览器请求该地址并检测响应头中的 Access-Control-Allow-Origin 值,如果包含该域,则允许访问。发起请求的 JS 对象在某些 IE 中不是 XMLHttpRequest
而是 XDomainRequest
,因此可以使用 jQuery 来做兼容处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const http = require('http'); const url = require("url");
http.createServer(function (request, response) { const data = { status: 'success' } const callback = url.parse(request.url, true).query.callback; response.writeHead(200, { 'Access-Control-Allow-Origin': 'http://127.0.0.1:8080' }); response.end(`${JSON.stringify(data)}`);
}).listen(8888, '127.0.0.1');
console.log('--------http server start on 8888 port--------')
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>CORS</title> </head> <body></body> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script> <script> $.ajax({ url: 'http://127.0.0.1:8888', type: 'get', dataType: 'json', success: function (data) { console.log(data) }, error: function (msg) { console.log(msg) } }) </script> </html>
|
CORS 的优点是可以处理任意类型的请求,而 JSONP 只支持 GET 请求,同时它能够更好的处理错误,而 JSONP 对于请求出错不能很好的处理,并且 CORS 更为安全。
服务器代理
听起来高大上,其实就是 JS 将跨域请求发送给同源的后端,由后端代为请求,然后将获取的数据封装返回。
location.hash
需要配合 <iframe>
使用,并且使用复杂,问题较多,暂不考虑。
document.domain
使用这种方式只适用两个主域名相同,子域名不同的域间通信,例如:www.test.com 和 sub.test.com 之间通信。为了模拟这种环境,除了跨域环境,还要进行域名映射,这里使用 nginx。
a.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>a.html</title> </head> <body></body> <script> document.domain = 'test.com'; let iframe = document.createElement('iframe'); iframe.src = 'http://sub.test.com/b.html'; iframe.style.display = 'none'; document.body.append(iframe); iframe.onload = function () { let win = iframe.contentWindow; console.log(win.data); } </script> </html>
|
b.html
1 2 3 4 5 6 7 8 9 10 11 12
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>b.html</title> </head> <body></body> <script> document.domain = 'test.com'; window.data = 'hello, cross domain'; </script> </html>
|
首先开启 http-server
。
然后将 www.test.com
和 sub.test.com
都映射到 http://192.168.0.141:8081
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #user nobody; worker_processes 1; events { worker_connections 1024; } http { server { listen 80; server_name www.test.com; location / { proxy_pass http://192.168.0.141:8081; }
} server { listen 80; server_name sub.test.com; location / { proxy_pass http://192.168.0.141:8081; } } }
|
www.test.com 这个域名在互联网上可能已经被注册,因此需要在 hosts 中指定我们要访问的并不是互联网上的这个地址,而是 http-server
部署的地址。
1 2 3
| # set nginx hosts 192.168.0.141 www.test.com 192.168.0.141 sub.test.com
|
经过上面的配置,在客户端访问 www.test.com
和 sub.test.com
时,都会通过 nginx 的反向代理去请求真实的地址:http://192.168.0.141:8081
。
window.name
window 对象有个 name 属性,该属性在一个窗口(window)的生命周期内载入的所有的页面都是共享一个 window.name 的,每个页面对 window.name 都有读写的权限,window.name 是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>a.html</title> </head> <body></body> <script> const iframe = document.createElement('iframe'); iframe.src = 'http://192.168.124.177:8081/b.html'; iframe.style.display = 'none'; document.body.appendChild(iframe); iframe.onload = function () { iframe.onload = function () { var data = iframe.contentWindow.name; console.log(data); } iframe.src = 'http://192.168.0.141:8081'; } </script> </html>
|
1 2 3 4 5 6 7 8 9 10 11
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>b.html</title> </head> <body></body> <script> window.name="this is data"; </script> </html>
|
http://192.168.0.141:8081/a.html
访问 http://192.168.124.177:8081/b.html
中的提供的数据。
window.name 的优点是兼容性好,几乎所有的浏览器都支持。抛开 HTML5 来说,它是 JSONP 很好的替代方案,比 JSONP 安全。
postMessage
window.postMessage(message,targetOrigin)
方法是 HTML5 新引进的特性,可以使用它来向其它的 window 对象发送消息,无论这个 window 对象是属于同源或不同源。监听 message
事件,事件能够获取三个重要的值。
- source:发送消息的 window 对象。
- data:发送的消息数据。
- origin:调用
postMessage
方法发送消息的 window 的源。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>a.html</title> </head> <body> <iframe src="http://192.168.124.177:8081/b.html" style="display: none;"></iframe> </body> <script> window.addEventListener('load', function () { const origin = 'http://192.168.124.177:8081'; window.frames[0].postMessage('我是 a,我发消息给了 b', origin); }, false);
window.addEventListener("message", function (e) { console.log('a.html 接收到了消息:' + e.data); }, false) </script> </html>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>b.html</title> </head> <body></body> <script> window.addEventListener('message', function (e) { if (e.source == window.parent) { console.log('b.html 接收到了消息:' + e.data); parent.postMessage('我是 b,我发消息给了 a', e.origin); } }, false) </script> </html>
|
参考
HTTP 访问控制(CORS)
构建 public APIs 与 CORS