跨域解决方案之JSONP,CORS

何为跨域

ajax出现,能够让页面无刷新的同时调用服务,浏览器出于安全方面的考虑,在进行Ajax 访问的时候不允许XMLHttpRequest跨域调用其他页面的对象。这里指的就是不允许XMLHttpRequest跨域调用服务,所谓跨域是指如果服务的URL中协议 hostname、端口号与当前页面的这三个参数中有一个不同的话则视为跨域,如下情况

如上的各种组合还有好多情况,那么我们如何才能绕过浏览器的跨域限制 正常地访问服务呢。

在flash或者silverlight中,只要服务的页面下放入 跨域策略文件就可以允许不同域下flash/silverlight的页面来调用,内容大致如下

<cross-domain-policy> 
<allow-access-from domain="*" to-ports="507" /> 
<allow-access-from domain="*.example.com" to-ports="507,516" /> 
<allow-access-from domain="*.example2.com" to-ports="516-523" /> 
<allow-access-from domain="www.example2.com" to-ports="507,516-523" /> 
<allow-access-from domain="www.example3.com" to-ports="*" /> 
</cross-domain-policy> 

关于Flash 的跨域情况 在这里不说解释,本文重点介绍Javascript的情况。

JSONP

虽然浏览器默认禁止了跨域访问,但并不禁止在页面中引用其他域的JS文件,并可以自由执行引入的JS文件中的function(包括操作cookie、Dom等等)。根据这一点,可以方便地通过创建script节点的方法来实现完全跨域的通信。这种方式叫JSONP解决方案。

利用动态的script来请求服务,服务端返回函数的调用的形式,比如callback(“hello world”);前端如果实现已经定义好了一个callback 的函数既可以获得helloworld;下面开始具体实现。

服务端使用NodeJS+express实现。新建express项目,app.js如下

var express = require('express');
var http = require('http');
var path = require('path');
var app = express();
app.set('port', process.env.PORT || 3000);
app.set('port1', process.env.PORT || 3002);
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')))

        function add(a,b){
            return a+b;
        } 
app.get('/add', function(req,res){
    var a=parseInt(req.query.a);
    var b=parseInt(req.query.b);
     res.send("addresult("+add(a,b)+")");
});

http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});
http.createServer(app).listen(app.get('port1'), function(){
  console.log('Express server listening on port ' + app.get('port1'));
});

为了方便演示,这里的跨域具体是指端口号不同,并且在一个文件里面开了两个server,一个3000端口,一个3002端口,这里的服务主要是实现一个加法的功能,读取两个参数,然后进行加法运算,最后返回结果用addresult包起来,比如前端传入过来的参数分别为a=3,b=7,则返回的参数为addresult(7) app.use(express.static(path.join(__dirname, 'public')))是将当前目录下的public 文件件配置为静态目录,接下来在public文件夹下新建crossdomain.html文件,关键代码如下。

var   $=function(id){
               return document.getElementById(id);
   }                
       var a=$('a').value;
       var b=$('b').value;
       var querystring="a="+a+"&b="+b;
       var script=document.createElement('script');
       script.async=true;
       script.src="http://localhost:3000/add?"+querystring;
       document.body.appendChild(script);

以上$为自定义的函数,根据ID 获取元素,然后从文本框中读取值。作为服务的参数传入。接下来要定义一个名字为addresult的函数来接受返回结果:

function addresult (data) {
    // body... 
    $("result").innerHTML=a;
}

启动程序输入 http://localhost:3002/crossdomain.html,这里是在3002上的页面调用的是3000端口上的服务。视为跨域,运行之后在文本框中输入两个值我们发现实现了跨域调用。
以上是实现了一个JSONP的精简版。当然还有些问题要考虑,比如回调函数名字的问题也可以作为参数来传入。第二个问题就是要在服务执行完毕或者请求失败之后 删除动态创建的script标签。

CORS

随着HTML5标准的 不断完善,也推出了一种新的跨域解决方案Cross-Origin Resource Sharing (CORS),现在已经是W3C指定的标准。允许 XMLHttpRequest 进行跨域调用,支持CORS的浏览器有

  • Chrome 3+
  • Firefox 3.5+
  • Opera 12+
  • Safari 4+
  • Internet Explorer 8+ (IE 8下为 XDomainRequest对象)

这里需要服务端和客户端配合 ,要求服务端返回的返回头中必须包含:

Access-Control-Allow-Origin: //发起请求的XMLHttpRequest所在的域
Access-Control-Allow-Methods: //支持的请求的方法
Access-Control-Allow-Credentials:true

同时在请求服务的时候需要将xhr的withCredentials设置为true;如

var xhr = new XMLHttpRequest();
xhr.open("get", "url", true);
xhr.withCredentials = true; //设置这个参数是为了允许cookie发送

这里请求的时候会发两次http请求,第一次是option请求,判断服务端时候只是CORS 以及支持哪些行为(post,get,put等)如果满足条件话则继续进行服务请求,同样这里也以NodeJS为例写个服务端。代码如下

function multiply(a,b){
        return a*b;
}
app.get("/multiply",function(req,res){
    console.log(res.query);
    var a=parseInt(req.query.a);
    var b=parseInt(req.query.b);
    res.set({
              'Access-Control-Allow-Origin': req.headers.origin,
              'Access-Control-Allow-Credentials': true,
              'Access-Control-Allow-Methods':'POST, GET, PUT, DELETE, OPTIONS'
            });
});

客户端的带代码如下

    var a=$('a').value;
    var b=$('b').value;
    var querystring="c="+a+"&d="+b;
    var xhr = new XMLHttpRequest();
        xhr.open("get", "http://localhost:3000/multiply?"+querystring, true);
        xhr.withCredentials = true;//经测试这个貌似没有什么作用
        xhr.onload =function(){
        var data = xhr.responseText;
        $("result").innerHTML="这两个数的乘积为"+data;
    }
xhr.send();  

其他的跨域解决方案。还有比如window.name,iframe,img标签等方式

当然使用代理也是比较给力的,所谓代理就是相当于请求先发到当前页面所在的webserver上,在webserver上的编写一个程序转发请求到另一个本来要打算访问的服务器上,同时也可以将结果返回到浏览器端,这样浏览器就间接地访问了服务。同时代理 又分为 正向和反向两部分。。。。。额,话题扯远了。。

本博文的用到的Demo的源码