成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

JavaScript 中的 HTTP 跨域請求

haitiancoder / 1631人閱讀

摘要:而如果跨域請求是從腳本里面發(fā)出去的,由于腳本具有高度靈活性,瀏覽器出于安全考慮,會根據(jù)同源策略來限制它的功能,使得正常情況下,腳本只能請求同源的資源。反之,則稱為跨域請求,需要遵守機制。非簡單跨域請求在發(fā)出請

原文發(fā)布于我的博客:https://blog.serenader.me/htt...

自從我接觸前端以來,接手的項目里面很大部分都是前后端分離的,后端只提供接口,前端根據(jù)后端接口渲染出實際頁面。個人覺得這是一個挺好的模式,前后端各自負(fù)責(zé)各自的模塊,分工明確,而且也給前端更大的發(fā)揮空間。

與以前套模板的模式不同,前后端分離以后,前端跟后端的溝通絕大部分都是通過前端主動向后端發(fā)起請求來完成的。而前端的請求又絕大部分是由 Ajax 構(gòu)成的,Ajax 是一種非常方便的獲取數(shù)據(jù)的方式。但是,一旦 Ajax 碰上跨域,那么問題就會麻煩很多。這篇文章主要梳理了我在項目開發(fā)里面碰到的一些關(guān)于跨域請求的問題,當(dāng)然也會有一些關(guān)于跨域請求的一些背景知識。PS:文末有個小彩蛋哦?

嚴(yán)格來說,跨域請求并不僅僅只是 Ajax 的跨域請求,而是對于一個頁面來說,只要它請求了其他域名的資源了,那么這個過程就屬于跨域請求了。比如,一個帶有其他域名的 src 標(biāo)簽,以及頁面中引入的其他第三方的 CSS 樣式等。

對于 img 以及 CSS 而言,跨域請求本身并沒有更多的安全問題,因為這些請求都屬于只讀請求,并不會對源資源造成副作用。而如果跨域請求是從腳本里面發(fā)出去的,由于腳本具有高度靈活性,瀏覽器出于安全考慮,會根據(jù)同源策略來限制它的功能,使得正常情況下,腳本只能請求同源的資源。如果頁面確實需要通過腳本請求其他網(wǎng)站的資源,那么就應(yīng)當(dāng)在跨域資源共享(CORS)的機制下工作。

等等同學(xué),什么叫做同源策略?

同源策略(Same-origin policy)

對于兩個頁面(資源)而言,只要他們滿足以下三個條件則稱他們符合同源策略:

協(xié)議相同

端口相同

域名相同

另外,about:blankjavascript: 繼承加載這些資源的頁面的 origin。data: 的資源不同,自身會擁有一個空的安全的上下文。

另外,子域可以通過JS 設(shè)置 document.domain 來通過同源策略。如:

在子域 http://a.example.com/test.html 的頁面中,通過 JS 設(shè)置 document.domain="example.com" ,則當(dāng)前頁面與 http://example.com/page.html 符合同源策略。

簡單的說,對于頁面 http://www.example.com/page1.html 來說,以下頁面與它都不符合同源策略,腳本無法直接請求這些資源:

https://www.example.com/page1.html : 協(xié)議不同

http://www.example.com:81/page1.html : 端口不同

http://another.example.com/page1.html : 域名不同

那么,什么又是 CORS 呢?

CORS(Cross-Origin Resource Sharing)

CORS 本質(zhì)上是規(guī)定了一系列的 HTTP 頭來作為判斷腳本是否能夠?qū)崿F(xiàn)跨域請求。在了解這些請求頭之前,先來看看跨域請求有哪些類型。

通過腳本來發(fā)出請求有兩種方式,一種是通過創(chuàng)建 XMLHttpRequest 的方式來發(fā)出請求,另外一種是通過 fetch API 來實現(xiàn)請求。

一般來說,跨域請求可以大致分為兩種,其中一種稱之為簡單的請求,其符合以下條件:

請求的方法是 GET、 POSTHEAD 其中之一。

除了瀏覽器自動帶上的請求頭(如 Connection User-Agent 等)之外,只允許下面幾種請求:頭

Accept

Accept-Language

Content-Language

Content-Type

Content-Type 請求頭的值只能是 application/x-www-form-urlencoded、 multipart/form-data、 text/plain 其中之一。

