什么是跨域?
概念:只要协议、域名、端口有任何一个不同,都被当作是不同的域。
URL 说明 是否允许通信
http:
http:
http:
http:
http:
http:
http:
https:
http:
http:
http:
http:
http:
http:
http:
http:
对于端口和协议的不同,只能通过后台来解决。
跨域资源共享(CORS)
CORS(Cross-Origin Resource Sharing
)跨域资源共享,定义了必须在访问跨域资源时,浏览器与服务器应该如何沟通。CORS
背后的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。
<script type="text/javascript">
var xhr = new XMLHttpRequest();
xhr.open("GET", "/trigkit4",true);
xhr.send();
</script>
以上的trigkit4
是相对路径,如果我们要使用CORS
,相关Ajax
代码可能如下所示:
<script type="text/javascript">
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://segmentfault.com/u/trigkit4/",true);
xhr.send();
</script>
代码与之前的区别就在于相对路径换成了其他域的绝对路径,也就是你要跨域访问的接口地址。
服务器端对于CORS
的支持,主要就是通过设置Access-Control-Allow-Origin
来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。
要解决跨域的问题,我们可以使用以下几种方法:
通过jsonp跨域
现在问题来了?什么是jsonp
?维基百科的定义是:JSONP(JSON with Padding)
是资料格式 JSON
的一种“使用模式”,可以让网页从别的网域要资料。
JSONP
也叫填充式JSON,是应用JSON的一种新方法,只不过是被包含在函数调用中的JSON,例如:
callback({"name","trigkit4"});
JSONP由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数,而数据就是传入回调函数中的JSON数据。
在js中,我们直接用XMLHttpRequest
请求不同域上的数据时,是不可以的。但是,在页面上引入不同域上的js脚本文件却是可以的,jsonp正是利用这个特性来实现的。 例如:
<script type="text/javascript">
function dosomething(jsondata){
}
</script>
<script src="http://example.com/data.php?callback=dosomething"></script>
js文件载入成功后会执行我们在url参数中指定的函数,并且会把我们需要的json数据作为参数传入。所以jsonp是需要服务器端的页面进行相应的配合的。
<?php
$callback = $_GET['callback'];
$data = array('a','b','c');
echo $callback.'('.json_encode($data).')';
?>
最终,输出结果为:dosomething(['a','b','c']);
如果你的页面使用jquery,那么通过它封装的方法就能很方便的来进行jsonp操作了。
<script type="text/javascript">
$.getJSON('http://example.com/data.php?callback=?,function(jsondata)'){
});
</script>
jquery
会自动生成一个全局函数来替换callback=?
中的问号,之后获取到数据后又会自动销毁,实际上就是起一个临时代理函数的作用。$.getJSON
方法会自动判断是否跨域,不跨域的话,就调用普通的ajax
方法;跨域的话,则会以异步加载js文件的形式来调用jsonp
的回调函数。
JSONP的优缺点
JSONP的优点是:它不像XMLHttpRequest
对象实现的Ajax请求那样受到同源策略的限制;它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;并且在请求完毕后可以通过调用callback的方式回传结果。
JSONP的缺点则是:它只支持GET请求而不支持POST等其它类型的HTTP请求;它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript
调用的问题。
CORS和JSONP对比
CORS与JSONP相比,无疑更为先进、方便和可靠。
1、 JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。
2、 使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理。
3、 JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS)。
通过修改document.domain来跨子域
浏览器都有一个同源策略,其限制之一就是第一种方法中我们说的不能通过ajax的方法去请求不同源中的文档。 它的第二个限制是浏览器中不同域的框架之间是不能进行js的交互操作的。
不同的框架之间是可以获取window对象的,但却无法获取相应的属性和方法。比如,有一个页面,它的地址是http://www.example.com/a.html
, 在这个页面里面有一个iframe
,它的src是http://example.com/b.html
, 很显然,这个页面与它里面的iframe
框架是不同域的,所以我们是无法通过在页面中书写js代码来获取iframe
中的东西的:
<script type="text/javascript">
function test(){
var iframe = document.getElementById('ifame');
var win = document.contentWindow;
var doc = win.document;
var name = win.name;
}
</script>
<iframe id = "iframe" src="http://example.com/b.html" onload = "test()"></iframe>
这个时候,document.domain
就可以派上用场了,我们只要把http://www.example.com/a.html
和 http://example.com/b.html
这两个页面的document.domain都设成相同的域名就可以了。但要注意的是,document.domain的设置是有限制的,我们只能把document.domain设置成自身或更高一级的父域,且主域必须相同。
1.在页面 http://www.example.com/a.html
中设置document.domain
:
<iframe id = "iframe" src="http://example.com/b.html" onload = "test()"></iframe>
<script type="text/javascript">
document.domain = 'example.com';
function test(){
alert(document.getElementById('iframe').contentWindow);
}
</script>
2.在页面 http://example.com/b.html
中也设置document.domain
:
<script type="text/javascript">
document.domain = 'example.com';
</script>
修改document.domain
的方法只适用于不同子域的框架间的交互。
使用window.name来进行跨域
window
对象有个name
属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name
的,每个页面对window.name
都有读写的权限,window.name
是持久存在一个窗口载入过的所有页面中的
使用HTML5的window.postMessage方法跨域
window.postMessage(message,targetOrigin)
方法是html5
新引进的特性,可以使用它来向其它的window
对象发送消息,无论这个window对象是属于同源或不同源,目前IE8+、FireFox、Chrome、Opera
等浏览器都已经支持window.postMessage
方法。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
总结一些跨域的方式
1、可能平时最常用到的就是get方式的jsonp跨域,可以用jquery提供的$.ajax 、$.getJSON。
$.ajax({
url:'接口地址',
type:'GET',
data:'想给接口传的数据',
dataType:'jsonp',
success:function(ret){
console.log(ret);
}
});
这样很简单的就可以实现jsonp的跨域,获取接口返回值。
想更多的了解$.ajax可以参考下面的链接,里面有很详细的介绍:链接描述
2、post方式的form表单跨域。
a.com html:
<script>
function postCallback(data){}
</script>
<form acrion='接口链接' method='post' target='ifr'></form>
<iframe name='ifr'></iframe>
a.com callback.php:
<?php
header('Content-type: text/javascript');
echo '<script>';
echo 'window.top.postcallback(' .$_GET['data']. ');';
echo '</script>';
b.com api.php:
<?php
$data = '{"ret":0,"msg":"ok"}';
header("Location: http://a.com/callback.php?data=".urlencode($data));
3、CORS跨域
原理:CORS定义一种跨域访问的机制,可以让AJAX实现跨域访问。CORS 允许一个域上的网络应用向另一个域提交跨域 AJAX 请求。实现此功能非常简单,只需由服务器发送一个响应标头即可。
注:移动终端上,除了opera Mini都支持。
利用 CORS,http://www.b.com 只需添加一个标头,就可以允许来自 http://www.a.com 的请求,下图是我在PHP中的 hander() 设置,“*”号表示允许任何域向我们的服务端提交请求:
header("Access-Control-Allow-Origin:*");
也可以设置指定域名:
header("Access-Control-Allow-Origin:http://www.b.com");
js部分:
$.ajax({
url: a_cross_domain_url,
crossDomain: true,
method: "POST"
});
CORS比较适合应用在传送信息量较大以及移动端来使用。
4、script标签来跨域
<script src=""></script>
js.onload = js.onreadystatechange = function() {
if (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete') {
js.onload = js.onreadystatechange = null;
}
};
5、h5的postMessage
otherWindow.postMessage(message, targetOrigin);
otherWindow: 对接收信息页面的window的引用。可以是页面中iframe的contentWindow属性;window.open的返回值;通过name或下标从window.frames取到的值。
message: 所要发送的数据,string类型。
targetOrigin: 用于限制otherWindow,“*”表示不作限制
a.com/index.html中的代码:
<iframe id="ifr" src=""></iframe>
<script type="text/javascript">
window.onload = function() {
var ifr = document.getElementById('ifr');
var targetOrigin = 'http://b.com';
ifr.contentWindow.postMessage('I was there!', targetOrigin);
};
</script>
b.com/index.html中的代码:
<script type="text/javascript">
window.addEventListener('message', function(event){
if (event.origin == 'http://a.com') {
alert(event.data);
alert(event.source);
}
}, false);
</script>
6、子域跨域(document.domain+iframe)
www.a.com上的a.html
document.domain = 'a.com';
var ifr = document.createElement('iframe');
ifr.src = 'http://script.a.com/b.html';
ifr.style.display = 'none';
document.body.appendChild(ifr);
ifr.onload = function(){
var doc = ifr.contentDocument || ifr.contentWindow.document;
alert(doc.getElementsByTagName("h1")[0].childNodes[0].nodeValue);
};
script.a.com上的b.html
document.domain = 'a.com';
具体的做法是可以在http://www.a.com/a.html
和http://script.a.com/b.html
两个文件中分别加上document.domain = 'a.com'
;然后通过a.html
文件中创建一个iframe
,去控制iframe
的contentDocument
,这样两个js
文件之间就可以“交互”了。当然这种办法只能解决主域相同而二级域名不同的情况。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
javascript跨域的几种方法
此文章学习借鉴了一些其他前端同学的文章,自己做了个实践总结
以下的例子包含的文件均为为 http://www.a.com/a.html
、http://www.a.com/c.html
与 http://www.b.com/b.html
,要做的都是从a.html
获取b.html
里的数据
jsonp
是利用script
标签没有跨域限制的特性,通过在src
的url
的参数上附加回调函数名字,然后服务器接收回调函数名字并返回一个包含数据的回调函数
function doSomething(data) {
}
var script = document.createElement("script");
script.src = "http://www.b.com/b.html?callback=doSomething";
document.body.appendChild(script);
假设在a.html
里嵌套个<iframe src=""http://www.b.com/b.html" rel="nofollow">http://www.b.com/b.html" frameborder="0"></iframe>
,在这两个页面里互相通信
a.html
window.onload = function() {
window.addEventListener("message", function(e) {
alert(e.data);
});
window.frames[0].postMessage("b data", "http://www.b.com/b.html");
}
b.html
window.onload = function() {
window.addEventListener("message", function(e) {
alert(e.data);
});
window.parent.postMessage("a data", "http://www.a.com/a.html");
}
这样打开a
页面就先弹出 a data
,再弹出 b data
window.name
的原理是利用同一个窗口在不同的页面共用一个window.name
,这个需要在a.com
下建立一个代理文件c.html
,使同源后a.html
能获取c.html
的window.name
a.html
var iframe = document.createElement("iframe");
iframe.src = "http://www.b.com/b.html";
document.body.appendChild(iframe);
var flag = true;
iframe.onload = function() {
if (flag) {
iframe.src = "c.html";
flag = false;
} else {
alert(iframe.contentWindow.name);
iframe.contentWindow.close();
document.body.removeChild(iframe);
iframe.src = '';
iframe = null;
}
}
b.html
window.name = "这是 b 页面的数据";
b.html
将数据以hash
值的方式附加到c.html
的url
上,在c.html
页面通过location.hash
获取数据后传到a.html
(这个例子是传到a.html
的hash
上,当然也可以传到其他地方)
a.html
var iframe = document.createElement("iframe");
iframe.src = "http://www.b.com/b.html";
document.body.appendChild(iframe);
function check() {
var hashs = window.location.hash;
if (hashs) {
clearInterval(time);
alert(hashs.substring(1));
}
}
var time = setInterval(check, 30);
b.html
window.onload = function() {
var data = "this is b's data";
var iframe = document.createElement("iframe");
iframe.src = "http://www.a.com/c.html#" + data;
document.body.appendChild(iframe);
}
c.html
parent.parent.location.hash = self.location.hash.substring(1);
CORS
是XMLHttpRequest Level 2
里规定的一种跨域方式。在支持这个方式的浏览器里,javascript
的写法和不跨域的ajax
写法一模一样,只要服务器需要设置Access-Control-Allow-Origin: *
这种方式适用于主域相同,子域不同,比如http://www.a.com
和http://b.a.com
假如这两个域名下各有a.html
和b.html
,
a.html
document.domain = "a.com";
var iframe = document.createElement("iframe");
iframe.src = "http://b.a.com/b.html";
document.body.appendChild(iframe);
iframe.onload = function() {
console.log(iframe.contentWindow....);
}
b.html
document.domain = "a.com";
注意:document.domain
需要设置成自身或更高一级的父域,且主域必须相同。
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
反向代理(Apache、Nginx)解决JS跨域问题
BY JACKSUN · 2016年3月13日
写在前面
之前介绍了JSONP的跨域方式,那是利用前端方案解决跨域问题。跨域问题也可以用后端方案解决,比如CORS(Cross-Origin-Resource-Shares)、方向代理等。今天介绍下反向代理如何解决跨域问题。
关于跨域:http://www.sundabao.com/利用JSONP解决跨域问题/
Apache和Nginx都可以实现反向代理,下面分别介绍下Apache和Nginx如何通过反向代理解决跨域问题。
Apache
Apache mod_proxy模块实现了代理/网关的功能,他实现了以下协议的代理-FTP、CONNECT(用于SSL)、HTTP0.9、HTTP1.0、HTTP1.1。此模块经过配置后可以通过以上协议或其它协议连接其它代理模块。
1、安装Apache Proxy_Http Server
vi /path/to/http.conf
//去调下面俩行的注释(#)
#LoadModule proxy_module modules/mod_proxy.so
#LoadModule proxy_http_module modules/mod_proxy_http.so
2、配置转发规则
ProxyRequests Off
proxy_pass /api http://127.0.0.1:8602;
ProxyPassReverse /api http://127.0.0.1:8602;
proxy_set_header Host "192.168.60.31:8602";
proxy_set_header X-Forwarded-For $remote_addr;
将api开头的请求转发到端口8602的端口服务上。
ProxyRequests Off 指令是指开启反向代理,对于客户端来说,他就是原始服务器,并且客户端不用进行特别的设置;而正向代理允许客户端通过它访问任何服务并隐藏客户端自身,因此必须采取一些安全措施确保只为授权的服务器提供服务;
ProxyPass 将一个远端服务器映射到本地服务器的URL空间中;
ProxyPassReverse 调整由反向代理服务器发送的HTTP回应头中的URL;
Proxy_set_header 是向反向代理服务器后端服务器发起请求时添加header信息,当请求的服务器有多个host时,可以通过Host选项区分。
理解正向代理与方向代理:
正向代理:
“反向代理(Reverse Proxy)是指以代理服务器来接受 Internet 上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给 Internet 请求连接的客户端,此时,代理服务器对外就表现为一个服务器。”——《实战Nginx》
正向代理
正向代理(Forward Proxy),通常都被简称为代理,就是在用户无法正常访问外部资源,比方说受到GFW的影响无法访问twitter的时候,我们可以通过代理的方式,让用户绕过防火墙,从而连接到目标网络或者服务。
Nginx
Nginx也可以通过设置proxy_pass来实现反向代理。配置如下:
location /dir {
proxy_pass http://127.0.0.1/api;
}
反向代理的优势
- 请求的统一控制,包括设置权限、过滤规则等;
- 隐藏内部服务真实地址,暴露在外的只是反向代理服务器地址;
- 实现负载均衡,内部可以采用多台服务器来组成服务器集群,外部还是可以采用一个地址访问;
- 解决Ajax跨域问题;
- 作为真实服务器的缓冲,解决瞬间负载量大的问题。
参考1:http://www.uis.cc/2014/11/21/apache-nginx-Reverse-proxy/
参考2:http://www.cnblogs.com/gabrielchen/p/5066120.html
参考3:http://blog.jobbole.com/90975/