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 loadhttp://server/arcgis/rest/info?f=json. Origin http://localhost:8080 is not allowed by Access-Control-Allow-Origin 的錯誤。
在esri中國社區中有同學提到:關於arcgis_js_api 3.0的問題,高手指教,在arcgis js api3.0中,在添加圖層的時候瀏覽器開發者工具中會輸出這個錯誤:XMLHttpRequest cannot load http://server/arcgis/rest/info?f=json. Origin http://localhost:8080 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) with the ArcGIS API for JavaScript。
那什么是Cross Origin Resource Sharing (CORS)呢,這個在arcgis js api中又在哪用到了呢?關於CORS,大家可以google一下,英文不好,翻譯的不標准怕對你產生誤導作用。我的理解是,cors是在HTML5中才有的,實現javascript跨域訪問的一個協議吧。當然這一點必須得你使用的瀏覽器和web服務器都得支持CORS。在arcgis js api中,可能做過編輯功能的同學都知道,在做編輯的時候會使用到代理來解決跨域請求的問題,詳見esri.config.defaults.io.corsEnabledServers。現在,在你的web服務器和使用的瀏覽器都支持CORS的情況下,遇到跨域請求的時候就不用再在你的應用程序中配置代理,只需要在esri.config.defaults.io.corsEnabledServers中添加已經配置過CORS的服務器地址就行。如:esri.config.defaults.io.corsEnabledServers.push(“servicesbeta.esri.com”);但是如果你的瀏覽器不支持CORS的話,還是得像以前那樣設置代理頁面。關於這一塊可以參考:Cross-Origin Resource Sharing (CORS) – Edit Point Data。
現在對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去請求http://server/arcgis/rest/info?f=json來測試服務器是否支持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; |
當然,這只是我的個人見解,結合到你的實際情況可能未必正確。
關於該函數中具體的代碼,你感興趣的話可以再深入研究。這只是我的一些很淺的理解,如果你有更深的認識,歡迎批評指正。