# 1. 從跨域到 CORS
#### 1. 介紹
跨域,可能很多前端開發者都會遇到過,也可能知道有jsonp,iframe之類的跨域方法。不過要說這些方法之前,先得來說說什么叫跨域,為什么要跨域。
所謂跨域,顧名思義,跨到了另外的域,域不僅僅指的是不同的域名網站,可能同一個域名不同的端口號也算不同的域。瀏覽器是有規則的,只要協議、域名、端口有任何一個不同,都被當作是不同的域。協議指的是http,或者https等。
下面給出一個列表,指出不同域的情況:

這個叫瀏覽器的同源策略(same-origin policy),為什么要這樣規定呢,原因之一就是為了安全。
假如你在A網站,用一段js腳本就能訪問B網站,B網站憑什么讓你訪問,為什么瀏覽器能讓你隨便訪問別的網站呢,說不定B網站存在著各種危險,比如盜取你的密碼,跨域攻擊(CSRF)等,一切都不太合理。
但也是有例外的情況,有些情況是要訪問到別的網站的,比如加載一張圖片,這張圖片可能在別的網站,還有加載一個js文件,也是有可能在別的網站的,還有嵌入一個frame元素,也可以訪問到別的網站的內容。
所以跨域正是利用了上面幾點,比如jsonp就是利用的加載js文件的功能,比如在`<script src="..."></script>`中的src指定為目標網站的js,同理,還有跨域攻擊也可以利用`<image src="..." />`的功能。還有iframe更能直接加載別的網站的內容到自己的網站里來。
這前面幾種可以說是技巧,說句不好聽的就是瀏覽器的漏洞,瀏覽器并未從正面上支持跨域,而且上面的幾種跨域方法也是有各種局限,比如jsonp的方法,只能用GET方法,iframe方法是能直接加載內容到網站上,但是本網站和iframe的數據交互也是一個頭疼的地方。
畢竟從iframe加載內容到本站后,是存在著數據交互的,可以用`document.domain`,`window.name`,不過這些方法都能用,且有效,不過總不盡完美,存在著各種各樣的局限。
然而HTML5引進了一個叫window.postMessage方法來跨域傳送數據,這個倒是不錯,不過也是利用了iframe。
除此之外,還有flash,服務器代理等跨域方法,但是本章要介紹的是瀏覽器或服務器的跨域方法,它的名字叫CORS。
#### 2. CORS
CORS全稱是Cross-Origin Resource Sharing,跨域資源共享,這是瀏覽器的標準,也算是協議,基本上現代瀏覽器都支持,除了奇葩瀏覽器,例如IE8、IE9,只支持部分特性。
使用它,需要服務器端和客戶端兩方面的準備,服務器端我們選擇nginx作為測試,客戶端只是js罷了。
nginx服務監聽在localhost的8080端口,而現在有一個網站運行localhost的3000端口,需要跨域到nginx那臺服務器。
現在開始測試之旅,要在瀏覽器模擬跨域請求,只需三行js代碼。
```
var xhttp = new XMLHttpRequest();
xhttp.open("GET", "http://localhost:8080", true);
xhttp.send();
```
我使用的瀏覽器是chrome,打開它的開發者工具,在`console`里運行上面的代碼,效果如下:

上面的報錯已經提示得很明顯了,主要是下面這句話:
```
XMLHttpRequest cannot load http://localhost:8080/. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access.
VM162:4 XHR failed loading: GET "http://localhost:8080/".
```
錯誤中說`Access-Control-Allow-Origin`這個頭信息不存在于被請求的服務器中,來源域`http://localhost:3000`是不允許訪問該服務器的。
`XMLHttpRequest`這個可能很多開發者都明白,那是ajax請求利用的對象,利用它能發起ajax請求,但它的功能不僅僅是發起ajax請求,還能用于跨域,還有設置時限,FormData對象管理表單數據,文件上傳等功能,具體可以自行搜索相關的資料,在這里,它能發起跨域請求就可以了。
我們來看看這個請求相關的信息。

具體的頭信息如下:
```
Request Headers
Accept:*/*
Accept-Encoding:gzip, deflate, sdch
Accept-Language:en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4
Connection:keep-alive
Host:localhost:8080
Origin:http://localhost:3000
Referer:http://localhost:3000/
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.97 Safari/537.36
```
最重要的是`Origin:http://localhost:3000`這一行,它標明的是來源的域,這是請求頭信息,會傳給服務器。
我們來看下服務器。

可見,服務器還是正常響應200狀態的。也就是說,服務器端該怎么應答就怎么應答,只是被瀏覽器給阻止了。
瀏覽器是如何阻止的呢。主要是看響應頭部的信息。
```
Response Headers
Accept-Ranges:bytes
Connection:keep-alive
Content-Length:612
Content-Type:text/html
Date:Mon, 01 Feb 2016 07:17:54 GMT
ETag:"56988bc6-264"
Last-Modified:Fri, 15 Jan 2016 06:03:50 GMT
Server:nginx/1.8.0
```
它沒有發現有發現`Origin:http://localhost:3000`這個來源域名是被服務器所允許的。
我們在服務器端設置一下,讓`Origin:http://localhost:3000`這個來源域被允許訪問。
```
location / {
add_header 'Access-Control-Allow-Origin' '*';
}
```
``add_header 'Access-Control-Allow-Origin' '*'`表示允許任何來源域訪問nginx這臺服務器。
用`sudo nginx -s reload`重新加載服務器配置。
再來看下效果。

果然成功了,不再提示錯誤。
來看下響應的信息。
```
Response Headers
Accept-Ranges:bytes
Access-Control-Allow-Origin:*
Connection:keep-alive
Content-Length:612
Content-Type:text/html
Date:Mon, 01 Feb 2016 07:25:25 GMT
ETag:"56988bc6-264"
Last-Modified:Fri, 15 Jan 2016 06:03:50 GMT
Server:nginx/1.8.0
```
響應信息中多了這一行`Access-Control-Allow-Origin:*`。
原來,瀏覽器在用`XMLHttpRequest`發起跨域請求的時候,它在請求頭帶了`Origin`這個項,而服務器,在響應頭信息中是有響應`Access-Control-Allow-Origin`這項的,兩者比較一下,如果匹配,則請求成功,不匹配就不成功,不過,服務器那邊還是照常執行。
**在測試的時候需要注意的事,有可能會發現改了nginx的配置,瀏覽器發出的跨域請求卻沒生效,這可能是因為瀏覽器cache的原因,只要清除瀏覽器的cache,再重新發起請求就好了。**
下一節[CORS進階之Preflight請求(二)](http://www.rails365.net/articles/cors-jin-jie-zhi-preflight-qing-qiu-er)會介紹CORS更高階的內容,比如`Preflight請求`,`Access-Control-Allow-Methods`,`Access-Control-Allow-Headers`等。
完結。