XMLHttpRequest cannot load http://server/arcgis/rest/info?f=json. Origin http://localhost:8080 is not allowed by Access-Control-Allow-Origin
在博客Hello World文章中提起过,以前在sinaapp中建立过wordpress博客,也写过一篇关于ArcGIS JavaScript API的文章,但是由于sinaapp开始收费以后,我的个人网站就没法访问了。今天在百度中发现尽然能搜索到此文章,而且,使用百度快照还能看见文章的内容,真是十分激动啊,于是决定趁此把文章转移到这个新博客来,呵呵。
文章内容主要是关于ArcGIS js Api v3.0版本报: XMLHttpRequest cannot load. Origin is not allowed by Access-Control-Allow-Origin 的错误。
在esri中国社区中有同学提到:,在arcgis js api3.0中,在添加图层的时候浏览器开发者工具中会输出这个错误:XMLHttpRequest cannot load . Origin is not allowed by Access-Control-Allow-Origin,虽然对整个程序运行没有影响,但是不太美观。而且这个错误2.x的版本是没有出现的。
看见这个错误可能有点经验的同学已经大致明白是个跨域问题了。对比了一下2.8和3.0 的源码(jsapi.js文件),发现在3.0版本的esri.request方法中多了这么一段代码:esri._detectCors(req.url)(当然还有其他的代码,只是这句是解决这个问题的关键)。还有在esri.config.defaults.io中多了这两个属性:corsDetection: true, _processedCorsServers: {},还有一个属性corsEnabledServers: [],这个属性可能平时关注的同学不多,但是其实这个属性在2.5的版本中就有了,主要作用是用来存放cross-origin resource sharing enabled server 的url地址,详见。
那什么是Cross Origin Resource Sharing (CORS)呢,这个在arcgis js api中又在哪用到了呢?关于CORS,大家可以google一下,英文不好,翻译的不标准怕对你产生误导作用。我的理解是,cors是在HTML5中才有的,实现javascript跨域访问的一个协议吧。当然这一点必须得你使用的浏览器和web服务器都得支持CORS。在arcgis js api中,可能做过编辑功能的同学都知道,在做编辑的时候会使用到代理来解决跨域请求的问题,详见。现在,在你的web服务器和使用的浏览器都支持CORS的情况下,遇到跨域请求的时候就不用再在你的应用程序中配置代理,只需要在esri.config.defaults.io.corsEnabledServers中添加已经配置过CORS的服务器地址就行。如:esri.config.defaults.io.corsEnabledServers.push(“servicesbeta.esri.com”);但是如果你的浏览器不支持CORS的话,还是得像以前那样设置代理页面。关于这一块可以参考:。
现在对CORS有一点了解了,回到代码中,我们可以看一下esri._detectCors这个函数到底做了些啥(可以打开jsapi.js.unconpressed.js查看没有压缩过的源码)。
01 | esri._detectCors = function (url) { |
02 | // I know we don't want to get used to the habit of using try-catch |
03 | // programming, but esri.request is a core part of the API. |
04 | // We don't want unexpected(*) error in the code below to affect |
05 | // normal response processing workflow (not to mention what we're doing |
06 | // below is an optimization - not a critical functionality) |
07 | // Note: the term "unexpected" means the developer overlooked something |
08 |
09 | var ioConfig = esri.config.defaults.io, |
10 | processed = ioConfig._processedCorsServers; |
11 | if (!ioConfig.corsDetection) { |
12 | return ; |
13 | } |
14 | try { |
15 | var origin = new dojo._Url(url); |
16 | origin = (origin.host + (origin.port ? ( ":" + origin.port) : "" )).toLowerCase(); |
17 | if ( |
18 | // Browser support |
19 | esri._hasCors && |
20 | // ServerInfo is available since version 10.0, but token service has |
21 | // issues prior to 10 SP1 |
22 | //this.version >= 10.01 && |
23 | // Interested in ArcGIS REST resources only |
24 | (url && url.toLowerCase().indexOf( "/rest/services" ) !== -1) && |
25 | // AND server not already known to support CORS |
26 | (!esri._hasSameOrigin(url, window.location.href) && !esri._canDoXOXHR(url)) && |
27 | // AND NOT already processed |
28 | !processed[origin] |
29 | ) { |
30 | //console.log("***************** esri._detectCors *********** ]", url); |
31 | //console.log("***************** [fetching server info] **************** ", origin); |
32 | processed[origin] = -1; |
33 | // TODO |
34 | // Can we use fetch "rest/services" instead of "rest/info"? This will allow |
35 | // 9.3 servers to get in the action. |
36 | // How reliable and fast is "rest/services" resource? |
37 |
38 | // If we use esri.request, it will use proxy to get the response. |
39 | // We don't want that - because we want to find out if cross-origin |
40 | // XHR works. So let's use dojo.xhrGet directly. |
41 | dojo.xhrGet({ |
42 | url: url.substring(0, url.toLowerCase().indexOf( "/rest/" ) + "/rest/" .length) + "info" , |
43 | content: { f: "json" }, |
44 | handleAs: "json" , |
45 | headers: { "X-Requested-With" : null } |
46 | }).then( |
47 | function (response) { |
48 | //console.log("REST Info response: ", arguments); |
49 | if (response) { |
50 | processed[origin] = 2; |
51 | // Add this server to corsEnabledServers list |
52 | if (!esri._canDoXOXHR(url)) { |
53 | ioConfig.corsEnabledServers.push(origin); |
54 | } |
55 | // Yes - response.error is also considered as confirmation for |
56 | // CORS support |
57 | } |
58 | else { |
59 | // Indicates no support for CORS on this server. Older servers |
60 | // that don't support ServerInfo will follow this path. |
61 | // Dojo returns null in this case. |
62 | processed[origin] = 1; |
63 | } |
64 | }, |
65 | function (error) { |
66 | //console.error("REST Info FAILED: ", error); |
67 |
68 | // Mark this server so that we don't make info request again |
69 | processed[origin] = 1; |
70 | } |
71 | ); |
72 | } |
73 | } |
74 | catch (e) { |
75 | console.log( "esri._detectCors: an unknown error occurred while detecting CORS support" ); |
76 | } |
77 | }; |
在这个函数中有这样一个请求dojo.xhrGet({…}),看见这一行代码顿时眼前一亮,这一个请求有可能就是这个错误的源, 问题应该就出在使用xhrGet来跨域请求中。那为什么代码会这样去请求呢?
我的理解这几段代码主要的意思就是:首先判断是否有需要测试浏览器和web服务器支持CORS;然后判断浏览器是否支持CORS、是否是ArcGIS REST发布的资源、暂时不清楚服务器是否支持CORS和这个服务器没有测试过这几个条件。如果满足这几个条件,就使用xhrGet去请求来测试服务器是否支持CORS,如果支持的话就将服务器地址添加到corsEnabledServers中,并将_processedCorsServers[服务器url]值设为2,在后面的程序中使用,如果不支持的话就设为1,当然也就抛出文章介绍的这个错误。
那如何解决这个问题呢?到此也明白了这个错误的原因和出处以及CORS是什么了吧。我的理解在代码中会发出这样一个请求,应该就是为了测试我们的服务器是否支持CORS,那在我们的程序中,如果我们已经知道用户使用的浏览器不支持CORS(比如说<IE10),或者我们的服务器暂不支持CORS,或者我们铁定要继续使用代理。那我觉得我们是不是就没必要去发送这个请求来测试服务器了吧。那这样的话可以使用下面的方法来避免这个错误:
在加载地图之前,添加如下代码:
1 | esri.config.defaults.io.corsDetection= false ; |
或者
1 | esri.config.defaults.io._processedCorsServers[“你的server url”]=1; |
当然,这只是我的个人见解,结合到你的实际情况可能未必正确。
关于该函数中具体的代码,你感兴趣的话可以再深入研究。这只是我的一些很浅的理解,如果你有更深的认识,欢迎批评指正。