ajax解决跨域方法集合

对于跨域的GET请求,我们最常用的是jsonp的方式,jQuery的ajax方法也对jsonp也有很好的封装,我们甚至可以利用http.getJSONP(url,data,callback)这样简洁的方式让开发人员只关注请求的url,数据以及回调方法。但是如果传输的数据量比较大,或者数据信息比较敏感的话,则需要POST大神出手了。那么跨域的post请求是否也能做到如此优雅地调用方式呢?
现在假设在a.com有个login.html页面,我们要用户用户名密码提交到b.com/post接口做校验,校验结果返回a.com/login.html做提示。我们也来打造这样一个方法

/* 跨域post请求方法
 * url 请求地址
 * data post表单数据
 * callback 回调方法
 */
http.postJSONP = function(url,callback) {  
    //TO DO
}

//请求方式
http.postJSONP('b.com/post',{  
    name: "garygao",password: "******"
},function(json) {
    //TO DO json
});

//或者像jsonp一样
http.postJSONP('b.com/post?postcallback=xxoo',password: "******"
});
//postcallback回调方法
function xxoo(json) {  
    //TO DO json
}

要实现这样一个方法,我们有两个难点:
1、如何实现跨域的post请求?
2、如何捕获回调数据,优雅地执行?
如何实现跨域POST请求?

1、CORS
利用CORS,让目标服务器设置标头"Access-Control-Allow-Origin:xxx.com ",表示允许设定的域向我们的服务端提交请求。
优点:W3C标准,配置简单;
缺点:需要服务端配合,IE67不支持

2、Server Proxy
当前域实现一个代理,所有向外部域名发送的请求都径由该代理中转。
缺点:每个使用方都需要部署代理,数据中转低效,对js有侵入。

3、Flash Proxy
服务端部署跨域策略文件crossdomain.xml,页面利用不可见的swf跨域post提交数据实现跨域通信。
优点:兼容性好,传输数据量大;
缺点:依赖flash。

4、Invisible Iframe
概述:通过js动态生成不可见表单和iframe,将表单的target设为iframe的name以此通过iframe做post提交。
优点:兼容性佳,原生JS即可完全实现;
缺点:无法直接读取响应内容

综合对比四种方式,还是第四种比较给力,兼容性好,不需要做服务器配置也不依赖Flash文件

注:需要应用页面、proxy.html、以及后台返回的数据需要<script>包住window.name的赋值,虽然很奇怪,但是这样也是为了让前端容易获取后台返回的数据。

我在本地进行测试的时候,一个服务器配置了两个域名,这样就可实现跨域访问,比如在hosts中这样写:

127.0.0.1 a.test.com

127.0.0.1 b.test.com

文件夹中建两个文件夹cross_domain_a和cross_domain_b ,前者中放应用页面(请求的页面)和proxy.html,后者放后台的程序即返回给前台的数据。另proxy.html是一个空白页面。原理:

利用window.name可以跨域访问,且值会保持不变,在前台中用脚本生成一个form表单和iframe,form.target= iframe.name,form.action = url(此url就是后台提供的接口),这样提交到后台的数据后台可以接收到,另后台的返回的数据也保存在了window.name中,且form提交完成之后,将location置为同域,这样就间接的解决掉了跨域的问题。

下面是前端js代码

var http = {};

    http.postJSONP = function(url,fn) {
        var form = document.createElement("form");
        form.id = form.name = 'postForm';

        //创建表单数据
        if (data) {
            for(var key in data) {
                var input = document.createElement("input");
                input.type = "hidden";
                input.name = key;
                input.value = data[key];
                form.appendChild(input);
            }
        }

        //创建iframe
        var iframe = null;
        //try&catch是为了解决IE67创建iframe新开窗问题
        try {
            iframe = document.createElement('<iframe name="postIframe">');
        } catch (ex) {
            iframe = document.createElement('iframe');
        }
        iframe.id = iframe.name = "postIframe";
        iframe.width = "1";
        iframe.height = "1";
        iframe.style.display = "none";
        document.body.appendChild(iframe);

        //表单提交
        document.body.appendChild(form);
        form.action = url;
        form.target = iframe.name;   //把表单的target设为iframe的name
        form.method = "post";
        form.submit();

        //iframe.src='http:'
        //事件处理
        if(iframe.attachEvent){
            iframe.attachEvent("onload",_loadFn);
        }else{
            iframe.onload = _loadFn;
        }

        //记录iframe的加载状态
        iframe.state = 0;
        function _loadFn() {
            if (iframe.state === 1) {
                var data = '';
                //获取window.name保存的数据
                try{
                    data = iframe.contentwindow.name;
                }catch(e){
                    console.log(e);
                }

                var json = data;
                try {
                    json = Kg.JSON.parse(data);
                } catch(e){}

                //执行回调方法
                _callback(json);
                //iframe清除
                iframe.onload = null;
                document.body.removeChild(iframe);
            } else if (iframe.state === 0) {
                //form提交完成之后,将location置为同域
                iframe.removeAttribute('name');//解决IE10+获取不到window.name的问题
                iframe.contentwindow.location = "blank.html";
                iframe.state = 1;
            }
        }
        function _callback(json) {
            //认执行传入的回调方法
            if (fn && typeof fn === "function") {
                fn(json);
            } else {
                //没有回调方法则解析postcallback
                var svalue = url.match(new RegExp("[\?\&]postcallback=([^\&]*)(\&?)"));
                fn = window[svalue ? svalue[1] : svalue];
                if (fn && typeof fn === "function") {
                    fn(json);
                }
            }
        }
        return false;
    }


    http.postJSONP('http://b.csdn.net/cross_domain/json.PHP',{
        name: "garygao",password: "******"
    },function(data){
        console.log(data);
    });

proxy.html就是一个空白的页面
<!DOCTYPE html>
<html>
<head>
    <Meta charset="UTF-8">
    <title>空白页面</title>
</head>
<body>

</body>
</html>

test.PHP页面后台提供的接口),这里用的是PHP:

<?PHP  

$name = $_POST['name'];
   if($name)
   {
      echo '<script type="text/javascript">'."\n";
      echo 'window.name="{succ:true,name:Mike}"';
      echo '</script>'."\n";
   }
?>

这里只是为了测试,后台返回的数据是json格式的字符串,可以用JSON.parse()或者eval()方式把数据转化成json对象,然后进行数据的操作。

文中提到跨域方案无非就是利用了两种比较经典的解决方式,即隐藏iframe+form和window.name,相对于其他方式,文中的方案算是兼容性比较强的方式了,也无需修改服务器配置和引入额外的Flash文件,唯一不太友好的地方是后端的返回方式比较奇怪,要用<script>包住window.name的赋值,但是这样做也是为了前端更加友好地处理回调数据。

相关文章

IE6是一个非常老旧的网页浏览器,虽然现在很少人再使用它,但...
PHP中的count()函数是用来计算数组或容器中元素的个数。这个...
使用 AJAX(Asynchronous JavaScript and XML)技术可以在不...
Ajax(Asynchronous JavaScript and XML)是一种用于改进网页...
本文将介绍如何通过AJAX下载Excel文件流。通过AJAX,我们可以...
Ajax是一种用于客户端和服务器之间的异步通信技术。通过Ajax...