今天在项目中要用到ajax提交数据到其它公司的接口,但ajax不允许跨域,折腾了一下午,把一些解决方法和踩到的坑记录一下。
一、浏览器
在chrome的快捷方式最后加上 --disable-web-security,注意前面要有个空格,且要加在引号后面。
"C:Program Files (x86)GoogleChromeApplicationchrome.exe" --disable-web-security
这样打开浏览器就会提示,但不影响使用。
您使用的是不受支持的命令行标记: --disable-web-security。稳定性和安全性会有所下降
只适用于chrome(其它浏览器没试过,不知有没有这样的参数),每台电脑的浏览器都要这样设置,不方便。
二、提交到后台
用ajax提交到后台(如C#的ashx),后台用WebClient之类的访问Url,返回数据给ajax的回调
多绕了一下,如果后台有封装好访问接口的公共方法的话,倒也不算麻烦。
三、jsonp
jsonp也是异步请求,也有回调,所以jquery把jsonp封装到了ajax中。但其实jsonp和ajax是两码事,ajax是XMLHttpRequest,而jsonp是html的标签。在chrome开发者工具中可以看到,ajax是xhr请求,而jsonp是算到script的js请求
jsonp是利用标签跨域的特点,生成一个新标签插入头部,来提交和返回数据。就像
<script src="http://www.abc.com?param=1&callback=CallbackFuc"></script>
标准使用方法,由于jsonp本质不是ajax而是<script>标签,所以只支持get。如果在参数里加上type:"POST",jquery会自动判断,同源的情况就用普通ajax进行post,不同源的情况自动转成get
$.ajax({ url : "http://www.abc.com?param=1", dataType : "jsonp", //表示使用jsonp jsonp : "callback", //传给服务器的值,为jsonpCallback服务的,两个值以&callback=jsonpCallback进行传递。不填的话默认就是callback,不想以此参数传递的话,可以填jsonp:false,这样就会生成jquery_xxxx之类的随机参数 jsonpCallback : "jsonpCallback", //和jsonp配合使用,服务器返回的结果是js字符串并直接执行:jsonpCallback(返回数据),jsonpCallback就是js的方法,数据就是该方法的参数 success : function(ret){ console.log("ok"); }, error : function(ret){ console.log("err"); } });
因为最后返回的是js代码,如jsonpCallback({"key":"value"}),所以不要success和error,直接在外面定义一个方法function jsonpCallback(){}也可以。(success是jquery帮我们封装好的,便于使用)
1、服务器端坑
一开始以为直接datatyepe:"jsonp"就可以了,没想到一直报错,无法解析。
本来返回的数据是json,如{"key":"value"},由于jsonp是返回直接执行的代码,所以要变成拼成jsonpCallback({"key":"value"})这样的字符串。
好在合作公司服务端接口的同事就在旁边,改一下就可以了,碰到其它不熟的公司,人家一般不会改。
2、循环异步jquery坑
项目是循环异步调用,一下子发若干个请求,发现回来时,有些成功有些失败,数目不定。。。
查了半天,总算发现一篇文章了,原来是jquery的bug。。。为避免广告,就不放原网址了,感谢原作者(原网站也没写作者,不知是不是转来的)
jquery已经将所有的调用封装成对象,调用起来很方便 js跨域写起来比较麻烦,尤其是传递参数的时候,需要手动拼接data ?&p1=1&p2=2 但是jquery的跨域有个bug,当同时发出大量请求瞬间返回结果时,会导致接收请求的时候丢失部分回调请求(实际上已经发送成功了) 例: 这种情况很常见,就是循环发送ajax请求么。 非跨域请求这么写没有问题,success会被调用100次,但是当dataType : "jsonp"时,success可能只会执行70次80次,剩下的请求没有成功也没有失败,用complete也抓不到。 浏览器会报错找不到callback,或无任何提示。有两个地方会引起这种问题 // Clean-up function jqXHR.always(function() { // Set callback back to previous value window[ jsonpCallback ] = previous; // Call if it was a function and we have a response if ( responseContainer && jQuery.isFunction( previous ) ) { window[ jsonpCallback ]( responseContainer[ 0 ] ); } }); 1.callback被清除,所以有时候会报出找不到callback这个方法 2.xmlHttp.readyState==4 同时发送请求的时候,浏览器的状态只有这么一个readyState当浏览器知道已经获取返回结果了,就不接收请求了 这个肯定是jquery的bug,使用js跨域没有问题
解决方法1:加上async:false,把异步改成同步就可以了,但是会卡浏览器,循环大、返回慢的时候很不好用。
经实践,加上异步也没用。。。
解决方法2:改成原生js请求jsonp
var script = document.createElement("script"); script.type = "text/javascript"; script.src = "http://www.abc.com?param=1&callback=jsonpCallback"; //src可以动态生成 var head = document.getElementsByTagName("head")[0]; head.insertBefore(script, head.firstChild);
定义好function jsonpCallback(){},回调时就会执行(因为返回的是jsonpCallback(数据)这种形式,直接执行js代码)
3、jquery自动转换的坑
被这个坑害惨了,可以说一晚上都被它误导。
前面说过,jquery把jsonp封装到ajax中,其实两者完全不同。我调试时,本身又作服务端又作客户端,实际上它是把jsonp当作ajax进行发送(chrome开发者工具中可以看到,是走xhr)
根据同源策略,同一域不同端口也算跨域,好在机上IIS布了两个端口用于两个项目的调试,把服务端布到另一个项目上,这样调试时jquery就会自动识别使用jsonp(开发者工具看到走的是js)
四、改进jquery的jsonp
虽然原生的是可以解决问题,但平时jquery用习惯了,尝试着改进一下。由于主要是因为瞬间大量并发请求造成jquery的Bug,那么就在这里面做文章
1、延时执行
前端
var interVal=0; for(var i=0;i<1000;i++){ interVal += 1000; //这个值根据数据返回速度进行设置,快的话200也可以 setTimeout(function(){ interVal += 1000; $.ajax({ //省略 }); },interVal) }
后端
Thread.Sleep(500); Response.Write("jsonpCallback({"key":"value"})");
用Thread.Sleep来模拟网络阻塞
如果网络状况好,返回数据快,interVal可以设成每次递增200。这个值要大于数据请求所用的时间,不然还是会并发导致jquery的Bug
这个方法可以凑合着用,特别是对一些后台的自动任务,间隔设大一些无所谓,让它慢慢跑。偶尔出一些错也不要紧,下次再循环时还会去请求。
2、响应式
使用$.Deferred()配合$.when()、$.then()、$.done()、$.fail(),一个完成后再调用下一个。
比较高级的用法,之前有在项目中试用过,好像还是会一口气把循环里的ajax请求全发出去,而不是一个个排队发。这里就不尝试了
3、递归
循环ajax时非常好用的手段,也是踩了无数坑研究出来的,成就感非常强。
先执行第一个ajax,在success和error里,都要执行递归,然后把计数器++,根据计数器==循环总数进行判断是否完成循环。
递归的好处就是界面上可以友好的显示正在执行第几个,每个的结果是什么,而且是按顺序执行的(不像发一堆ajax出去,回来时零零散散的),还可以随时中断。
缺点就是多重ajax时,每一个都要判断error并递归,如果漏了,在那个环节出错的话,就不会继续下去。
这里也懒的再尝试了