反之,如果有違背上面三條規(guī)則中的任意一條,那么即不是簡單的跨域請求。非簡單的跨域請求相對于簡單的跨域請求來說區(qū)別在于,請求在發(fā)出去之前,瀏覽器會先發(fā)送一個 preflighted 請求,用來向服務(wù)器端確認(rèn)接下來要進(jìn)行的請求是否是被允許的。

Preflight 請求

在實際項目開發(fā)中,在使用 XHR 或者 fetch API 請求接口的時候很多情況下都會帶上一些額外的特殊請求頭,或者使用特殊的 HTTP 方法,如 PUT、DELETE 等(常見于 Restful 接口)。由于多了額外的請求頭或者使用了特殊的 HTTP 方法,瀏覽器就將這些請求視為非簡單的跨域請求,將會在實際請求發(fā)出去之前先自動發(fā)出一個 preflight 請求,也就是一個 OPTIONS 請求。

OPTIONS 請求會將當(dāng)前的跨域請求所使用的特殊 HTTP 請求頭和 HTTP 請求方法發(fā)送給服務(wù)器端,如 Access-Control-Request-MethodAccess-Control-Request-Headers 。服務(wù)器端接收到 OPTIONS 請求后返回相應(yīng)的響應(yīng)頭。瀏覽器根據(jù)返回的響應(yīng)頭再來判斷該跨域請求是否被允許的。當(dāng)瀏覽器判定 OPTIONS 請求通過了,真正的請求才會發(fā)出。如以下則是一個帶有 OPTIONS 請求以及真正的 GET 請求的響應(yīng)頭和請求頭:

OPTIONS /api4 HTTP/1.1
Host: us1.serenader.me:3333
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Access-Control-Request-Method: PUT
Origin: http://us1.serenader.me:3334
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36
Accept: */*
Referer: http://us1.serenader.me:3334/
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4,fr;q=0.2
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET,POST,PUT,DELETE
Content-Type: text/html; charset=utf-8
Content-Length: 2
ETag: W/"2-REvLOj/Pg4kpbElGfyfh1g"
Date: Thu, 19 Jan 2017 15:21:15 GMT
Connection: keep-alive
PUT /api4 HTTP/1.1
Host: us1.serenader.me:3333
Connection: keep-alive
Content-Length: 0
Pragma: no-cache
Cache-Control: no-cache
Origin: http://us1.serenader.me:3334
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36
Accept: */*
Referer: http://us1.serenader.me:3334/
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4,fr;q=0.2
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: text/html; charset=utf-8
Content-Length: 2
ETag: W/"2-REvLOj/Pg4kpbElGfyfh1g"
Date: Thu, 19 Jan 2017 15:21:15 GMT
Connection: keep-alive

了解了簡單跨域請求以及會發(fā)出 preflight 請求的非簡單跨域請求之后,我們再來看看究竟是哪些 HTTP 頭在決定這些跨域請求的「宿命」。

為了幫助讀者更好地理解這些 HTTP 頭的作用,我編寫了一個簡單的 demo ,開源在了 GitHub 上,感興趣的可以到 這個鏈接查看代碼,或者訪問這個在線 demo 預(yù)覽效果:http://us1.serenader.me:3334/。記得加載完頁面后打開 Chrome 的控制臺來查看詳細(xì)的請求信息。

Access-Control-Allow-Origin

Access-Control-Allow-Origin 是一個響應(yīng)頭,它指定了當(dāng)前資源允許被哪些域名的腳本所請求到。

跨域請求(無論簡單請求還是非簡單請求)在發(fā)出時都會帶上 Origin 請求頭,用來表明當(dāng)前發(fā)出請求的是哪一個域名。此時服務(wù)器端的響應(yīng)頭里面必須包含一個 Access-Control-Allow-Origin 并且該值匹配 Origin 請求頭,這時候該跨域請求才有可能成功。否則一律失敗。

Access-Control-Allow-Origin 是第一道門檻。其值的匹配規(guī)則是:

如果其值是通配符 * 的話,則允許所有的域名進(jìn)行跨域請求

如果其值是指定的某個固定域名,那么只允許該域名進(jìn)行跨域請求,其他域名將會失敗

如果其值是帶有通配符的域名,如 *.example.com ,那么則允許該域名以及該域名的子域名進(jìn)行跨域。

具體可以觀看 demo,demo-0 展示了當(dāng)腳本請求沒有配置跨域頭的接口時,請求被瀏覽器攔截了的情況:

