解决js跨域问题
解决js跨域问题
2011年03月14日
解决js跨域问题 文章分类:Web前端
如何解决js跨域问题 Js跨域问题是web开发人员最常碰到的一个问题之一。所谓js跨域问题,是指在一个域下的页面中通过js访问另一个不同域下 的数据对象,出于安全性考 虑,几乎所有浏览器都不允许这种跨域访问,这就导致在一些ajax应用中,使用跨域的web service会成为一个问题。 解决js跨域问题,目前在客户端和服务端都有一些现成的解决方案,但这些方案并不能解决所有问题。下面我们先来看下有哪些常用的解决方案,并针对空间产品 对跨域问题的需求给出一个space自己的解决方案,希望能对其他产品组有借鉴意义。
客户端解决方案 如何在客户端解决js跨域问题几乎是所有web开发人员会首先考虑的。目前最常用的方法有2种:设置document.domain、通过script 标签加载。
设置document.domain 采用这种方法的前提是跨域请求涉及的两个页面必须属于一个基础域(例如都是xxx.com,或是xxx.com.cn),使用同一协议(例如都是 http)和同一端口(例如都是80)。例如,aaa.xxx.com里面的一个页面需要调用bbb.xxx.com里的一个对象,则将两个页面的 document.domain都设置为xxx.com,就可以实现跨域调用了。 另外,需要注意的是,这种方式只能用在父、子页面之中,即只有在用iframe进行数据访问时才有用。
通过script标签加载 对于浏览器来说,script标签的src属性所指向资源就跟img标签的src属性所指向的资源一样,都是一个静态资源,浏览器会在适当的时候自 动去加 载这些资源,而不会出现所谓的跨域问题。这样我们就可以通过该属性将要访问的数据对象引用进当前页面而绕过js跨域问题。 例如,在space的我的空间项目中,需要在hi域下管理中心页面中随机推荐几个热门模块给用户,由于热门模块的相关信息都在act域下的php模块中维 护,如果直接在hi域下通过ajax请求去获取act域下的推荐模块列表相关信息就出现js跨域问题。解决这个问题的最简单方法就是,在hi域下通过 script标签去访问act域提供的这个http接口:
当然,前提是act域的这个http接口必须是返回一段js脚本,如一个json对象数组定义的脚本: modlist = [ {"modname" : "mod1", "usernum" : 200, "url" : " /widget/info/1"}, {"modname" : "mod2", "usernum" : 300, "url" : " /widget/info/2"}, … ];
但script标签也有一定的局限性,并不能解决所有js跨域问题。script标签的src属性值不能动态改变以满足在不同条件下获取不同数据的需求, 更重要的是,不能通过这种方式正确访问以xml内容方式组织的数据。
服务端解决方案 从上面的说明可以看到,客户端的解决方案局限性太大,而且对于ajax跨域请求,无论两个域是否属于同个基础域,都无法在客户端加以解决。也就是 说,如果 我们要想在ajax请求中访问其他域下的数据,就只能通过服务端进行处理了。 服务端的解决方案的基本原理就是,由客户端将请求发给本域服务器,再由本域服务器的代理来请求数据并将响应返回给客户端。 最常用的服务器解决方案就是利用web服务器本身提供的proxy功能,如apache和lighttpd的mod_proxy模块。在百度内 部,transmit的分流功能也可以解决部分跨域问题。但这些方法都有一定的局限性,鉴于安全性等问题的考虑,space这边最后开发了一个专门用于处 理跨域请求代理服务的spproxy模块,用于彻底解决js跨域问题。 下面我们将以空间的开放平台为例,简单介绍下如何通过apache的mod_proxy、transmit的分流以及space的spproxy模块来解 决该跨域问题,并简单介绍下spproxy的一些特性、缺点及下一步的改进计划。 空间在展现每个UWA开放模块之前都必须请求该模块的xml源代码以进行解析,每个模块的源代码文件都是存放在act域下的/ow/uwa目录下,那么在 用户空间首页(hi域)中请求该xml文件时就会存在js跨域问题。要解决该问题,只能让js向hi域的web服务器请求xml文件,而hi域web服务 器则通过一定的代理机制(如mod_proxy、transmit分流、spproxy)向act域的web服务器请求文件。
利用apache的mod_proxy模块 如果apache是2.0系列版本,则可以通过在httpd.conf文件中增加以下配置加以解决: ProxyRequests Off Order deny,allow Allow from all ProxyPass /ow/uwa http://act.hi.baidu.com/ow/uwa
其中,ProxyRequests 指令关闭了mod_proxy的正向代理功能而启用反向代理功能,Proxy指令使得该配置对所有访问生效,ProxyPass指令使得对本域的/ow /uwa目录下的任何资源的访问都会在内部被转换为一个对act.hi.baidu.com域下的/ow/uwa目录下对应资源的代理请求。 这样,js就可以直接通过访问http://hi.baidu.com/ow/uwa/0/1/0/10001.xml 获取位于act域下的/ow/uwa/0/1/0/目录下的10001.xml文件。
如果apache是经过百度各产品线修改过的1.3版本,则需要mod_proxy和mod_rewrite模块一起配合来达到同样的目的。首先需要在 httpd.conf中增加以下Location指令: SetHandler proxy-server order allow,deny Allow from all
这样,对于本域下的/ow/uwa目录下的任何资源的访问都会首先由proxy-server这个handler(mod_proxy模块内部定义 的一个 handler)来处理,但光有这段配置还不行,因为还不proxy-server还不知道应该怎么处理,仅仅知道需要自己处理而已。这时还需要在配置段 中增加一个rewrite规则: RewriteRule ^/ow/uwa/(.*)$ http://act.hi.baidu.com/ow/uwa/$1?%{QUERY_STRING} [P,L]
Rewrite规则最后的[P,L]表明该rewrite是通过mod_proxy代理过去,而不是通过外部重定向过去。如果去掉P标志,即采用以下 rewrite规则: RewriteRule ^/ow/uwa/(.*)$ http://act.hi.baidu.com/ow/uwa/$1?%{QUERY_STRING} [L]
则响应返回给客户端时标明的资源uri将是重定向后的uri,在我们的例子中就是act.hi.baidu.com域的uri,则浏览器仍然会出现 js跨 域问题。 以上只是对apache的proxy功能的简单应用,更好更强大的介绍可以参考资料【1】和【2】。 Mod_proxy虽然强大,但我们并没有用它来解决跨域问题。首先,要使用它必须要求我们的每台前端机器都能够访问外网,否则我们就只能将请求代理到其 中一台前端机器上(通过机器名做内网域名进行rewrite或代理),而这显然是不可取的,因为我们的一个域名通常由很多前端机器组成,只代理到其中一台 机器会导致该机器压力与其他机器相比很不均衡,甚至撑不住压力,而给所有前端机器都加访问外网权限又可能会存在一些安全性策略问题(具体原因不清楚,但 op和sa显然是不会赞同这种做法)。其次,由于apache本身并没有很好的防ddos攻击机制,一旦有人通过代理去攻击目标域(比如说我们的竞争对手 的网站),则在目标域的web服务器上看来,攻击者就成了我们了,这样的事情发生时,我们就百口莫辩,跳进黄河也洗不清了。
利用transmit分流方案 用过transmit的产品线应该都知道,transmit除了用于防攻击之外,还有一个很重要的功能就是分流。有了分流功能,我们就可以将对特定 url 的访问分发给不同的apache处理,从而实现跨域访问的目的。 还是以空间开放平台的这个例子为例,假设我们的act域在jx机房内由jx-space-act00.jx和jx-space-act01.jx这两台机 器组成,apache的端口为8080,则只要我们在transmit的配置文件transmit_common.conf中增加以下配置: PP_APACHE_DIR : /ow/uwa/