首先歡迎你們關注個人Github博客,也算是對個人一點鼓勵,畢竟寫東西無法得到變現,能堅持下去也是靠的是本身的熱情和你們的鼓勵。最近工做上事情比較繁多致使博客一度斷更,爲了挽救慘淡的關注量併兼顧本身有限的時間,準備近期多推出一些有關前端的基礎知識學習,一塊兒夯實基礎。但願你們多多關注呀!
javascript
跨域問題在Web開發中一直都是一個很是常見的問題,但在平常工做說實話使用頻率並無那麼多,再加上重使用輕原理致使我對跨域問題理解的一直很是的淺顯,藉此機會讓咱們好好探索一下。前端
跨域問題的起源於衆所周知的瀏覽器同源策略。做爲現在Web安全基石的同源策略,早在上個世紀九十年代由網景公司提出,當時僅僅只是針對於Cookie
的訪問,即不一樣源的網頁之間Cookie
不能共享。後來同源策略變得更加嚴格,安全性也逐步提升,升級爲:java
說了這麼多同源,那麼何爲同源。一般狀況下,若是一個源擁有相同的協議(protocol)、主機(host)、端口(port),則稱其爲同源。固然咱們說了一般狀況下,也就意味着這個方面有例外存在,IE毫無疑問的在這個方面承擔了它固有的職責。IE並無將端口號加入到同源策略的組成部分,所以http://host:81
和http://host:80
是屬於同源的。雖然同源的安全限制是必要的,可是同時也帶了束縛,假設我確實須要向不一樣源的地址發送請求該怎麼辦呢?那就回到了咱們所要討論的跨域問題。git
往往提起跨域問題,第一個想到的就是JSONP了。JSONP與JSON雖然看起來很像,但卻有本質區別
。JSONP全稱是JSON with Padding,並非一種新的數據格式,而是數據格式JSON的一種「使用模式」。瀏覽器的同源限制對含有src
屬性的元素(例如: script
、img
)不起做用,JSONP就是利用了script
的這個特性。github
前面咱們說了JSONP利用的是script
標籤,繞過瀏覽器的同源策略,試想咱們經過一個URL獲取JSON數據是可能的。然而JSON數據倒是不可執行的,所以JSONP經過將返回的JSON數據用函數包裹來實現執行的目的,這也就是JSONP中P表示padding(包裹)的含義。由於須要函數包裹數據,因此先後端須要提早約定好函數名,咱們通常會在請求的URL中帶上函數名稱做爲參數。例如請求接口:ajax
http://api.demo.com/data?userid=1&jsonp=parseResponse
服務器會將對應的數據填充到函數parseResponse
:express
parseResponse({"Name": "小明", "Id" : 1823, "Rank": 7})
咱們實現一個最簡單的函數用來建立一個JSONP請求:json
function createJSONPRequest(url, callbackName){ var script = document.createElement("script"); script.src = url +"?callback=handleResponse"; document.body.appendChild(script); } function handleResponse(res){ console.log(res); } createJSONPRequest("http://localhost:3001/jsonp", "parseResponse")
而後咱們用express.js來響應這個JSONP請求:後端
// 服務器端口號:3001 app.get('/jsonp', (req, res) => { var callbackName = req.query.callback; var mockData = { name: "MrErHu" }; res.send(`${callbackName}(${JSON.stringify(mockData)})`); })
運行時你會發現瀏覽器會正確打印出跨域訪問的數據,說明咱們的跨域請求成功。api
固然咱們並不須要在前端手動去實現該函數,不少第三方庫已經封裝了JSONP的請求,咱們以JQuery提供的Ajax理由爲例,上面的請求用JQuery去實現代碼是:
// 客戶端端口號:3000 $.ajax({ url: "http://localhost:3001/jsonp", type: "GET", dataType: "jsonp", jsonpCallback: "parseResponse", success: function (res) { console.log(res); } });
咱們能夠看到在JQuery中提供的ajax
函數中經過配置dataType
爲jsonp
,則能夠實現JSONP請求,須要注意的是JQuery只是將其封裝在ajax
函數內,兩者實質上有本質區別。
瞭解JSONP的實現原理,咱們知道這玩意確定不會存在瀏覽器兼容性問題,畢竟我不相信會存在不支持script
標籤的瀏覽器。可是由於正是經過script
標籤實現的,JSONP也就只能支持GET
請求,那麼若是咱們想實現POST
請求的跨域有什麼好的辦法嗎?
CORS是Cross-origin resource sharing的縮寫,即跨域資源共享,屬於W3C標準,容許跨域發送XMLHttpRequest
請求,支持多種HTTP Method。與JSONP的原理不一樣的是,CORS採用的是先後端HTTP表頭協商的方式(我本身起的名字)判斷是否容許跨域訪問,而且相比與JSONP來講,CORS須要客戶端和服務端共同支持,而且整個過程由瀏覽器自動完成。對於前端開發者而言,跨域的CORS通訊與普通的Ajax通訊沒有差異,整個跨域處理的過程主要集中在服務器端。CORS通訊分紅簡單請求(simple request)和非簡單請求(not-so-simple request)兩種模式。
所謂的簡單請求是指,請求方法屬於: HEAD
、GET
、POST
之一,而且HTTP的頭信息不超出如下幾種字段:
Accept Accept-Language Content-Language Last-Event-ID Content-Type:僅限於三個值:application/x-www-form-urlencoded、multipart/form-data、text/plain
若是不知足以上任一條件,均屬於非簡單請求。對於簡單請求,瀏覽器會直接發送CORS請求。當咱們使用Ajax發送一條跨域請求,瀏覽器會在HTTP請求頭(Request Headers)中增長Origin
屬性:
Origin
屬性用來代表當前的請求來自於哪一個源(協議、主機、端口)。若是服務端支持CORS跨域,則須要在響應頭中返回如下屬性:
Access-Control-Allow-Origin: Access-Control-Allow-Credentials Access-Control-Expose-Headers
Access-Control-Allow-Origin
: 表示跨域的訪問的源,其中"*"表示容許來自任何源的請求跨域訪問Access-Control-Allow-Credentials
: 表示跨域訪問是否容許帶有Cookie信息,該值僅能夠容許設置爲true
。若是服務器不容許請求攜帶Cookie,則不須要發送該屬性。值得注意的是,攜帶的Cookie信息僅僅只是所跨域的服務器域名設置的Cookie,不能攜帶其餘域名下的Cookie信息,Cookie仍然循序同源策略。而且還須要知足Access-Control-Allow-Origin
指定的域與當前的請求的域徹底一致(不能是"*")以及在XMLHttpRequest顯式設置withCredentials
屬性爲true
,表示瀏覽器也容許發送Cookie。Access-Control-Expose-Headers
: 該字段可選,表示瀏覽器能夠從該XMLHttpRequest
請求中拿到的響應頭信息。默認僅能拿到Cache-Control
、Content-Language
、Content-Type
、Expires
、Last-Modified
、Pragma
這六個屬性,若是想取得其餘的屬性,必須在該屬性中指定。咱們用express.js
模擬一下:
// 服務器端口號:3001 app.post('/cors', (req, res) => { var mockData = { name: "MrErHu" }; var origin = req.get("origin"); res.set("Access-Control-Allow-Origin", origin); res.set("Access-Control-Allow-Credentials", true); res.send(JSON.stringify(mockData)) });
當咱們前端Ajax請求:
// 瀏覽器端口號:3000 $.ajax({ url: "http://localhost:3001/cors", type: "POST", success: function (res) { console.log(res); } });
你會發現該條請求已經不會出現跨域問題,能夠正確訪問到數據。
對於非簡單請求,瀏覽器會預先發送一次預檢請求,詢問服務器是否容許跨域訪問以及是否容許HTTP方法,只有獲得確定答覆後,瀏覽器纔會發出正式的HTTP請求。好比咱們跨域向服務器發送一次PUT
請求:
// 瀏覽器端口號:3000 $.ajax({ url: "http://localhost:3001/cors", type: "PUT", success: function (res) { console.log(res); } });
你會發現瀏覽器首先會發送OPTIONS請求去預檢本次跨域請求。
當預檢請求檢測了Origin
、Access-Control-Request-Method
和Access-Control-Request-Headers
屬性後就會正式發送請求。須要注意的是,不只僅是上述三個屬性,預檢請求還會返回Access-Control-Max-Age
屬性用來表示該條預檢請求的有效期,在有效期內,瀏覽器不會再次發送預檢請求,僅會使用該條緩存的預檢請求。
咱們用express.js
來模擬本次請求:
// 服務器端口號:3001 app.options('/cors', (req, res) => { var origin = req.get("origin"); res.set("Access-Control-Allow-Origin", origin); res.set("Access-Control-Allow-Credentials", true); res.set("Access-Control-Allow-Methods", "PUT"); res.set("Access-Control-Max-Age", 24 * 60 * 60 * 1000); res.send(); }); app.put('/cors', (req, res) => { var mockData = { name: "MrErHu" }; var origin = req.get("origin"); res.set("Access-Control-Allow-Origin", origin); res.set("Access-Control-Allow-Credentials", true); res.send(JSON.stringify(mockData)) });
你就會發現本次PUT
跨域請求成功,正確返回數據,達到了咱們跨域的要求。
與JSONP相比,CORS支持更多的HTTP方法而且整個過程由瀏覽器自動完成,可是僅僅只兼容IE10及以上的瀏覽器,若是須要兼容老版本IE瀏覽器,CORS可能就不適合你了。
咱們這邊文章着重講述了兩種跨域的基本原理,但實際上圍繞跨域問題還有不少值得學習的地方,好比後端對跨域請求的處理,以及前端跨域帶來種種的安全性問題。其實不只僅是JSONP與CORS,Websocket也能實現跨域請求,之後有機會能夠學習一下,最後仍是歡迎你們關注個人博客,水平欠佳,但願體諒,願一同進步。