demo-1 則展示了接口有配置 Access-Control-Allow-Origin 響應(yīng)頭,但是并非腳本請求的域名,此時瀏覽器會報這種錯:

只有配置了正確的 Access-Control-Allow-Origin 響應(yīng)頭請求才能夠正常接收到響應(yīng),如demo-2,此時的請求頭和響應(yīng)頭為:

GET /api2 HTTP/1.1
Host: us1.serenader.me:3333
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Origin: http://us1.serenader.me:3334
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36
Accept: */*
Referer: http://us1.serenader.me:3334/
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4,fr;q=0.2
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: text/html; charset=utf-8
Content-Length: 2
ETag: W/"2-REvLOj/Pg4kpbElGfyfh1g"
Date: Thu, 19 Jan 2017 15:03:33 GMT
Connection: keep-alive

對于簡單的跨域請求來說,通常只需要通過 Access-Control-Allow-Origin 這個響應(yīng)頭則可以請求成功(帶 cookie 等情況先不考慮,會在下面討論)。而當(dāng)請求不是簡單的跨域請求,情況就比較復(fù)雜。

Access-Control-Allow-Headers

Access-Control-Allow-Headers 是用來告訴瀏覽器當(dāng)前接口所允許帶上的特殊請求頭是哪些。這個 HTTP 頭一般會出現(xiàn)在 OPTIONS 請求的響應(yīng)頭中。

當(dāng)請求設(shè)置了一個特殊的請求頭而且所請求的接口并沒有配置 Access-Control-Allow-Headers 響應(yīng)頭時,會報如下錯誤,如 demo-3 所示:

上面的截圖展示了請求附帶了一個 X-Custom-Header 的請求頭,但是請求在 preflight 階段就失敗了,如果要讓請求成功完成的話,則必須在 OPTIONS 請求的響應(yīng)里面配上 Access-Control-Allow-Headers: X-Custom-Header。

Access-Control-Allow-Methods

與上一個 HTTP 頭相似,Access-Control-Allow-Methods 告訴瀏覽器當(dāng)前接口允許使用哪些 HTTP 方法去請求它。這個 HTTP 頭通常也是在 OPTIONS 請求的響應(yīng)頭中才有意義。當(dāng)沒有通過這個響應(yīng)頭時,會報這樣的錯誤:

同樣的,上面的截圖在 preflight 階段就失敗了。如果要讓請求成功執(zhí)行的話,那么需要配置響應(yīng)頭為:Access-Control-Allow-Methods: GET,POST,PUT。

Access-Control-Max-Age

由于 OPTIONS 請求的存在,對于一個非簡單請求來說,實際發(fā)出去的請求會有兩個。這多多少少會浪費帶寬,畢竟這個校驗應(yīng)該只會在第一次發(fā)生而已,一旦通過校驗,在接下來的一段時間里,再次請求該接口的話,那么實際上 OPTIONS 請求則沒有必要再發(fā)出。

好在,有個叫做 Access-Control-Max-Age 的響應(yīng)頭可以實現(xiàn)這樣的功能。這個響應(yīng)頭指定了請求一旦通過了 preflight 請求之后,會在多長時間內(nèi)無須再次觸發(fā) preflight 請求。從而達(dá)到減少實際請求,減少帶寬浪費的問題。

Access-Control-Allow-Credentials

默認(rèn)情況下, 任何跨域請求都不會帶上任何身份憑證的,這些身份憑證包括:

cookie

與身份認(rèn)證相關(guān)的請求

TLS 客戶端證書

然而,在大多數(shù)情況下,我們需要請求帶上 cookie ,那么則需要開啟跨域請求的 withCredentials 選項。

想要手動開啟傳輸 cookie 的話,有以下方法;

XHR:為 XHR對象設(shè)置 xhr.withCredentials = true 。

fetch: 傳入的參數(shù)選項里面開啟 credentials fetch(url, { credentials: "include" })

開啟了 withCredentials 之后,請求在發(fā)出去的時候就會默認(rèn)加上 Cookie。

然而,除了需要在前端中手動開啟 withCredentials 之外,服務(wù)器端也需要有相應(yīng)響應(yīng)頭支持,請求才會成功。

Access-Control-Allow-Credentials 這個響應(yīng)頭則是表明了當(dāng)前請求的資源是否允許附帶身份憑證。當(dāng)其值為 true 時請求才成功,否則會失敗,失敗內(nèi)容如下:

可以參考 demo-7觀看請求頭以及響應(yīng)頭。

另外,一旦開啟了 withCredentials 選項,服務(wù)器端的 Access-Control-Allow-Origin 響應(yīng)頭就不能是通配符,只能是固定的一個域名,否則會請求失敗。具體錯誤內(nèi)容為:

demo-8 和 demo-9 分別演示了當(dāng)請求帶上 cookie 時,響應(yīng)頭配置為通配符的情況以及響應(yīng)頭有正確配置為具體域名的情況。

總結(jié)

總的來說,當(dāng)在腳本里面發(fā)出請求時,會有以下情況:

所請求資源的協(xié)議、端口或者域名如果與當(dāng)前發(fā)出請求的頁面地址一致,那么則符合同源策略,請求可以被正常發(fā)出。反之,則稱為跨域請求,需要遵守 CORS 機制。

所有跨域請求里面,服務(wù)器端必須返回 Access-Control-Allow-Origin 響應(yīng)頭,并且其值與請求中的 Origin 請求頭的值相匹配。此時請求才可以被允許,否則請求將會被瀏覽器攔截掉。

跨域請求分為兩種,一種是簡單跨域請求,另外一種是非簡單跨域請求。非簡單跨域請求在發(fā)出請求之前,瀏覽器會先發(fā)出一個 preflight 請求,即一個 OPTIONS 請求,用來驗證服務(wù)器端是否允許該請求的訪問。當(dāng) OPTIONS 請求成功時,才會繼續(xù)發(fā)送真正的請求。否則請求將會在 OPTIONS 階段便失敗了,后續(xù)真正的請求也不會發(fā)出去。

當(dāng)請求帶上了特殊的請求頭時,服務(wù)器端返回的 OPTIONS 請求的響應(yīng)必須包含 Access-Control-Allow-Headers 響應(yīng)頭,并且該值包含請求所帶上的特殊請求頭的名稱。這時候請求才會成功,否則會被瀏覽器攔截。

當(dāng)請求使用了特殊的 HTTP 方法,服務(wù)器端返回的 OPTIONS 請求的響應(yīng)必須包含 Access-Control-Allow-Methods 響應(yīng)頭,并且該值包含當(dāng)前使用的 HTTP 方法。如果沒有該響應(yīng)頭,或者當(dāng)前使用的方法并不在其值里面,則請求會被瀏覽器攔截。

因為非簡單請求每次完整請求一次資源實際上都會發(fā)出去兩個請求,為了減少 OPTIONS 請求發(fā)出的次數(shù),以便減少帶寬浪費,服務(wù)器端可以配置 Access-Control-Max-Age 來指定瀏覽器可以在多長時間內(nèi)對 OPTIONS 請求做緩存,使得一次請求成功后,下次請求相同的接口時不用再發(fā)出 OPTIONS 請求。

當(dāng)跨域請求需要帶上 cookie 等身份憑證時,需要手動開啟 withCredentials 選項,并且服務(wù)器端需要配置 Access-Control-Allow-Credentials 的響應(yīng)頭,否則請求將不會帶上任何身份憑證,或者當(dāng)沒有 Access-Control-Allow-Credentials 時請求會被瀏覽器攔截。

當(dāng)請求有帶上身份憑證時,服務(wù)器端除了需要配置 Access-Control-Allow-Credentials 響應(yīng)頭之外,Access-Control-Allow-Origin 響應(yīng)頭的值不能是通配符,必須是具體的某一個域名。否則會被瀏覽器攔截。

在以上 8 點當(dāng)中,值得注意的是第 3 點和第 8 點。

OPTIONS 請求是一個比較容易被人忽略的一個關(guān)鍵點,有一些后端人員在編寫接口的時候,往往只知道在接口的響應(yīng)頭里面寫入 Access-Control-Allow-Origin ,而沒有意識到 OPTIONS 請求的存在。特別是 OPTIONS 請求并不是每個跨域請求都會帶上的,這就導(dǎo)致了有些人會有疑問,為什么明明我發(fā)出去的是 GET 請求,結(jié)果卻是發(fā)出去了一個 OPTIONS 請求。而即使有對 OPTIONS 請求做跨域允許的話,那么也很容易因為缺少相應(yīng)的 Access-Control-Allow-HeadersAccess-Control-Allow-Methods 響應(yīng)頭導(dǎo)致請求仍然失敗。

第 8 點也是一個非常重要的關(guān)鍵點。如果你有接口需要對多個不同域名的網(wǎng)站提供服務(wù)的話,那么你的接口就不能使用 cookie 等身份憑證了,畢竟 Access-Control-Allow-Origin 不能設(shè)置為通配符,限制了接口使用的對象。

彩蛋時間

前面提到了只有非簡單請求才會觸發(fā) OPTIONS 請求,而滿足簡單請求也就只有那三個條件。但是事實并不是想象中的那么完美。

假如你使用了 XMLHttpRequest 來實現(xiàn)文件上傳的話,如果在 xhr.upload 這個對象里面添加任何事件監(jiān)聽,就會觸發(fā) OPTIONS 請求。即使此時該請求本身是滿足簡單請求的三個條件的。而一旦把事件監(jiān)聽去掉就沒有。具體可以參考 demo-10、 demo-11、 demo-12

這個「bug」是我當(dāng)初在編寫 uploader 這個庫時無意間發(fā)現(xiàn)的,我當(dāng)時還以為是瀏覽器的 bug ,但是后來在 Stackoverflow 進(jìn)行一番搜索后才發(fā)現(xiàn),原來這是瀏覽器隱藏的一個 「feature」。。

Turns out this is not a bug. The spec for XMLHttpRequest does mention that upload progress event handlers should cause the "force preflight" flag to be set. I was a bit confused when this was not specifically mentioned in the CORS spec, even though that spec does reference the existence of a "force preflight" flag.

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/81696.html

相關(guān)文章

  • JavaScript 的同源策略及其"CORS"跨域方案

    摘要:設(shè)置的值,為其當(dāng)前域或其當(dāng)前域的父域。場景文檔中的一個腳本執(zhí)行以下語句即可通過同源檢測跨源網(wǎng)絡(luò)訪問同源策略控制了不同源之間的交互。服務(wù)器確認(rèn)允許之后,才發(fā)起實際的請求。 文章大綱 同源策略 同源是什么? 如何跨源,以及場景應(yīng)用 源的更改 跨源網(wǎng)絡(luò)訪問 跨源腳本API訪問 跨源數(shù)據(jù)存儲訪問 了解CORS CORS是什么? CORS功能概述 CORS關(guān)于Cookie ...

    maochunguang 評論0 收藏0
  • JavaScript跨域方式

    摘要:跨域原因同源策略在客戶端編程語言中,如和,同源策略是一個很重要的安全理念,它在保證數(shù)據(jù)的安全性方面有著重要的意義。同源策略規(guī)定跨域之間的腳本是隔離的,一個域的腳本不能訪問和操作另外一個域的絕大部分屬性和方法。由兩部分組成回調(diào)函數(shù)和數(shù)據(jù)。 1.JavaScript跨域原因--同源策略 在客戶端編程語言中,如javascript和 ActionScript,同源策略是一個很重要的安全理...

    Cciradih 評論0 收藏0
  • javascript跨域

    摘要:實現(xiàn)跨域的原理通過方式請求載入并執(zhí)行一個文件,相當(dāng)于通過的形式的導(dǎo)入一個外部的方法語法該函數(shù)是簡寫的函數(shù),等價于在中,您可以通過使用形式的回調(diào)函數(shù)來加載其他網(wǎng)域的數(shù)據(jù),如。將自動替換為正確的函數(shù)名,以執(zhí)行回調(diào)函數(shù)。 更多詳情見http://blog.zhangbing.club/Ja... 最近在項目開發(fā)的過程中遇到一些Javascript 跨域請求的問題,今天抽空對其進(jìn)行總結(jié)一下,以...

    PingCAP 評論0 收藏0
  • 大話javascript 5期:跨域

    摘要:同源策略所謂同源是指協(xié)議,域名,端口均相同。同源策略是瀏覽器的一個安全功能,不同源的客戶端腳本在沒有明確授權(quán)的情況下,不能讀寫對方資源。需注意的是由于同源策略的限制,所讀取的為跨域請求接口所在域的,而非當(dāng)前頁。 一、什么是跨域 1.URL解析 URL (Uniform Resource Locator )統(tǒng)一資源定位符(URL)是用于完整地描述Internet上網(wǎng)頁和其他資源的地址的...

    jzzlee 評論0 收藏0

發(fā)表評論

0條評論

閱讀需要支付1元查看
<